CVE-2026-42533: nginx map regex heap buffer overflow
CVE-2026-42533: a pre-auth heap buffer overflow in nginx's HTTP script engine. We reproduced the crash on 1.31.2 and verified the 1.31.3 fix end to end.
CVE-2026-42533 is a heap buffer overflow in nginx's HTTP script engine, the interpreter behind map, set, rewrite, and complex-value string expressions. The root cause is a contract failure between two passes: nginx measures an output value in a length pass, then later copies a different value into the buffer in the copy pass.
The vulnerable path is pre-auth and request-controlled. In the proven variant, URI length directly sizes the overflow. On nginx 1.31.2, that path crashes the worker. It is classified CWE-122 heap buffer overflow and scored CVSS 3.1 8.1 HIGH by F5 and CVSS 4.0 9.2 CRITICAL by NVD; the advisory notes that code execution is possible if ASLR is disabled or an attacker can bypass it.
Evidence status
The vulnerable path
The repro fits in a few lines because the bug is not in request parsing. It is in how nginx evaluates script variables with side effects.
map $uri $map { ~(?<capture>.*) $capture; }
server {
listen 127.0.0.1:8081;
location / {
set $capture "";
set $temp "$capture $map";
add_header X-Temp "$temp";
return 200 "ok\n";
}
}
| Step | What happens |
|---|---|
| Length pass | nginx evaluates $capture as "", adds one space, then evaluates $map as the URI length N. It allocates N+1 bytes. |
| Side effect | Evaluating $map runs the regex against $uri and overwrites $capture with the full URI. |
| Copy pass | nginx re-reads $capture as N bytes, adds one space, then copies $map as N bytes. |
| Overflow | The copy pass writes 2N+1 bytes into an N+1 byte heap allocation. The overflow is N bytes, controlled by the request URI length. |
That is the useful shape of this class of bug: the first pass and second pass are both internally reasonable, but they are no longer talking about the same value.
The obvious fix is a trap
The tempting patch is local. Make this map non-volatile. Cap the capture length. Special-case regex-backed maps. Each of those fixes one observed path and leaves the script engine trusting an invariant it does not enforce.
| Candidate fix | Why it is incomplete |
|---|---|
| Make the map value non-volatile | It narrows one timing path, but does not make copy operations safe when length and copy diverge for another reason. |
| Cap capture length | It reduces the blast radius of this repro, but still permits stale sizing if the copied value changes after measurement. |
| Special-case this regex capture path | It treats map as the bug, when the bug is the engine writing without a boundary check. |
The real invariant belongs at the write site. Every copy operation needs to know where the allocation ends, and it needs to stop there unconditionally.
The fix
The core fix in b767540492e, by Maxim Dounin, adds an e->end boundary pointer to the script engine and introduces ngx_http_script_check_length(). The guard is called from the copy operations that can write into the evaluated buffer: copy code, variable copy, regex start and end, capture copy, and complex-value copy.
If length and copy disagree, nginx now aborts the script evaluation with HTTP 500 and logs [alert] no buffer space in script copy. It does not continue writing into heap memory.
The follow-up commits carry the same boundary model through adjacent script users. 25f920eca97 extends the guard to proxy, FastCGI, SCGI, uwsgi, gRPC, index, and try_files paths. a8289aa69c7 trims the final value to the actual written length, closing a related trailing-uninitialized-bytes leak. 4d32a2703c7 applies the guard to the access-log script engine.
How we verified it
Assurance tested real nginx binaries built from release-1.31.2 at 2fd01ed4 and release-1.31.3 at 073ab5db, with gcc 12.2 on aarch64, AddressSanitizer, and PCRE2. The same config and the same request were used on both sides.
Evidence status
The regression check matters because the fix sits in a shared interpreter, not in a narrow handler. A boundary guard that breaks valid interpolation would be safer than a heap overflow, but still wrong.
| Check | Result |
|---|---|
Ordinary map + regex + set config | Byte-identical correct output on 1.31.2 and 1.31.3 |
| Patched nginx test suite, affected map and rewrite paths | map.t, map_complex.t, map_volatile.t, rewrite.t, rewrite_set.t, 61 tests passed |
| Patched nginx test suite, adjacent script users | ssi.t, proxy_variables.t, rewrite_if.t, rewrite_unescape.t, 84 tests passed |
| Same five affected-module tests on vulnerable 1.31.2 baseline | Passed, confirming no regression introduced by the fix path |
| Total (script-engine-adjacent tests) | 145 tests, 0 regressions |
Security gate result: PASS at FULL fidelity. The vulnerable binary crashed under the request-controlled overflow. The patched binary converted the same input into a contained 500 and kept serving.
Scope
This teardown proves the side-effect map + set variant end to end. That is the path above: regex evaluation of $map overwrites $capture between the length pass and copy pass, then set $temp "$capture $map" writes past the heap allocation.
Assurance also re-drove the volatile map variant at the byte level, inspecting the actual response bytes on both 1.31.2 and 1.31.3 rather than only checking for crash or no crash. That path is a real overflow: the divergence is about 7 bytes, and the same request trips the guard on patched 1.31.3 with HTTP 500 and [alert] no buffer space in script copy. On vulnerable 1.31.2, the write lands inside nginx's pool-allocator slack rather than across an ASan-guarded block boundary, so it does not crash the worker and does not appear in the response. Across both variants and both builds, the emitted response bytes contained no adjacent-heap content. This is confirmed as a write-overflow bug, not a read or leak, consistent with the CWE-122 heap-overflow classification and the absence of any memory-disclosure CWE for this CVE.
Two nearby nginx CVEs should stay out of this one. CVE-2026-60005 is a distinct slice module bug involving uninitialized memory access via unnamed regex captures, a genuine memory-disclosure read rather than this write-overflow path. CVE-2026-42945 is also distinct: an earlier, unrelated rewrite module bug credited to Leo Lin, fixed in 1.31.0 before CVE-2026-42533's 1.31.3 fix. It shares no code path with this fix. The b767540492e fix here touches the generic script engine, not that rewrite-module bug.
The follow-up commits extending the guard to proxy, FastCGI, SCGI, uwsgi, gRPC, index, try_files, and access-log script paths were tracked as part of the fix map, but not independently exercised in this teardown. We drove the core HTTP script path, which is where the proven crash and the proven guard behavior occur.
Close
The bug was a disagreement between two passes. The fix is the thing the interpreter should have enforced all along: the pass that writes is not allowed to believe the pass that measured.