Skip to content

mod_ssl: fix leak of X509 reference in ssl_hook_Access_classic quick renegotiation path#649

Open
rootvector2 wants to merge 1 commit into
apache:trunkfrom
rootvector2:fix-ssl-engine-kernel-peer-cert-leak
Open

mod_ssl: fix leak of X509 reference in ssl_hook_Access_classic quick renegotiation path#649
rootvector2 wants to merge 1 commit into
apache:trunkfrom
rootvector2:fix-ssl-engine-kernel-peer-cert-leak

Conversation

@rootvector2
Copy link
Copy Markdown

While reading ssl_hook_Access_classic(), I noticed that the renegotiate_quick branch obtains a peer certificate with SSL_get_peer_certificate(ssl) and never releases it on the common path.

The leak

cert_stack = (STACK_OF(X509) *)SSL_get_peer_cert_chain(ssl);
cert = SSL_get_peer_certificate(ssl);   /* refcount +1, owned by caller */

if (!cert_stack || (sk_X509_num(cert_stack) == 0)) {
    ...
    cert_stack = sk_X509_new_null();
    sk_X509_push(cert_stack, cert);     /* (A) ownership now in stack */
}
...
if (cert_stack != SSL_get_peer_cert_chain(ssl)) {
    sk_X509_pop_free(cert_stack, X509_free);  /* frees (A) */
}

SSL_get_peer_certificate() increments the X509 refcount and returns a reference the caller owns. Every other call site in this file releases it with X509_free() (lines 319, 615, 1531).

The trailing sk_X509_pop_free() only runs when we built the stack ourselves — i.e., only when the original SSL_get_peer_cert_chain() was empty/NULL. In the common case where the peer chain already contains certificates, the reference is never freed, leaking one X509 refcount per quick renegotiation. The two early-return paths ("Cannot find certificate storage" and the X509_STORE_CTX_init failure return added in bz 65902) leak unconditionally.

This matches the documented OpenSSL contract for SSL_get_peer_certificate(3):

The reference count of the X509 object [...] is incremented by one [...]. The X509 object must be explicitly freed using X509_free().

The fix

Track the owned reference as peer_cert. When ownership is transferred into the freshly-created stack via sk_X509_push(), clear peer_cert so sk_X509_pop_free() remains the sole owner and there is no double free. Release peer_cert on every exit path.

Only affects TLS ≤ 1.2 renegotiation (ssl_hook_Access_classic); TLS 1.3 has no renegotiation and uses ssl_hook_Access_modern.

Notes for the reviewer

  • Found by static reading. I have not run a leak-sanitizer reproducer; the analysis follows the OpenSSL ownership contract and the pattern used by the other SSL_get_peer_certificate() sites in this file.
  • No CHANGES entry — happy to add one in the format the committer prefers.

In ssl_hook_Access_classic(), when SSL renegotiation is performed via
the quick path, SSL_get_peer_certificate() is called and its result is
used to seed the certificate stack passed to X509_STORE_CTX_init().

SSL_get_peer_certificate() increments the X509 refcount and the caller
owns the returned reference. Every other call site in this file releases
the reference with X509_free(); this one did not, except in the narrow
case where the original peer chain was empty (or NULL) and we built a
fresh stack with sk_X509_push(), in which case the trailing
sk_X509_pop_free() happens to release it.

In the common path -- non-empty SSL_get_peer_cert_chain(ssl) -- the
returned reference was never released, leaking one X509 reference per
quick renegotiation for the lifetime of the SSL_CTX. The two early
returns at "Cannot find certificate storage" and the X509_STORE_CTX_init
failure path leaked unconditionally.

Track the owned reference separately as peer_cert and release it in all
exit paths. When ownership is transferred into the freshly-created stack
via sk_X509_push(), peer_cert is cleared so sk_X509_pop_free() remains
the sole owner and there is no double free.
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.

1 participant