Re: [PATCH V3] netrom: Prevent race conditions between neighbor operations
From: Lizhi Xu <hidden>
Date: 2025-10-23 12:54:31
Also in:
linux-hams, lkml
On Thu, 23 Oct 2025 13:44:18 +0200, Paolo Abeni wrote:
quoted
The root cause of the problem is that multiple different tasks initiate SIOCADDRT & NETROM_NODE commands to add new routes, there is no lock between them to protect the same nr_neigh. Task0 can add the nr_neigh.refcount value of 1 on Task1 to routes[2]. When Task2 executes nr_neigh_put(nr_node->routes[2].neighbour), it will release the neighbour because its refcount value is 1. In this case, the following situation causes a UAF on Task2: Task0 Task1 Task2 ===== ===== ===== nr_add_node() nr_neigh_get_dev() nr_add_node() nr_node_lock() nr_node->routes[2].neighbour->count-- nr_neigh_put(nr_node->routes[2].neighbour); nr_remove_neigh(nr_node->routes[2].neighbour) nr_node_unlock() nr_node_lock() nr_node->routes[2].neighbour = nr_neigh nr_neigh_hold(nr_neigh); nr_add_node() nr_neigh_put() if (nr_node->routes[2].neighbour->count Description of the UAF triggering process: First, Task 0 executes nr_neigh_get_dev() to set neighbor refcount to 3. Then, Task 1 puts the same neighbor from its routes[2] and executes nr_remove_neigh() because the count is 0. After these two operations, the neighbor's refcount becomes 1. Then, Task 0 acquires the nr node lock and writes it to its routes[2].neighbour. Finally, Task 2 executes nr_neigh_put(nr_node->routes[2].neighbour) to release the neighbor. The subsequent execution of the neighbor->count check triggers a UAF. The solution to the problem is to use a lock to synchronize each add a route to node, but for rigor, I'll add locks to related ioctl and route frame operations to maintain synchronization.I think that adding another locking mechanism on top of an already complex and not well understood locking and reference infra is not the right direction. Why reordering the statements as: if (nr_node->routes[2].neighbour->count == 0 && !nr_node->routes[2].neighbour->locked) nr_remove_neigh(nr_node->routes[2].neighbour); nr_neigh_put(nr_node->routes[2].neighbour); is not enough?
This is not enough, the same uaf will appear, nr_remove_neigh() will also execute nr_neigh_put(), and then executing nr_neigh_put() again will trigger the uaf.
quoted
syzbot reported: BUG: KASAN: slab-use-after-free in nr_add_node+0x25db/0x2c00 net/netrom/nr_route.c:248 Read of size 4 at addr ffff888051e6e9b0 by task syz.1.2539/8741 Call Trace: <TASK> nr_add_node+0x25db/0x2c00 net/netrom/nr_route.c:248 Reported-by: syzbot+2860e75836a08b172755@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=2860e75836a08b172755 Signed-off-by: Lizhi Xu <redacted>quoted
--- V1 -> V2: update comments for cause uaf V2 -> V3: sync neighbor operations in ioctl and route frame, update comments net/netrom/nr_route.c | 4 ++++ 1 file changed, 4 insertions(+)diff --git a/net/netrom/nr_route.c b/net/netrom/nr_route.c index b94cb2ffbaf8..debe3e925338 100644 --- a/net/netrom/nr_route.c +++ b/net/netrom/nr_route.c@@ -40,6 +40,7 @@ static HLIST_HEAD(nr_node_list); static DEFINE_SPINLOCK(nr_node_list_lock); static HLIST_HEAD(nr_neigh_list); static DEFINE_SPINLOCK(nr_neigh_list_lock); +static DEFINE_MUTEX(neighbor_lock); static struct nr_node *nr_node_get(ax25_address *callsign) {@@ -633,6 +634,8 @@ int nr_rt_ioctl(unsigned int cmd, void __user *arg) ax25_digi digi; int ret; + guard(mutex)(&neighbor_lock);See: https://elixir.bootlin.com/linux/v6.18-rc1/source/Documentation/process/maintainer-netdev.rst#L395
Using guard is not recommended. I'll reconsider. BR, Lizhi