lwIP is a lightweight, open-source TCP/IP stack used in embedded devices. It brings IP networking to systems that cannot carry the footprint of a conventional desktop or server stack, including network appliances, sensors, and industrial equipment. Its optional SNMP agent lets management stations query device information and change supported settings.
During our agentic audit of lwIP, we found a flaw in the way that agent copies incoming data into its replies. A fragmented SNMP request can make the agent read beyond a packet buffer and send adjacent memory back to the requester. The response does not have to grow: memory that never belonged to the request can replace part of its echoed contents.
The vulnerability is tracked as CVE-2026-119053 and
affects lwIP 2.0.0 through 2.2.1 with the relevant SNMP
functionality enabled. An attacker needs network access to the agent and
the configured read community for the demonstrated GET path. The default
read community is public.
Our research demonstrated disclosure over the network, including packet-pool pointers in a production-style build. We did not demonstrate a memory write, code execution, or a network-triggered crash from this finding.
This investigation followed the same approach as our earlier lwIP MQTT research: Claude Code assisted source analysis and proof-of-concept development, with validation by Codex as part of our agentic AI pentest harness. Here, the decisive evidence was the content of the agent's reply.
The finding at a glance
| Property | Detail |
|---|---|
| Identifier | CVE-2026-119053 |
| Component | SNMP agent, snmp_pbuf_stream_writeto() |
| Affected releases | lwIP 2.0.0 through 2.2.1 |
| Weakness | CWE-125: out-of-bounds read |
| Demonstrated entry point | Fragmented IPv4 SNMPv1 GET to an enabled raw-API agent over UDP/161 |
| Attacker prerequisites | Reachability, the configured read community, and a request producing the relevant buffer geometry |
| Demonstrated outcome | Adjacent receive-buffer or pool memory returned in the response |
| CNA severity | Medium: CVSS 3.1 5.3; CVSS 4.0 6.9 |
| Upstream fix | e98415014aca66ef0daefded8020763b9279a3b2 |
The CVE record credits Julio Fort of Blaze Information Security as finder and remediation developer. The upstream report is Savannah bug #68802, with a corresponding VulnCheck advisory.
How fragmentation creates the relevant buffer chain
lwIP represents packet data using pbuf structures. Each
pbuf describes a payload region and its length; a logical packet can
span several linked pbufs. Those links matter. The next bytes in the
logical packet are in the next pbuf's payload, which is not necessarily
the memory immediately following the current payload.
A field can begin partway through a pbuf and continue into another. A copy routine must locate the starting buffer, determine the offset inside it, and copy only the bytes remaining before that buffer ends. It must then follow the chain to obtain the rest.
In the tested raw-API path, IPv4 reassembly supplied the chained receive buffers. Fragmenting the request at selected boundaries gave us a way to arrange where the SNMP variable bindings—the list of object identifiers and values carried by the request—began within that chain. IPv4 reassembly is enabled by default in lwIP, although individual products can override its configuration and network equipment can filter fragments.

A copy length that forgot its starting offset
The vulnerable helper is in src/apps/snmp/snmp_pbuf_stream.c.
It uses pbuf_skip() to locate the pbuf holding the current
stream position. That call also supplies target_offset, the
position inside the returned pbuf.
Despite its name, target_offset is used here to address
the source payload. The vulnerable code locates the
right starting byte, then uses the wrong limit:
/* Vulnerable loop excerpt; surrounding checks and cursor updates omitted. */
u16_t target_offset;
struct pbuf *pbuf = pbuf_skip(pbuf_stream->pbuf,
pbuf_stream->offset, &target_offset);
if ((pbuf == NULL) || (pbuf->len == 0)) {
return ERR_BUF;
}
chunk_len = LWIP_MIN(len, pbuf->len);
err = snmp_pbuf_stream_writebuf(target_pbuf_stream,
&((u8_t *)pbuf->payload)[target_offset], chunk_len);len bounds the number of bytes still to copy in the
logical stream. pbuf->len describes the whole current
payload. Neither tells the routine how many bytes remain
after target_offset within that
payload.
Consider an illustrative chain with two 96-byte payloads. The requested field starts 40 bytes into the first payload and contains 152 bytes: 56 bytes in the first pbuf and 96 in the second.
The correct first copy is min(152, 96 - 40), or
56 bytes. The vulnerable first copy is
min(152, 96), or 96 bytes. Starting at
offset 40 and reading 96 bytes reaches offset 136: 40 bytes
beyond the first payload's end.

The overread is not always equal to the offset. If the remaining
requested length is small enough, the copy can stay within the payload.
More generally, it occurs when min(len, pbuf->len)
exceeds pbuf->len - target_offset. The attack needs both
a nonzero intra-pbuf offset and enough requested data to cross the
boundary.
Why an SNMP error returns the overread to the sender
An invalid read alone does not establish network disclosure. We also needed a response path that copies those bytes into an outbound packet.
The SNMP response builder provides one. In snmp_complete_outbound_frame(),
error responses and successful SET responses reuse the request's
variable bindings. The code initializes an inbound stream at the
recorded binding offset and copies it into the outbound stream:
/* Response-builder excerpt; macro wrappers and other setup omitted. */
snmp_pbuf_stream_init(&inbound_stream, request->inbound_pbuf,
request->inbound_varbind_offset,
request->inbound_varbind_len);
snmp_pbuf_stream_writeto(&inbound_stream,
&(request->outbound_pbuf_stream), 0);The final argument, zero, tells this helper to copy the minimum available length of the two streams. It does not request a zero-byte copy.
Our network reproduction used an SNMPv1 GET for an unimplemented OID.
The resulting noSuchName error entered the echo path. That
let us trigger the copy using the read community, without changing a
managed value or requiring a successful SET.
After the oversized first copy, the helper advances its logical stream cursor by that same oversized amount. In the two-buffer example, it then resumes 40 bytes into the second pbuf, skipping 40 legitimate request bytes. The output still contains 152 bytes, but 40 come from adjacent memory instead of the request.

