Skip to content

Two NX_ASSERT invariants can be broken by a preempting thread #495

Open

Description

@fdesbiens

Summary

Two NX_ASSERTs in the IP layer state invariants that hold while the IP mutex is held, but some
code paths write or read the state without it. A higher-priority thread that preempts at the wrong
point fails the assert, and NX_ASSERT_FAIL sleeps forever, in one case while holding the IP mutex.
Both were found by reading the code; neither can be opened deterministically from a test, so neither
has been reproduced. Line numbers are on dev at 86aa8546.

1. The received-fragment queue under NX_FRAGMENT_IMMEDIATE_ASSEMBLY

_nx_ip_fragment_disable takes the IP mutex, disables interrupts, clears
nx_ip_fragment_assembly (nx_ip_fragment_disable.c:94) and asserts that the received-fragment queue
is empty (:103).

With immediate assembly every enqueue onto nx_ip_received_fragment_head is followed, in the same
call, by (ip_ptr -> nx_ip_fragment_assembly)(ip_ptr), which takes the whole queue:

  • nx_ipv4_packet_receive.c: TX_RESTORE at :480 then the call at :487 (NAT path), and :621
    then :628;
  • nx_ipv6_process_fragment_option.c: TX_RESTORE at :201 then the call at :208.

Between the TX_RESTORE and the call, the packet is on the queue and nothing holds the IP mutex
unless the caller does. A driver may call _nx_ip_packet_receive from a thread of its own. If that
thread is preempted at that point by a higher-priority thread calling nx_ip_fragment_disable, the
assert fails and that thread sleeps forever holding the IP mutex. If asserts are compiled out,
nx_ip_fragment_disable returns, the receiving thread resumes, and it calls the now-NULL
nx_ip_fragment_assembly.

2. The interface address list in _nxd_ipv6_interface_find

_nxd_ipv6_interface_find takes the head of a multicast group's interface address list and asserts
that it is a valid address (nxd_ipv6_interface_find.c:122).

_nx_icmpv6_DAD_failure marks the address invalid (nx_icmpv6_DAD_failure.c:87) and removes it
from the list afterwards (:105, :116). It runs on the IP thread. Several callers of
_nxd_ipv6_interface_find do not hold the IP mutex, for example nxd_udp_socket_send.c:393,
nx_icmp_ping6.c:92 and nx_icmpv6_send_rs.c:123. An application thread of higher priority than
the IP thread, readied inside that window, that sends to a multicast group joined on that interface,
fails the assert.

A related, deterministic case is fixed separately in #494: the application's address change
callback was called inside the same window, on the IP thread itself.

Suggested fixes

  1. In the receive paths, call the assembly while interrupts are still disabled, or check
    nx_ip_fragment_assembly again after TX_RESTORE; or have nx_ip_fragment_disable drain the
    queue instead of asserting that it is empty.
  2. In _nx_icmpv6_DAD_failure, remove the address from the list before marking it invalid, so a
    reader never sees an invalid address at the head.

Activity

  1. giovannicozzolongo commented on Oct 22, 2026

    @giovannicozzolongo

    I'd like to work on this. Could you assign it to me?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions