pico_cyw43_arch_lwip_sys_freertos: LWIP_PROVIDE_ERRNO=1 conflicts with newlib's thread-local errno #2973

Open
opened 2026-05-27 21:02:45 -05:00 by skycmoon · 1 comment
skycmoon commented 2026-05-27 21:02:45 -05:00 (Migrated from github.com)

Summary

src/rp2_common/pico_cyw43_arch/CMakeLists.txt:70 unconditionally sets LWIP_PROVIDE_ERRNO=1 on the pico_cyw43_arch_lwip_sys_freertos variant. Under FreeRTOS+newlib (the only libc bundled with the SDK's FreeRTOS port), this causes a silent storage divergence: lwIP writes errno values to its own extern int errno global, while any POSIX-style code reading errno via <errno.h> hits newlib's thread-local (*__errno()) macro. The two are different memory locations.

The bug is silent in code that uses lwIP's own socket API (which reads lwIP's global directly), but fatal for code that follows the standard mbedTLS BIO pattern — i.e. mbedTLS's stock library/net_sockets.c, or any port of it. That code does non-blocking recv(), reads errno, and translates EAGAIN/EWOULDBLOCK into MBEDTLS_ERR_SSL_WANT_READ so the TLS state machine retries. With the storage mismatch, the shim reads 0 from newlib's untouched thread-local and treats every empty poll as a hard error → session tear-down.

Affected configuration

  • pico_cyw43_arch_lwip_sys_freertos (Pico W / Pico 2W under FreeRTOS)
  • lwIP + mbedTLS + any TCP-bytestream client that uses the stock mbedTLS BIO (or an equivalent shim reading errno via <errno.h>)
  • The bug surfaces at the first non-blocking recv() after the TLS handshake completes — typical observable: TLS handshake succeeds, application sends e.g. MQTT CONNECT, server queues CONNACK, client immediately FINs the connection without reading the response.

Root cause

pico_cyw43_arch/CMakeLists.txt:

target_compile_definitions(pico_cyw43_arch_lwip_sys_freertos_headers INTERFACE
        CYW43_LWIP=1
        LWIP_PROVIDE_ERRNO=1   # <-- declares lwIP's own global errno
        ...
)

LWIP_PROVIDE_ERRNO=1 makes lwip/errno.h declare extern int errno; and define the E* constants itself. That's the correct choice for bare-metal builds where no libc owns errno. For FreeRTOS+newlib, newlib already provides a thread-local errno (with all the same E* constants), and lwIP's global silently diverges from it.

Fix proposal

Switch the FreeRTOS+lwIP variant to LWIP_ERRNO_STDINCLUDE=1, which keeps lwIP's errno-constants behaviour but routes through <errno.h> — so lwIP and the rest of the program share newlib's thread-local storage:

 target_compile_definitions(pico_cyw43_arch_lwip_sys_freertos_headers INTERFACE
         CYW43_LWIP=1
-        LWIP_PROVIDE_ERRNO=1
+        LWIP_ERRNO_STDINCLUDE=1
         ...
)

This is behaviour-preserving for users who don't touch errno directly (lwIP's E* constants still resolve, just through <errno.h>). It is correcting for users on the stock mbedTLS BIO pattern.

The threadsafe_background (bare-metal) variant intentionally does not set LWIP_PROVIDE_ERRNO=1 in the current code, so no change there is needed.

Workarounds available today

A user can override the SDK's -D by defining LWIP_ERRNO_STDINCLUDE and #undef LWIP_PROVIDE_ERRNO in their own lwipopts.h. This works but the symptom is hard to diagnose without first understanding the storage divergence — the visible failure mode (TLS connection that handshakes successfully then dies on first recv()) leads most developers to look at TLS config, mbedTLS flags, cert chains, etc. first.

PR

PR with the proposed change against develop: https://github.com/raspberrypi/pico-sdk/pull/2974

