【lwIP】Bug #1 | Crash in accept() | The Double Free Bug
Japanese version available here: 日本語版
This series covers real-world bugs, vulnerabilities, reproduction steps, minimal fixes, and debugging know-how for lwIP — the TCP/IP stack widely used in embedded systems.
▶ English article index: lwIP Troubleshooting Notes
■ Have You Seen These Symptoms?
Your TCP server suddenly stops with an ASSERT
You see ASSERT messages like `netconn state error`
The device crashes or becomes unresponsive at specific connection timings
The problem tends to occur during network scans or port scans
This may be caused by a known bug in lwIP.
■ Affected Versions and Configurations
lwIP version: lwIP 2.1.2 (or earlier)
Affected configuration: TCP server started via the Socket API
■ What You'll Learn
Step-by-step reproduction
How to reproduce with the nmap command and the required TCP packet conditions
Alternative reproduction method using a Python script
Internal behavior traced through the source code
Root cause of the Double Free in the error path of accept()
How the double memory release happens internally
Why Use-After-Free leads to ASSERT stops and HardFaults
How to avoid it
Minimal fix patch

■ Problem Overview
The `lwip_accept()` function in `sockets.c` of lwIP 2.1.2 and earlier has a Double Free bug in its error-handling code path.
This bug can cause Use-After-Free access to already-freed memory, which may lead to crashes or ASSERT stops depending on build settings and timing.
In some environments, a simple TCP port scan is enough to trigger this bug.
The root cause lies in `lwip_accept()`'s error path: when a connection error is detected, the deallocation of `newconn` (`struct netconn`) via `netconn_delete()` is executed twice.
■ Reproduction Steps
Important (please review before proceeding): The following steps must be performed only in a test environment and isolated network that you manage. Unauthorized scanning of devices or networks managed by a third party may violate unauthorized access laws, regardless of whether any damage occurs. Ensure you have management authority over the target device and act only within the scope of what is permitted.
Prerequisites
lwIP 2.1.2 (or earlier)
A TCP port in LISTEN state (e.g., an HTTP server on port 80)
The target uses the Socket API (`lwip_accept`)
An environment capable of running a TCP full-connect scan (e.g., nmap)
Steps
Step 1: Verify the target is running normally
ping <target IP address>Confirm that ping responses are received.
Step 2: Run a TCP full-connect scan with nmap
What is nmap: An open-source port scanner for discovering hosts and open ports on a network. Runs on Linux, macOS, and Windows. Supports multiple scan modes including `-sT` (TCP full connect) and `-sS` (SYN scan).
nmap -sT -p <open port> <target IP address>`-sT`: TCP full-connect scan (completes the 3-way handshake, then immediately sends RST)
`-p <open port>`: Specify the port the server is LISTENing on (e.g., `-p 80`)
Note: This bug is timing-dependent, so a single scan may not trigger it. If it does not reproduce, run the scan repeatedly. The following loop is efficient:
for i in $(seq 1 10); do
nmap -sT -p <open port> <target IP address>
sleep 1
done
Step 3: Observe the crash
If `LWIP_NOASSERT` is not defined, the ASSERT will fire. In upstream lwIP 2.1.2 with default settings, the output looks like:
Assertion "netconn state error" failed at line xxxx in api_msg.cThe exact format depends on the `LWIP_PLATFORM_ASSERT` implementation and varies by platform.
Other symptoms include:
TCP thread stops
Device hangs
Watchdog reset (if a watchdog is present)
If the crash does not occur, repeat Step 2. The target will stop once the crash happens.
If nmap is not available, an alternative reproduction method using a Python script with `SO_LINGER` is provided in the paid section (after the fix methods).
■ Reproduction, Root Cause, and Fix
This section has covered the affected conditions, problem overview, and reproduction steps.
Next, as an introduction to the source code analysis, we briefly cover the minimum needed to understand the overall picture of the problem.
Detailed code tracing, fix methods, verification after applying the fix, and official fix status are covered in the paid section below.
For readers dealing with the same issue, or manufacturers who need to patch already-shipped products, the paid section provides concrete, immediately usable information.
For broader context — how lwIP fits into the embedded ecosystem, how it varies across vendor SDKs, and why tracking these known bugs matters — see the introduction article:
【lwIP】Common Bugs, Vulnerabilities and Fixes | Embedded Engineer's Field Notes
■ Source Code Analysis
We trace through the `lwip_accept()` function in `sockets.c` of lwIP 2.1.2.
【Analysis 1】Normal Flow of lwip_accept()
When `accept()` is called, `lwip_accept()` executes. The normal processing flow is:
// sockets.c lwip_accept()
// 1. Dequeue a new connection from the accept queue
err = netconn_accept(sock->conn, &newconn);
// 2. Allocate a socket number and bind it to newconn
newsock = alloc_socket(newconn, 1); // newsock != -1 → success
nsock = &sockets[newsock - LWIP_SOCKET_OFFSET];
// ↑ At this point nsock->conn = newconn is set (inside alloc_socket)
// 3. Retrieve the remote IP address and port
err = netconn_peer(newconn, &naddr, &port); // normally ERR_OK
// 4. Return successfully
return newsock;【Analysis 2】The Error Path Where the Bug Hides
Look at the code executed when `netconn_peer()` fails:
ここから先は
¥ 500
この記事が気に入ったらチップで応援してみませんか?
