Skip to content

ESP32 ECMA-419 TCP: fix two lwIP/XS task races under connection churn#1656

Open
xymeow wants to merge 1 commit into
Moddable-OpenSource:publicfrom
xymeow:fix/esp32-tcp-lwip-races
Open

ESP32 ECMA-419 TCP: fix two lwIP/XS task races under connection churn#1656
xymeow wants to merge 1 commit into
Moddable-OpenSource:publicfrom
xymeow:fix/esp32-tcp-lwip-races

Conversation

@xymeow

@xymeow xymeow commented Jul 13, 2026

Copy link
Copy Markdown

Fixes #1655.

Two independent races between the lwIP tcpip task and the XS task produced LoadProhibited panics (rst:0xc) under ordinary HTTP connection churn on ESP32-S3:

  1. Socket teardown. removeTCPCallbacks() now clears the callback argument first (tcp_arg(pcb, NULL)) and then the callbacks, per @phoddie's suggestion in the issue — plain pointer stores, no marshaling. tcpReceive/tcpSent/tcpError bail out on a NULL argument (with tcpReceive freeing the pbuf), so late deliveries — including lwIP refused-data redelivery, which appears in one captured backtrace via tcp_process_refused_data — cannot touch a freed TCP record.

  2. Receive-buffer list. tcpReceive (lwIP task) walks to the tail of tcp->buffers to append while xs_tcp_read (XS task) pops and frees head nodes. The unsynchronized tail walk could traverse a node mid-free — this matches the two captured backtraces pointing at the walker->next walk. Both sides now do their pointer surgery inside builtinCriticalSection, with heap operations (c_free, pbuf_free, tcp_recved_safe) kept outside the critical section.

Verification: ESP32-S3 (M5Stack CoreS3), ESP-IDF 6.0, release build, serving HTTP to a LAN poller — the workload that previously crashed every 15–25 minutes (24 resets captured in one evening). With both fixes: hours of continuous clean uptime, instrumented via an abort hook so unrelated app-level restarts are excluded from the count. Soak details and symbolicated backtraces are in #1655.

CLA: the completed individual CLA has been emailed to info@moddable.com.

🤖 Generated with Claude Code

… churn

Fixes two use-after-free races behind the LoadProhibited panics reported
in Moddable-OpenSource#1655, both between the lwIP tcpip task and the XS task:

1. Socket teardown: removeTCPCallbacks now clears the callback argument
   (tcp_arg) before clearing the callbacks, and tcpReceive/tcpSent/
   tcpError bail out on a NULL argument, so a late delivery - including
   lwIP refused-data redelivery - cannot touch a freed TCP record.

2. Receive-buffer list: tcpReceive (lwIP task) walks to the tail of
   tcp->buffers to append while xs_tcp_read (XS task) pops and frees
   head nodes; the unsynchronized tail walk could traverse a node
   mid-free. Both sides now do their pointer surgery inside
   builtinCriticalSection, with heap operations kept outside.

Verified on ESP32-S3 (M5Stack CoreS3) under the reproducing workload -
an HTTP server polled by a LAN client, previously crashing every 15-25
minutes: hours of clean uptime with both fixes applied, instrumented to
exclude unrelated app-level restarts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ESP32: use-after-free panic in ECMA-419 TCP tcpReceive when a socket closes with data in flight (lwIP tcpip thread vs XS thread race)

1 participant