Repository navigation
[Bug] lwip-dhcpd: dhcp_client_alloc infinite loop after lease pool exhaustion — pre-auth remote permanent DoS of the whole lwIP stack (dhcp_server_raw.c) #11878
Description
RT-Thread Version
master 5d1fb66d13 (2026-10-15). Introduced by 330e68e5f4 ("[DHCPD] add
new version DHCPD use lwip raw api.", 2018-06-20); first affected release is
v4.0.0 (2019-01-27). All releases v4.0.0–v5.3.0 and master are affected.
Affected area
Components / lwIP (components/net/lwip-dhcpd, DHCP server)
Hardware/BSP vendor
Not applicable / Other
Architecture
ARM / AArch64 / any (component-level bug; reproduced on Cortex-A9 QEMU, code
path is architecture-independent)
Board and hardware details
QEMU vexpress-a9 (bsp/qemu-vexpress-a9), RT_USING_LWIP + lwIP 2.2.1 +
LWIP_USING_DHCPD=y. Server started through the component's own exported
dhcp_server_start() (the same dhcp_server_recv udp callback that
dhcpd_start() registers; in products it is auto-started by WLAN AP mode —
components/drivers/wlan/dev_wlan_lwip.c:188 — and by the USB ECM/RNDIS
gadget demos via dhcpd_start("u0")).
Develop Toolchain
GCC (arm-none-eabi 12)
Describe the bug
dhcp_client_alloc() in components/net/lwip-dhcpd/dhcp_server_raw.c
(lines 252–263) searches the address pool with an unbounded
goto dhcp_alloc_again loop:
dhcp_alloc_again:
node = dhcp_client_find_by_ip(dhcpserver, &dhcpserver->current);
if (node != NULL)
{
ipaddr = (ntohl(dhcpserver->current.addr) + 1);
if (ipaddr > ntohl(dhcpserver->end.addr))
ipaddr = ntohl(dhcpserver->start.addr);
dhcpserver->current.addr = htonl(ipaddr);
goto dhcp_alloc_again; /* no iteration cap, no exhaustion exit */
}There is no iteration limit and no "pool exhausted" failure path. In
addition node->lease_end is written (lines 380/457) but never read —
leases never expire, so once every address in the pool has a node, the pool
stays exhausted forever. After that, any DISCOVER carrying a new MAC makes
dhcp_client_find_by_ip() return non-NULL for every candidate and the loop
never terminates.
dhcp_server_recv() runs as a udp PCB callback in the lwIP tcpip thread,
so the loop spins the whole network stack thread: no further packet is ever
consumed (ip4_input never runs again) and every thread blocked in a socket
call (including msh while running ping) hangs permanently. Only a reboot
recovers.
Steps to reproduce (full chain under QEMU; scripts available on request):
- Build
bsp/qemu-vexpress-a9with lwIP 2.2.1 +LWIP_USING_DHCPD=y;
start the DHCP server one0with pool 10.0.2.2–10.0.2.254. - Send 253 DHCP DISCOVER packets with unique
chaddr(one UDP packet each,
~300 bytes, no authentication). GDB check:nodes=253,
current=10.0.2.254. - Verify the device works:
ping 10.0.2.2from msh → 4/4, 0% loss. - Send DISCOVER [USB]fix rt_usbd_set_config() BUG which set the USB device to configurat... #254 with a new
chaddr. - Attach GDB — the tcpip thread is spinning inside the loop:
#0 dhcp_client_alloc dhcp_server_raw.c:256
#1 dhcp_server_recv dhcp_server_raw.c:375
#2 udp_input udp.c:404
#3 ip4_input ip4.c:737
#4 ethernet_input ethernet.c:186
#5 tcpip_thread_handle_msg / #6 tcpip_thread
Two GDB samples ~3 s apart show the cursor advancing inside the pool
(current=10.0.2.13 → current=10.0.2.118) — the loop never exits.
Two further probe packets are never processed (ip4_input breakpoint:
0 hits) and msh no longer echoes (help produces no output).
Expected behavior: bounded pool search with an exhaustion failure path
(return NULL → sender gets no OFFER); leases expire and addresses are
recycled.
Actual behavior: permanent pre-auth remote denial of service of the
whole network stack (and any thread waiting on sockets) after 254 UDP
packets; pool exhaustion alone (253 packets) already permanently disables
address allocation.
Other additional context
- Severity: CVSS 3.1
AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H(7.5 High),
CWE-835 (+ CWE-665 for the missing lease lifecycle). - Related fixes in this file so far (
49f985e0fchaddr clamp,c00f13005
pbuf leak,1e24039cf8RELEASE loop) do not touch this path. - Suggested fix: cap the
dhcp_alloc_againwalk at pool-size iterations and
fail cleanly when exhausted; makelease_enda real expiry timestamp
checked by a periodic timer so nodes are recycled.
Activity
github-actions commentedon Oct 17, 2026
如需认领并处理此 Issue,请评论
/claim。To claim and work on this issue, comment
/claim.