NULL Pointer Dereference in exportPairs() (proxy_wasm_api.h)
Component: proxy-wasm-cpp-sdk (guest-side SDK header, proxy_wasm_api.h)
Pinned commit examined: e5256b0c5463ea9961965ad5de3e379e00486640
Confirmed still present verbatim at current upstream main as of 2026-09-09.
CWE: CWE-476 (NULL Pointer Dereference)
Summary
exportPairs() allocates a marshalling buffer with ::malloc(size) and
passes the result straight to marshalPairs(), which writes through it
unconditionally, without ever checking whether the allocation succeeded:
template <typename Pairs> void exportPairs(const Pairs &pairs, const char **ptr, size_t *size_ptr) {
if (pairs.empty()) {
*ptr = nullptr;
*size_ptr = 0;
return;
}
size_t size = pairsSize(pairs);
char *buffer = static_cast<char *>(::malloc(size));
marshalPairs(pairs, buffer); // <-- no NULL check before this write
*size_ptr = size;
*ptr = buffer;
}
marshalPairs()'s first action is an unconditional write through buffer
(a uint32_t length-prefix write), followed by further writes/memcpys
for each key/value pair. If malloc() returns NULL -- under memory
pressure, or when marshalling a sufficiently large set of headers/pairs --
this becomes a write through address 0x0.
Affected call sites
exportPairs() is a template used by two SDK convenience wrappers in the
same file, both of which inherit the same defect with no additional
mitigation:
sendLocalResponse(uint32_t response_code, ...) (~line 1112, free
function -- body calling exportPairs() around ~line 1118)
setHeaderMapPairs(WasmHeaderMapType, const HeaderStringPairs &) (~line 1244)
Any caller of these two functions -- or of exportPairs() directly -- is
exposed. This is not specific to any one embedder; it affects any
proxy-wasm-cpp-sdk guest module consumer (Envoy's WASM filter path is one
such consumer, and is how this was originally found while auditing a
vendored copy of this header, but the defect is upstream in
proxy-wasm-cpp-sdk itself, not an Envoy-specific issue).
Root cause
Straightforward missing-NULL-check-after-allocation. Grepping the file for
other ::malloc(size) call sites shows the same pattern repeated with no
existing convention for handling allocation failure anywhere in this file:
getProperty(const std::initializer_list<std::string_view> &) (~line 909)
getProperty(const std::vector<S> &) (~line 936)
MakeHeaderStringPairsBuffer(...) (~line 1396)
All four sites call ::malloc() and immediately write through the result
with no check. There is no existing malloc-null-check idiom elsewhere in
the file to match, so a fix has to introduce the convention rather than
follow one.
Suggested fix
Add a NULL check on buffer immediately after the malloc() call in
exportPairs(), e.g.:
size_t size = pairsSize(pairs);
char *buffer = static_cast<char *>(::malloc(size));
if (buffer == nullptr) {
*ptr = nullptr;
*size_ptr = 0;
return;
}
marshalPairs(pairs, buffer);
*size_ptr = size;
*ptr = buffer;
(Callers of exportPairs() already need to handle *ptr == nullptr as a
valid "empty" case for the zero-pairs path, so this reuses an existing
contract rather than introducing a new one.) The same missing-check pattern
at the three other ::malloc() sites listed above should be considered as
part of the same fix, since they share the identical defect shape.
Evidence: ASan crash reproduction
Built a minimal standalone harness (test_exportpairs_nullderef.cc)
against the pinned-commit header, compiled with
-fsanitize=address -g -O0 -std=c++17, calling exportPairs() directly
with one key/value pair sized so the marshalled buffer requires
~5,000,017 bytes.
Forcing the allocation to fail (ASAN_OPTIONS=allocator_may_return_null=1:max_allocation_size_mb=1)
produces:
==2048==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x61c16549dea7 ...)
==2048==The signal is caused by a WRITE memory access.
==2048==Hint: address points to the zero page.
#0 ... in marshalPairs<...>(...) proxy_wasm_api.h:177
#1 ... in exportPairs<...>(...) proxy_wasm_api.h:205
#2 ... in main test_exportpairs_nullderef.cc:58
Control run, identical harness and input, no allocation cap
(ASAN_OPTIONS=allocator_may_return_null=0): completes normally, prints
exportPairs succeeded: ptr=0x783493273800 size=5000017, exits 0 --
confirming the crash above is specifically the injected allocation
failure, not a harness defect. Full build command and both runs' complete
stdout/stderr are captured in the accompanying summary file.
Impact
- Denial of Service only. This is a write to a fixed, predictable
address (0x0) with no attacker-controlled offset or content -- there is
no plausible path to memory corruption or code execution from this
specific bug.
- Realistically triggered under general memory pressure (a guest
module marshalling headers/pairs while the host process is already
under allocation stress), not by a single pathological request; the
marshalled size is nonetheless attacker-influenceable via the size and
number of header/pair entries being exported, which affects how much
memory pressure is needed to hit this path.
- Affects any proxy-wasm-cpp-sdk consumer, not just Envoy -- any
embedder linking this SDK's guest-side header inherits the same
unchecked-allocation pattern in exportPairs(), getProperty() (x2),
and [MakeHeaderStringPairsBuffer().](url)
proxy_wasm_exportpairs_poc_20260909_090734_SUMMARY.txt
NULL Pointer Dereference in
exportPairs()(proxy_wasm_api.h)Component:
proxy-wasm-cpp-sdk(guest-side SDK header,proxy_wasm_api.h)Pinned commit examined:
e5256b0c5463ea9961965ad5de3e379e00486640Confirmed still present verbatim at current upstream
mainas of 2026-09-09.CWE: CWE-476 (NULL Pointer Dereference)
Summary
exportPairs()allocates a marshalling buffer with::malloc(size)andpasses the result straight to
marshalPairs(), which writes through itunconditionally, without ever checking whether the allocation succeeded:
marshalPairs()'s first action is an unconditional write throughbuffer(a
uint32_tlength-prefix write), followed by further writes/memcpysfor each key/value pair. If
malloc()returnsNULL-- under memorypressure, or when marshalling a sufficiently large set of headers/pairs --
this becomes a write through address
0x0.Affected call sites
exportPairs()is a template used by two SDK convenience wrappers in thesame file, both of which inherit the same defect with no additional
mitigation:
sendLocalResponse(uint32_t response_code, ...)(~line 1112, freefunction -- body calling
exportPairs()around ~line 1118)setHeaderMapPairs(WasmHeaderMapType, const HeaderStringPairs &)(~line 1244)Any caller of these two functions -- or of
exportPairs()directly -- isexposed. This is not specific to any one embedder; it affects any
proxy-wasm-cpp-sdk guest module consumer (Envoy's WASM filter path is one
such consumer, and is how this was originally found while auditing a
vendored copy of this header, but the defect is upstream in
proxy-wasm-cpp-sdk itself, not an Envoy-specific issue).
Root cause
Straightforward missing-NULL-check-after-allocation. Grepping the file for
other
::malloc(size)call sites shows the same pattern repeated with noexisting convention for handling allocation failure anywhere in this file:
getProperty(const std::initializer_list<std::string_view> &)(~line 909)getProperty(const std::vector<S> &)(~line 936)MakeHeaderStringPairsBuffer(...)(~line 1396)All four sites call
::malloc()and immediately write through the resultwith no check. There is no existing malloc-null-check idiom elsewhere in
the file to match, so a fix has to introduce the convention rather than
follow one.
Suggested fix
Add a NULL check on
bufferimmediately after themalloc()call inexportPairs(), e.g.:(Callers of
exportPairs()already need to handle*ptr == nullptras avalid "empty" case for the zero-pairs path, so this reuses an existing
contract rather than introducing a new one.) The same missing-check pattern
at the three other
::malloc()sites listed above should be considered aspart of the same fix, since they share the identical defect shape.
Evidence: ASan crash reproduction
Built a minimal standalone harness (
test_exportpairs_nullderef.cc)against the pinned-commit header, compiled with
-fsanitize=address -g -O0 -std=c++17, callingexportPairs()directlywith one key/value pair sized so the marshalled buffer requires
~5,000,017 bytes.
Forcing the allocation to fail (
ASAN_OPTIONS=allocator_may_return_null=1:max_allocation_size_mb=1)produces:
Control run, identical harness and input, no allocation cap
(
ASAN_OPTIONS=allocator_may_return_null=0): completes normally, printsexportPairs succeeded: ptr=0x783493273800 size=5000017, exits 0 --confirming the crash above is specifically the injected allocation
failure, not a harness defect. Full build command and both runs' complete
stdout/stderr are captured in the accompanying summary file.
Impact
address (0x0) with no attacker-controlled offset or content -- there is
no plausible path to memory corruption or code execution from this
specific bug.
module marshalling headers/pairs while the host process is already
under allocation stress), not by a single pathological request; the
marshalled size is nonetheless attacker-influenceable via the size and
number of header/pair entries being exported, which affects how much
memory pressure is needed to hit this path.
embedder linking this SDK's guest-side header inherits the same
unchecked-allocation pattern in
exportPairs(),getProperty()(x2),and [
MakeHeaderStringPairsBuffer().](url)proxy_wasm_exportpairs_poc_20260909_090734_SUMMARY.txt