## Summary `src/rp2_common/pico_cyw43_arch/CMakeLists.txt:70` unconditionally sets `LWIP_PROVIDE_ERRNO=1` on the `pico_cyw43_arch_lwip_sys_freertos` variant. Under FreeRTOS+newlib (the only libc bundled with the SDK's FreeRTOS port), this causes a silent storage divergence: lwIP writes errno values to its own `extern int errno` global, while any POSIX-style code reading `errno` via `<errno.h>` hits newlib's thread-local `(*__errno())` macro. The two are different memory locations. The bug is silent in code that uses lwIP's own socket API (which reads lwIP's global directly), but **fatal for code that follows the standard mbedTLS BIO pattern** — i.e. mbedTLS's stock `library/net_sockets.c`, or any port of it. That code does non-blocking `recv()`, reads `errno`, and translates `EAGAIN`/`EWOULDBLOCK` into `MBEDTLS_ERR_SSL_WANT_READ` so the TLS state machine retries. With the storage mismatch, the shim reads `0` from newlib's untouched thread-local and treats every empty poll as a hard error → session tear-down. ## Affected configuration - `pico_cyw43_arch_lwip_sys_freertos` (Pico W / Pico 2W under FreeRTOS) - lwIP + mbedTLS + any TCP-bytestream client that uses the stock mbedTLS BIO (or an equivalent shim reading errno via `<errno.h>`) - The bug surfaces at the first non-blocking `recv()` after the TLS handshake completes — typical observable: TLS handshake succeeds, application sends e.g. MQTT `CONNECT`, server queues `CONNACK`, client immediately FINs the connection without reading the response. ## Root cause `pico_cyw43_arch/CMakeLists.txt`: ```cmake target_compile_definitions(pico_cyw43_arch_lwip_sys_freertos_headers INTERFACE CYW43_LWIP=1 LWIP_PROVIDE_ERRNO=1 # <-- declares lwIP's own global errno ... ) ``` `LWIP_PROVIDE_ERRNO=1` makes `lwip/errno.h` declare `extern int errno;` and define the `E*` constants itself. That's the correct choice for **bare-metal** builds where no libc owns `errno`. For FreeRTOS+newlib, newlib already provides a thread-local errno (with all the same `E*` constants), and lwIP's global silently diverges from it. ## Fix proposal Switch the FreeRTOS+lwIP variant to `LWIP_ERRNO_STDINCLUDE=1`, which keeps lwIP's errno-constants behaviour but routes through `<errno.h>` — so lwIP and the rest of the program share newlib's thread-local storage: ```diff target_compile_definitions(pico_cyw43_arch_lwip_sys_freertos_headers INTERFACE CYW43_LWIP=1 - LWIP_PROVIDE_ERRNO=1 + LWIP_ERRNO_STDINCLUDE=1 ... ) ``` This is behaviour-preserving for users who don't touch `errno` directly (lwIP's `E*` constants still resolve, just through `<errno.h>`). It is **correcting** for users on the stock mbedTLS BIO pattern. The `threadsafe_background` (bare-metal) variant intentionally does **not** set `LWIP_PROVIDE_ERRNO=1` in the current code, so no change there is needed. ## Workarounds available today A user can override the SDK's `-D` by defining `LWIP_ERRNO_STDINCLUDE` and `#undef LWIP_PROVIDE_ERRNO` in their own `lwipopts.h`. This works but the symptom is hard to diagnose without first understanding the storage divergence — the visible failure mode (TLS connection that handshakes successfully then dies on first `recv()`) leads most developers to look at TLS config, mbedTLS flags, cert chains, etc. first. ## PR PR with the proposed change against `develop`: https://github.com/raspberrypi/pico-sdk/pull/2974
peterharperuk commented 2026-06-11 05:40:14 -05:00 (Migrated from github.com)

Thanks for the explanation and analysis!

Thanks for the explanation and analysis!
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-raspberrypi/pico-sdk#2973
No description provided.