What we observed on the wire
Our July 17, 2026 validation used the real lwIP Unix-port SNMP daemon with AddressSanitizer enabled. The reproducer sent a 575-byte SNMPv1 request as seven IPv4 fragments and captured a 575-byte reply. The supplied execution record reported:
[*] sending 575-byte SNMPv1 request as 7 IPv4 fragments
[*] captured 575 response bytes
[+] CONFIRMED: response disclosed ASan heap-slack poison at offset 88
The disclosed region contained runs of 0xbe, the
fresh-allocation fill used by that sanitizer runtime. Those runs were
absent from the request. This was evidence of unintended bytes reaching
the requester, beyond a source-code inference or a sanitizer report that
stopped execution before transmission.
The log calls the bytes “poison”; more precisely, the observed
0xbe pattern was allocator fill. A separate direct harness
exercised the real copy helper and produced an AddressSanitizer
invalid-read report. The direct test and packet capture answered
different questions: where the read crossed a memory boundary, and
whether unintended contents reached the network.
We revisited the finding on October 11 using a non-sanitizer build with the default contiguous static pbuf pool. Captured replies contained live pointers into that pool that were absent from the requests, repeatably across runs with address randomization enabled.
A pool pointer can disclose the base of its containing static region when its relative offset is known. That establishes address disclosure on the tested build. It does not establish a universal address-space randomization bypass across embedded products: memory layouts and even the presence of randomization vary.
Our research records also document reproduction on
STABLE-2_2_1_RELEASE and development master
d08f4773. A patched-tree comparison removed the disclosure
from the live response. These observations distinguish the confirmed
outcome from possible consequences that depend on a particular device's
memory contents.
Exposure and the limits of the primitive
SNMP is optional and disabled by default through
LWIP_SNMP=0. Our demonstrated path used the raw-API
backend, the default choice when the agent is enabled. The vulnerable
helper is shared SNMP code; testing one backend should not be treated as
proof that every other integration is safe.
For the demonstrated GET path, the attacker must reach UDP/161 and
supply the configured read community. SNMPv1 and v2c community strings
travel in clear text. Leaving public unchanged makes that
prerequisite trivial for a reachable agent. Changing the string can
restrict access, but it does not repair the memory-safety defect.
The attacker can influence request layout and the buffer geometry exposed by fragmentation. They do not get a general-purpose read of an arbitrary chosen address. The disclosed bytes come from memory adjacent to the payload being overread, and their value depends on allocation strategy, pool layout, and current contents.
Our tests established allocator-fill and pointer disclosure. Stale traffic or other useful data is a possible consequence where it occupies the relevant memory; we did not establish extraction of a particular password, key, or customer record. Likewise, no memory write or control-flow takeover was demonstrated. A sanitizer abort in the direct harness is not evidence of a remotely induced production crash.
The CNA rates the issue Medium, with CVSS 3.1 5.3 and CVSS 4.0 6.9. Both published vectors describe confidentiality impact without integrity or availability impact. Actual exposure depends on the product's enabled features and management-network access.
The upstream fix
Upstream commit e98415014aca66ef0daefded8020763b9279a3b2,
authored October 18, 2026, corrects the source-buffer bound:
/* Fixed bounds calculation from the upstream patch. */
if ((pbuf == NULL) || (pbuf->len == 0) ||
(target_offset >= pbuf->len)) {
return ERR_BUF;
}
chunk_len = LWIP_MIN(len, (u16_t)(pbuf->len - target_offset));The guard rejects an unusable starting offset. Subtracting that offset then limits the copy to bytes belonging to the current payload. The next loop iteration locates the next logical position through the pbuf chain, rather than treating adjacent memory as packet data.
Firmware teams should check the actual lwIP source compiled into their products, including vendored SDK copies. Verify that the fix is present or backport it; the affected-version range alone does not identify a fixed release. Where the agent is unnecessary, disable it. Where it is needed, restrict access to trusted management hosts and remove default communities as additional controls.
Regression coverage should vary the field's starting offset and fragmentation boundaries, including exact-end and multi-pbuf cases. Check the error-response and successful-SET echo paths, and compare the echoed bindings byte for byte with the intended request section. A response-length assertion alone would miss the replacement shown above.
Disclosure and credit
The research records trace source analysis to June 2026, network disclosure validation to July 17, and static-pool pointer disclosure validation to October 11. The upstream patch carries an October 18 author date. VulnCheck published the CVE record on October 29, 2026.
The public issue is Savannah #68802. VulnCheck's CNA record credits Julio Fort, Blaze Information Security, for discovery and remediation development. We thank the lwIP maintainers for integrating the correction and VulnCheck for assigning CVE-2026-119053.
The failed invariant was small: a payload's length is not the number of bytes available after an offset into it. In a reply-building path, that arithmetic mistake became a confidentiality boundary failure. The strongest proof was equally concrete: bytes absent from the request appeared in the response.
References
- Upstream report: Savannah bug #68802
- VulnCheck advisory for CVE-2026-119053
- Published CNA record in the CVE List
- Upstream remediation commit
- Affected SNMP stream implementation in lwIP 2.2.1
- SNMP response builder in lwIP 2.2.1
- SNMP configuration defaults in lwIP 2.2.1
- Blaze Labs: our earlier lwIP MQTT investigation



