Skip to content

tlshd: Deinit output variable in __tlshd_config_get_privkey on error#157

Merged
chucklever merged 1 commit into
oracle:mainfrom
trchen1033:issue_156
Jun 22, 2026
Merged

tlshd: Deinit output variable in __tlshd_config_get_privkey on error#157
chucklever merged 1 commit into
oracle:mainfrom
trchen1033:issue_156

Conversation

@trchen1033

Copy link
Copy Markdown
Contributor

Fixes a small memory leak where the output variable was constructed but not destructed on error exit.

Fixes: #156

Comment thread src/tlshd/config.c Outdated
free(data.data);
if (ret != GNUTLS_E_SUCCESS) {
tlshd_log_gnutls_error(ret);
gnutls_privkey_deinit(*privkey);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracking this down -- the leak is real. gnutls_privkey_init() allocates the key object, and a failed gnutls_privkey_import_x509_raw() leaves it allocated, so the early return false does drop it.

One thing to sort out before this lands: where the key gets freed on the normal path. These keys are owned by tlshd_x509_client_put_privkey() / tlshd_x509_server_put_privkey(), which unconditionally gnutls_privkey_deinit() the module-global keys. On the TLS handshake paths a get_privkey() failure jumps to the cleanup label (out_free_certs in client.c, out_deinit_session in server.c), which calls put_privkey(). So with this patch the same key is deinit'd twice -- once here, once in put_privkey() -- a double-free.

The leak actually bites on the QUIC paths, where the failure handler does a bare return and never reaches put_privkey(). Both behaviors are real; they just live on different call paths.

There's also a quieter case: tlshd_config_get_privkey() ignores the return value of the PQ-key call. If the PQ key file exists but fails to import while the regular key succeeds, the handshake proceeds, and this patch will have left the PQ key dangling for put_privkey() to deinit again later.

Nulling the pointer after freeing it covers all of these:

Suggested change
gnutls_privkey_deinit(*privkey);
gnutls_privkey_deinit(*privkey);
*privkey = NULL;

That plugs the QUIC-path leak and makes the second deinit in put_privkey() a no-op. gnutls_privkey_deinit(NULL) is already safe and relied upon -- when get_certs() fails before get_privkey(), put_privkey() deinits the still-NULL globals on every handshake.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, sorry for underestimating the extent of the problem and made a bad patch. I traced through the object lifetime this time and agree what you've suggested is the best way forward.

Fixes a small memory leak where the output variable was constructed but
not destructed on error exit.

Fixes: oracle#156

Signed-off-by: Tien-Ren Chen <trchen1033@gmail.com>
Comment thread src/tlshd/server.c

for (i = 0; i < TLSHD_MAX_CERTS; i++)
gnutls_pcert_deinit(&tlshd_server_certs[i]);
memset(tlshd_server_certs, 0, sizeof(tlshd_server_certs));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, gnutls_pcert_deinit is not idempotent. Sad. :(

@chucklever chucklever left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks -- this is correct. gnutls_privkey_init() allocates the key object and a failed gnutls_privkey_import_x509_raw() leaves it allocated, so deinit'ing the local before the early return plugs the leak without leaving a dangling global for put_privkey() to double-free. Routing the QUIC server path through err_config closes the bare-return leak there, and NULL-ing the globals after deinit keeps the second put a no-op.

The QUIC client path (tlshd_quic_client_set_x509_session()) has the same bare-return pattern; I'll handle that in a follow-up so this can go in.

@chucklever chucklever merged commit 03dc2dd into oracle:main Jun 22, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Small memory leak in __tlshd_config_get_privkey

2 participants