]> git.ipfire.org Git - thirdparty/kernel/linux.git/commit
net: core: neighbour: Make one netlink notification atomically
authorPetr Machata <petrm@nvidia.com>
Wed, 21 Jan 2026 16:43:41 +0000 (17:43 +0100)
committerJakub Kicinski <kuba@kernel.org>
Sun, 25 Jan 2026 22:57:37 +0000 (14:57 -0800)
commitd0887dc8b2d0b5b52b38b54474a22af7c294265b
tree530428fe2c5754f0c8b328268719d2af4c7b5aa0
parent795258891c944f37075ab8d3bc7415f5875e897a
net: core: neighbour: Make one netlink notification atomically

As noted in a previous patch, one race remains in the current code. A
kernel thread might interrupt a userspace thread after the change is done,
but before formatting and sending the message. Then what we would see is
two messages with the same contents:

    userspace thread        kernel thread
    ================        =============
    neigh_update
       write_lock_bh(n->lock)
       n->nud_state = STALE
       write_unlock_bh(n->lock)
 -------------------------->
      neigh:update
      write_lock_bh(n->lock)
      n->nud_state = REACHABLE
      write_unlock_bh(n->lock)
      neigh_notify
read_lock_bh(n->lock)
__neigh_fill_info
   ndm->nud_state = REACHABLE
rtnl_notify
read_unlock_bh(n->lock)
      RTNL REACHABLE sent
    <--------
       neigh_notify
 read_lock_bh(n->lock)
 __neigh_fill_info
   ndm->nud_state = REACHABLE
 rtnl_notify
 read_unlock_bh(n->lock)
       RTNL REACHABLE sent again

The solution is to send the netlink message inside the critical section
where the neighbor is changed, so that it reflects the notified-upon
neighbor state.

To that end, in __neigh_update(), move the current neigh_notify() call up
to said critical section, and convert it to __neigh_notify(), because the
lock is held. This motion crosses calls to neigh_update_managed_list(),
neigh_update_gc_list() and neigh_update_process_arp_queue(), all of which
potentially unlock and give an opportunity for the above race.

This also crosses a call to neigh_update_process_arp_queue() which calls
neigh->output(), which might be neigh_resolve_output() calls
neigh_event_send() calls neigh_event_send_probe() calls
__neigh_event_send() calls neigh_probe(), which touches neigh->probes,
an update which will now not be visible in the notification.

However, there is indication that there is no promise that these changes
will be accurately projected to notifications: fib6_table_lookup()
indirectly calls route.c's find_match() calls rt6_probe(), which looks up a
neighbor and call __neigh_set_probe_once(), which sets neigh->probes to 0,
but neither this nor the caller seems to send a notification.

Additionally, the neighbor object that the neigh_probe() mentioned above is
called on, might be the alternative neighbor looked up for the ARP queue
packet destination. If that is the case, the changed value of n1->probes is
not notified anywhere.

So at least in some circumstances, the reported number of probes needs to
be assumed to change without notification.

Signed-off-by: Petr Machata <petrm@nvidia.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/ceb44995498eb52375cb2d46c3245bdb9e74b355.1769012464.git.petrm@nvidia.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
net/core/neighbour.c