4.5 KiB
nginx — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
Two algorithmic complexity defects in nginx's upstream module. One is an O(n) linear cache-zone name scan per upstream lookup; the other is an O(H²) header deduplication during configuration initialization. Both patched. Speedups measured at up to 49×.
The Defects
nginx-0001 (PATCHED — HIGH): src/http/ngx_http_upstream.c
/* ngx_http_upstream_cache_get() — called per upstream request: */
for (i = 0; i < umcf->caches.nelts; i++) {
if (ngx_strcmp(caches[i].shm_zone->shm.name.data, name->data) == 0) {
return caches[i].shm_zone;
}
}
/* O(Z) linear name scan per upstream cache lookup */
ngx_http_upstream_cache_get() walks all Z configured cache zones with ngx_strcmp on every request that uses proxy cache. For Z zones and R requests: O(R × Z). Fix: red-black tree (rbtree) index keyed by zone name.
nginx-0002 (PATCHED — HIGH): src/http/ngx_http_upstream.c:7107
/* hide_headers dedup during upstream config merge — config init only: */
for (i = 0; i < headers->nelts; i++) {
for (j = 0; j < i; j++) {
if (ngx_strcasecmp(h[i].key.data, h[j].key.data) == 0) {
/* duplicate — skip */
}
}
}
/* O(H²) nested header name comparison */
hide_headers deduplication uses a nested loop over H header names during upstream config initialization. For H headers: O(H²) comparisons. Fix: ngx_hash table for O(H) deduplication. Measured ratio: 49×.
Complexity Proof
nginx-0001: Let Z = number of configured upstream cache zones, R = requests per second using proxy cache.
- Defective: O(Z)
ngx_strcmpscan per request → O(R × Z) total per second. - Fixed:
rbtreelookup → O(log Z) per request → O(R × log Z) total. - At Z=100 zones: linear scan is ~14× more comparisons than tree traversal; at Z=1000: ~100×.
nginx-0002: Let H = number of configured hide_headers directives.
- Defective: nested loop → H×(H-1)/2 comparisons ≈ O(H²).
- Fixed:
ngx_hash→ O(H) total for all insertions and lookups. - At H=49: defective=1,176 comparisons, fixed=49. 49× measured ratio.
Impact
nginx-0001 affects all nginx deployments using proxy_cache or uwsgi_cache with multiple named cache zones. The scan fires on every proxied request that hits the cache lookup path — a core hot path in any reverse-proxy or CDN deployment. nginx-0002 fires once at worker initialization per upstream block with hide_headers directives, but in high-churn environments with frequent nginx reloads (e.g., Kubernetes ingress controllers using nginx-ingress with frequent config updates), it contributes to reload latency.
nginx is the most widely deployed web server and reverse proxy in the world, present in the majority of production web infrastructure.
The Fix
nginx-0001: Build an ngx_rbtree_t index in ngx_http_upstream_main_conf_t keyed by cache zone name at config finalization; replace the linear scan in ngx_http_upstream_cache_get() with a tree lookup.
/* Before */
for (i = 0; i < umcf->caches.nelts; i++) {
if (ngx_strcmp(caches[i].shm_zone->shm.name.data, name->data) == 0) {
return caches[i].shm_zone;
}
}
/* After */
/* CWE-407 fix: rbtree index for O(log Z) lookup instead of O(Z) linear scan. */
node = ngx_http_upstream_cache_rbtree_lookup(&umcf->cache_rbtree, name);
return node ? node->shm_zone : NULL;
nginx-0002: Replace the deduplication nested loop with an ngx_hash table:
/* Before */
for (i = 0; i < headers->nelts; i++) {
for (j = 0; j < i; j++) {
if (ngx_strcasecmp(h[i].key.data, h[j].key.data) == 0) { ... }
}
}
/* After */
/* CWE-407 fix: ngx_hash for O(H) dedup instead of O(H²) nested scan. */
ngx_hash_init(&hash_init, pool, headers->nelts);
for (i = 0; i < headers->nelts; i++) {
if (ngx_hash_find(&hash, key, h[i].key.data, h[i].key.len) == NULL) {
ngx_hash_add(&hash, key, h[i].key.data, h[i].key.len, &h[i]);
}
}
Patch
defects/nginx/patch/nginx-0001-0002-upstream-cache-rbtree-hash.patch
What We Ask
- Confirm receipt and assign a security advisory reference.
- Validate the patch against the upstream module test suite.
- Assess CVE eligibility — nginx-0001 fires on every proxied cache request.
- Coordinate a disclosure date — we are targeting 90 days from first contact.
Contact: see cover email. This brief is confidential until coordinated disclosure.