From 1deadf69730fb89c5765ea5232091ec71843ea9b Mon Sep 17 00:00:00 2001 From: Andy Postnikov Date: Fri, 8 May 2026 03:23:55 +0200 Subject: [PATCH] fix(tls): validate cert/key pair and guard wildcard SAN matcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two TLS hygiene fixes from the security audit (V3, PR-D slot in security-audit.md): * Missing SSL_CTX_check_private_key() — `src/nxt_openssl.c:522`. After SSL_CTX_use_PrivateKey() succeeds, also verify the loaded private key actually corresponds to the public key in the cert. Previously, a mismatched key/cert pair was accepted at config time and only surfaced as a TLS handshake failure at runtime, which presents to operators as a client-side problem. * 1-byte OOB read in wildcard SAN matcher — `src/nxt_openssl.c:986`. The check `item->name.start[0] == '*'` read byte 0 without first confirming `item->name.length > 0`. A cert with a zero-length SAN entry would dereference one byte past the buffer. Guard with `length > 0 &&` before the start[0] read. Two other V3 findings audited and reclassified as false positives (not addressed here): * "Cert chain mutated on active SSL_CTX during reload" (`nxt_openssl.c:504-510`) — on closer reading, the SSL_CTX in question is freshly created in nxt_openssl_server_init() and only reaches the listener / SNI dispatch table after nxt_openssl_chain_file() completes; SSL_CTX_add0_chain_cert() runs against a private context. No concurrent handshakes can hit it. * "realloc failure leaves chain count inconsistent" (`nxt_cert.c:278-283`) — cert->count is incremented only on the success path (line 288), and the just-parsed X509 is not yet in cert->chain[] when realloc fails; POSIX realloc leaves the original block intact on failure, so cert and chain[0..count-1] remain consistent for nxt_cert_destroy(). No protocol or config-surface changes. Co-Authored-By: Claude Opus 4.7 --- CHANGES | 7 +++++++ src/nxt_openssl.c | 6 ++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 8ae835854..913be643d 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,13 @@ Changes with FreeUnit 1.35.4 xx xxx 2026 + *) Bugfix: validate that the loaded TLS private key matches the + certificate; previously a mismatched key/cert pair was accepted + at config time and only surfaced as a handshake failure. Also + guard the wildcard-name SAN matcher against an empty name (1-byte + OOB read of item->name.start[0] when the cert had a zero-length + SAN entry). + *) Bugfix: fix router process CPU spin and connection hang under port scanning load; CLOSE-WAIT sockets are now cleaned up properly on client FIN, idle connection queue iteration fixed, systemd file diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 265542aae..78de39935 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -519,7 +519,9 @@ nxt_openssl_chain_file(nxt_task_t *task, SSL_CTX *ctx, nxt_tls_conf_t *conf, goto end; } - if (SSL_CTX_use_PrivateKey(ctx, key) == 1) { + if (SSL_CTX_use_PrivateKey(ctx, key) == 1 + && SSL_CTX_check_private_key(ctx) == 1) + { ret = NXT_OK; } @@ -983,7 +985,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh, str = item->name; - if (item->name.start[0] == '*') { + if (item->name.length > 0 && item->name.start[0] == '*') { item->name.start++; item->name.length--;