Skip to content

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

@skycmoon

Description

@skycmoon

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: #2974

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions