CVE-2023-35846 - How a Length Check Oversight in VirtualSquare PicoTCP-NG Leads to Packet Filtering Bypass
VirtualSquare’s PicoTCP-NG is a lightweight TCP/IP stack often used in embedded environments, IoT devices, and virtualized networking. Its small footprint and ease of integration make it attractive for these use cases. But even well-designed, minimal code can hide surprising security pitfalls.
In this article, we break down CVE-2023-35846, a vulnerability arising from insufficient validation in the PicoTCP-NG transport layer. We’ll see why untrusted packets can bypass port filters, show where the issue lurks, and explain how an attacker can use it. Let’s dive in.
What is CVE-2023-35846?
CVE-2023-35846 refers to an input validation error in VirtualSquare PicoTCP-NG, up to and including version 2.1. The official description is:
> VirtualSquare picoTCP (aka PicoTCP-NG) through 2.1 does not check the transport layer length in a frame before performing port filtering.
In simpler terms: when a packet (say, UDP or TCP) arrives, the network stack does not properly verify whether the claimed header and payload actually exist in full before using key fields—like destination port—for firewall decisions. This can lead to attackers tricking the system into misclassifying or leaking information.
Where Filtering Fails
In typical implementations, when an incoming network frame (e.g., Ethernet, IP, UDP, or TCP) is processed, the stack should verify that the full contents—including headers and payload—are present before accessing fields like source/destination port or flags.
PicoTCP-NG’s default code path in pico_ipv4_in() (IPv4 processing) and its subsequent handler for processing TCP/UDP does not check that the transport-layer length fits in the IP packet. It may look like this (pseudo code):
void pico_ipv4_in(struct pico_frame *f) {
...
// Assume header is already checked
// Call the port filtering function
switch (proto) {
case PICO_PROTO_TCP:
pico_apply_port_filter(f, TCP);
break;
case PICO_PROTO_UDP:
pico_apply_port_filter(f, UDP);
break;
...
}
}
Inside the port filtering function, the code might access port numbers like this
void pico_apply_port_filter(struct pico_frame *f, int proto) {
if(proto == TCP) {
struct pico_tcp_hdr *tcp = (struct pico_tcp_hdr *)(f->payload + f->offset);
uint16_t dst_port = tcp->dst_port;
// ...check if port is allowed
}
// similar for UDP
}
Notice: The code above assumes tcp points to a valid header. But what if the packet is malformed and too short? *No check* ensures you don’t read past the actual buffer, possibly fetching garbage or out-of-bounds memory.
The Crash Course: Port Filtering and Memory Safety
Network stacks often apply firewall-like rules (e.g., “discard all TCP/UDP packets except to port 80”). If you can trick the filtering code into reading junk, you may:
Step-by-Step Exploit
Say you’re an attacker who discovers a device running PicoTCP-NG with firewall rules only allowing UDP port 12345. You want to inject a UDP frame to port 11111, but normal packets would be dropped.
Craft a UDP Packet with a Short Payload:
You form an IPv4+UDP packet where the UDP header is incomplete (maybe only the source port is present, or less).
Set the UDP Header’s Port Field to Mislead the Filter:
In the real bytes sent, leave out the bytes of the destination port, so the field is missing or gets read as some default/garbage value.
Send the Packet to the Victim:
PicoTCP-NG receives the packet. It tries to apply port filtering, dereferences the missing/partial UDP header, and might misread the destination port.
Result:
The packet passes the filter (if the misread port appears as the allowed port), or the stack crashes or behaves unpredictably.
Minimal Example (Scapy packet creation)
from scapy.all import IP, UDP, send
# Let's send a UDP packet with 5 bytes UDP header (incomplete)
ip = IP(dst="TARGET_IP")
incomplete_udp = bytes([x30, x39]) # Source port 12345 only, missing destination port, etc.
packet = ip/incomplete_udp
send(packet, verbose=)
Note: In practice you’ll need raw sockets or privileged access to send truly malformed UDP.
Why Does This Matter?
- Filtering bypass: An attacker could reach restricted services by tricking the stack’s filter into reading incorrect destination port numbers.
- Information leaks or crashes: If the function reads past the buffer, the result could be denial of service or data leakage.
- Embedded & IoT: PicoTCP-NG is used in resource-constrained devices with less mitigations, making such bugs extra serious.
How To Fix
Developers should always validate that the transport layer header is fully contained in the received buffer before parsing headers or applying filtering logic. For example, check the size before casting:
if ((packet_length - offset) < sizeof(struct pico_tcp_hdr)) {
// Packet too short!
drop_packet();
}
[Patched code or references can be found at:]
- Official PicoTCP-NG GitHub
- CVE-2023-35846 - NVD Entry
References
- NVD Entry for CVE-2023-35846
- PicoTCP-NG Repository
- Original commit fixing length checks
Conclusion
While not as flashy as remote code execution, CVE-2023-35846 shows how the smallest validation checks protect critical code paths. If you’re building or maintaining embedded networked systems, patch early—and always check your lengths before you peek.
Stay safe, and keep an eye out for “out of bounds” bugs—they hide where you least expect!
Timeline
Published on: 06/19/2023 03:15:00 UTC
Last modified on: 06/26/2023 18:11:00 UTC