From e74be46469c80089cef935d79667f4cca2fc00b0 Mon Sep 17 00:00:00 2001 From: snowpaper <121743268+snowpaper@users.noreply.github.com> Date: Thu, 9 Jul 2026 11:57:51 +0700 Subject: [PATCH] wireguardif: route decrypted RX through netif->input, not ip_input() The decrypted-RX path called ip_input() directly on the caller's task, entering the lwIP core concurrently with tcpip_thread. Under sustained TCP traffic through the tunnel this double-frees a sent-segment pbuf ('pbuf_free: p->ref > 0' assert) and reboots the device. ml_wg_mgr already sets netif->input = tcpip_input, i.e. the integration intends all input serialized through the tcpip mailbox; the direct ip_input() call bypassed that. Hand the pbuf to netif->input instead so the lwIP core is only entered from tcpip_thread. Verified on ESP32-S3 (ESP-IDF v5.3): multi-hundred-KB TCP relays that previously crashed mid-transfer now run to completion. Fixes #17 --- .../components/wireguard_lwip/src/wireguardif.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/components/microlink/components/wireguard_lwip/src/wireguardif.c b/components/microlink/components/wireguard_lwip/src/wireguardif.c index 6627fd0..d54f19d 100644 --- a/components/microlink/components/wireguard_lwip/src/wireguardif.c +++ b/components/microlink/components/wireguard_lwip/src/wireguardif.c @@ -539,9 +539,14 @@ static void wireguardif_process_data_message(struct wireguard_device *device, st if (dest_ok) { // Send packet to be processed by LWIP WG_DEBUG("[WG_RX_IP] Passing %u bytes to IP layer\n", (unsigned)pbuf->tot_len); - ip_input(pbuf, device->netif); - // pbuf is owned by IP layer now - pbuf = NULL; + // Enter the lwIP core ONLY from the tcpip_thread. netif->input is + // tcpip_input (set in ml_wg_mgr): posting the packet defers ip_input + // to the tcpip_thread. Calling ip_input() directly here runs on the + // wg_mgr task and races the tcpip_thread's WiFi TCP processing -> + // unsynchronized pbuf/PCB access -> double-free crash (pbuf.c:753). + if (device->netif->input(pbuf, device->netif) == ERR_OK) { + pbuf = NULL; // ownership handed to the tcpip_thread queue + } // else: still ours -> freed by pbuf_free() at the end of this block } else { WG_DEBUG("[WG_RX_IP] DROPPED: dest_ok=false\n"); }