6.18-stable review patch. If anyone has any objections, please let me know. ------------------ From: Bernard Pidoux nr_route_frame() swaps nr_neigh->ax25 for the ax25_cb returned by ax25_send_frame() and puts the old one while holding nr_route_lock. nr_link_failed(), called by AX.25 when that link fails, looks the neighbour up under the lock but then clears nr_neigh->ax25, puts the ax25_cb and bumps nr_neigh->failed after dropping it. If a frame is routed to the same neighbour at that moment, both paths can see the same ax25_cb and put it twice. Do the whole of nr_link_failed() in one critical section. The neighbour no longer needs a temporary reference, since it cannot be unlinked while the lock is held. This depends on "netrom: use a single lock for the node and neighbour lists". Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Signed-off-by: Bernard Pidoux (cherry picked from commit 6e6f80bff5c901f317c3750cdf375766800ecc1a) Signed-off-by: Sasha Levin --- net/netrom/nr_route.c | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/net/netrom/nr_route.c b/net/netrom/nr_route.c index 67d9ed0006b40..98ba317ada637 100644 --- a/net/netrom/nr_route.c +++ b/net/netrom/nr_route.c @@ -715,34 +715,35 @@ void nr_link_failed(ax25_cb *ax25, int reason) struct nr_neigh *s, *nr_neigh = NULL; struct nr_node *nr_node = NULL; + /* + * nr_route_frame() replaces nr_neigh->ax25 under nr_route_lock; + * clear it and drop its reference under the same lock, so that the + * two paths cannot both put the same ax25_cb. + */ spin_lock_bh(&nr_route_lock); nr_neigh_for_each(s, &nr_neigh_list) { if (s->ax25 == ax25) { - nr_neigh_hold(s); nr_neigh = s; break; } } - spin_unlock_bh(&nr_route_lock); if (nr_neigh == NULL) - return; + goto out; nr_neigh->ax25 = NULL; ax25_cb_put(ax25); - if (++nr_neigh->failed < READ_ONCE(sysctl_netrom_link_fails_count)) { - nr_neigh_put(nr_neigh); - return; - } - spin_lock_bh(&nr_route_lock); + if (++nr_neigh->failed < READ_ONCE(sysctl_netrom_link_fails_count)) + goto out; + nr_node_for_each(nr_node, &nr_node_list) { if (nr_node->which < nr_node->count && nr_node->routes[nr_node->which].neighbour == nr_neigh) nr_node->which++; } +out: spin_unlock_bh(&nr_route_lock); - nr_neigh_put(nr_neigh); } /* -- 2.53.0