[PATCH net-next] ipv6: Serialize hardware flag notifications
Yuyang Huang <[email protected]>
| Newsgroups | org.kernel.vger.netdev,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
fib6_info_hw_flags_set() first performs a lockless check of fib6_node.
It then allocates a notification skb with GFP_KERNEL, which can sleep. A
concurrent route deletion can remove the route, set fib6_node to NULL, and
emit RTM_DELROUTE while the allocation sleeps. When the thread wakes up,
it can emit RTM_NEWROUTE for the already deleted route.
This can cause userspace routing daemons to receive RTM_DELROUTE followed
by RTM_NEWROUTE and incorrectly believe that the deleted route still
exists in the kernel.
Allocate the skb before taking tb6_lock, then recheck fib6_node while
holding the lock. Keep the lock until RTM_NEWROUTE is published. If route
deletion wins the race, the recheck sees NULL and drops the notification.
Otherwise, deletion cannot remove the route until RTM_NEWROUTE has been
published, preserving notification order.
RTM_DELROUTE is sent by fib6_del_route() with tb6_lock held, so
publishing RTM_NEWROUTE under the same lock is sufficient to guarantee
ordering. rt6_fill_node() does not sleep in this path, and the
notification uses GFP_ATOMIC, matching inet6_rt_notify() which already
broadcasts under tb6_lock.
The race was found by Sashiko during code review.
Fixes: 907eea486888 ("net: ipv6: Emit notification when fib hardware flags are changed")
Signed-off-by: Yuyang Huang <[email protected]>
---
net/ipv6/route.c | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 16dfac54a259..73db4f630dcc 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -6463,6 +6463,7 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i,
bool offload, bool trap, bool offload_failed)
{
u8 fib_notify_on_flag_change;
+ struct fib6_table *table;
struct sk_buff *skb;
int err;
@@ -6491,22 +6492,33 @@ void fib6_info_hw_flags_set(struct net *net, struct fib6_info *f6i,
if (!fib_notify_on_flag_change)
return;
+ table = f6i->fib6_table;
skb = nlmsg_new(rt6_nlmsg_size(f6i), GFP_KERNEL);
if (!skb) {
err = -ENOBUFS;
goto errout;
}
+ spin_lock_bh(&table->tb6_lock);
+ if (!rcu_dereference_protected(f6i->fib6_node,
+ lockdep_is_held(&table->tb6_lock))) {
+ spin_unlock_bh(&table->tb6_lock);
+ kfree_skb(skb);
+ return;
+ }
+
err = rt6_fill_node(net, skb, f6i, NULL, NULL, NULL, 0, RTM_NEWROUTE, 0,
0, 0, RT_DEL_REASON_UNSPEC);
if (err < 0) {
/* -EMSGSIZE implies BUG in rt6_nlmsg_size() */
WARN_ON(err == -EMSGSIZE);
+ spin_unlock_bh(&table->tb6_lock);
kfree_skb(skb);
goto errout;
}
- rtnl_notify(skb, net, 0, RTNLGRP_IPV6_ROUTE, NULL, GFP_KERNEL);
+ rtnl_notify(skb, net, 0, RTNLGRP_IPV6_ROUTE, NULL, GFP_ATOMIC);
+ spin_unlock_bh(&table->tb6_lock);
return;
errout:
--
2.43.0