]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
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)
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

index 187615bcc1c3923ad860015b0f07158cb65021d4..1d7489f50b21bd1ccb7090c0c143828a79ad5c3e 100644 (file)
@@ -52,6 +52,7 @@ do {                                          \
 
 static void neigh_timer_handler(struct timer_list *t);
 static void neigh_notify(struct neighbour *n, int type, int flags, u32 pid);
+static void __neigh_notify(struct neighbour *n, int type, int flags, u32 pid);
 static void pneigh_ifdown(struct neigh_table *tbl, struct net_device *dev,
                          bool skip_perm);
 
@@ -1512,6 +1513,9 @@ out:
        if (update_isrouter)
                neigh_update_is_router(neigh, flags, &notify);
 
+       if (notify)
+               __neigh_notify(neigh, RTM_NEWNEIGH, 0, nlmsg_pid);
+
        if (process_arp_queue)
                neigh_update_process_arp_queue(neigh);
 
@@ -1522,10 +1526,8 @@ out:
        if (managed_update)
                neigh_update_managed_list(neigh);
 
-       if (notify) {
-               neigh_notify(neigh, RTM_NEWNEIGH, 0, nlmsg_pid);
+       if (notify)
                call_netevent_notifiers(NETEVENT_NEIGH_UPDATE, neigh);
-       }
 
        trace_neigh_update_done(neigh, err);
        return err;