From: Nikolay Aleksandrov <razor@blackwall.org> Date: 2021-11-22 15:15:22
From: Nikolay Aleksandrov <redacted>
Hi,
This set fixes a refcount bug when replacing nexthop groups and
modifying routes. It is complex because the objects look valid when
debugging memory dumps, but we end up having refcount dependency between
unlinked objects which can never be released, so in turn they cannot
free their resources and refcounts. The problem happens because we can
have stale IPv6 per-cpu dsts in nexthops which were removed from a
group. Even though the IPv6 gen is bumped, the dsts won't be released
until traffic passes through them or the nexthop is freed, that can take
arbitrarily long time, and even worse we can create a scenario[1] where it
can never be released. The fix is to release the IPv6 per-cpu dsts of
replaced nexthops after an RCU grace period so no new ones can be
created. To do that we add a new IPv6 stub - fib6_nh_release_dsts, which
is used by the nexthop code only when necessary. We can further optimize
group replacement, but that is more suited for net-next as these patches
would have to be backported to stable releases.
v2: patch 02: update commit msg
patch 03: check for mausezahn before testing and make a few comments
more verbose
Thanks,
Nik
[1]
This info is also present in patch 02's commit message.
Initial state:
$ ip nexthop list
id 200 via 2002:db8::2 dev bridge.10 scope link onlink
id 201 via 2002:db8::3 dev bridge scope link onlink
id 203 group 201/200
$ ip -6 route
2001:db8::10 nhid 203 metric 1024 pref medium
nexthop via 2002:db8::3 dev bridge weight 1 onlink
nexthop via 2002:db8::2 dev bridge.10 weight 1 onlink
Create rt6_info through one of the multipath legs, e.g.:
$ taskset -a -c 1 ./pkt_inj 24 bridge.10 2001:db8::10
(pkt_inj is just a custom packet generator, nothing special)
Then remove that leg from the group by replace (let's assume it is id
200 in this case):
$ ip nexthop replace id 203 group 201
Now remove the IPv6 route:
$ ip -6 route del 2001:db8::10/128
The route won't be really deleted due to the stale rt6_info holding 1
refcnt in nexthop id 200.
At this point we have the following reference count dependency:
(deleted) IPv6 route holds 1 reference over nhid 203
nh 203 holds 1 ref over id 201
nh 200 holds 1 ref over the net device and the route due to the stale
rt6_info
Now to create circular dependency between nh 200 and the IPv6 route, and
also to get a reference over nh 200, restore nhid 200 in the group:
$ ip nexthop replace id 203 group 201/200
And now we have a permanent circular dependncy because nhid 203 holds a
reference over nh 200 and 201, but the route holds a ref over nh 203 and
is deleted.
To trigger the bug just delete the group (nhid 203):
$ ip nexthop del id 203
It won't really be deleted due to the IPv6 route dependency, and now we
have 2 unlinked and deleted objects that reference each other: the group
and the IPv6 route. Since the group drops the reference it holds over its
entries at free time (i.e. its own refcount needs to drop to 0) that will
never happen and we get a permanent ref on them, since one of the entries
holds a reference over the IPv6 route it will also never be released.
At this point the dependencies are:
(deleted, only unlinked) IPv6 route holds reference over group nh 203
(deleted, only unlinked) group nh 203 holds reference over nh 201 and 200
nh 200 holds 1 ref over the net device and the route due to the stale
rt6_info
This is the last point where it can be fixed by running traffic through
nh 200, and specifically through the same CPU so the rt6_info (dst) will
get released due to the IPv6 genid, that in turn will free the IPv6
route, which in turn will free the ref count over the group nh 203.
If nh 200 is deleted at this point, it will never be released due to the
ref from the unlinked group 203, it will only be unlinked:
$ ip nexthop del id 200
$ ip nexthop
$
Now we can never release that stale rt6_info, we have IPv6 route with ref
over group nh 203, group nh 203 with ref over nh 200 and 201, nh 200 with
rt6_info (dst) with ref over the net device and the IPv6 route. All of
these objects are only unlinked, and cannot be released, thus they can't
release their ref counts.
Message from syslogd@dev at Nov 19 14:04:10 ...
kernel:[73501.828730] unregister_netdevice: waiting for bridge.10 to become free. Usage count = 3
Message from syslogd@dev at Nov 19 14:04:20 ...
kernel:[73512.068811] unregister_netdevice: waiting for bridge.10 to become free. Usage count = 3
Nikolay Aleksandrov (3):
net: ipv6: add fib6_nh_release_dsts stub
net: nexthop: release IPv6 per-cpu dsts when replacing a nexthop group
selftests: net: fib_nexthops: add test for group refcount imbalance
bug
include/net/ip6_fib.h | 1 +
include/net/ipv6_stubs.h | 1 +
net/ipv4/nexthop.c | 25 +++++++-
net/ipv6/af_inet6.c | 1 +
net/ipv6/route.c | 19 +++++++
tools/testing/selftests/net/fib_nexthops.sh | 63 +++++++++++++++++++++
6 files changed, 108 insertions(+), 2 deletions(-)
--
2.31.1
From: Nikolay Aleksandrov <razor@blackwall.org> Date: 2021-11-22 15:15:25
From: Nikolay Aleksandrov <redacted>
We need a way to release a fib6_nh's per-cpu dsts when replacing
nexthops otherwise we can end up with stale per-cpu dsts which hold net
device references, so add a new IPv6 stub called fib6_nh_release_dsts.
It must be used after an RCU grace period, so no new dsts can be created
through a group's nexthop entry.
Similar to fib6_nh_release it shouldn't be used if fib6_nh_init has failed
so it doesn't need a dummy stub when IPv6 is not enabled.
Fixes: 7bf4796dd099 ("nexthops: add support for replace")
Signed-off-by: Nikolay Aleksandrov <redacted>
---
v2: no changes
include/net/ip6_fib.h | 1 +
include/net/ipv6_stubs.h | 1 +
net/ipv6/af_inet6.c | 1 +
net/ipv6/route.c | 19 +++++++++++++++++++
4 files changed, 22 insertions(+)
From: Nikolay Aleksandrov <razor@blackwall.org> Date: 2021-11-22 15:15:27
From: Nikolay Aleksandrov <redacted>
The new selftest runs a sequence which causes circular refcount
dependency between deleted objects which cannot be released and results
in a netdevice refcount imbalance.
Signed-off-by: Nikolay Aleksandrov <redacted>
---
v2: check for mausezahn before testing and make a few comments
more verbose
tools/testing/selftests/net/fib_nexthops.sh | 63 +++++++++++++++++++++
1 file changed, 63 insertions(+)
@@ -629,6 +629,66 @@ ipv6_fcnal()log_test$?0"Nexthops removed on admin down"}+ipv6_grp_refs()+{+if[!-x"$(command-vmausezahn)"];then+echo"SKIP: Could not run test; need mausezahn tool"+return+fi++run_cmd"$IP link set dev veth1 up"+run_cmd"$IP link add veth1.10 link veth1 up type vlan id 10"+run_cmd"$IP link add veth1.20 link veth1 up type vlan id 20"+run_cmd"$IP -6 addr add 2001:db8:91::1/64 dev veth1.10"+run_cmd"$IP -6 addr add 2001:db8:92::1/64 dev veth1.20"+run_cmd"$IP -6 neigh add 2001:db8:91::2 lladdr 00:11:22:33:44:55 dev veth1.10"+run_cmd"$IP -6 neigh add 2001:db8:92::2 lladdr 00:11:22:33:44:55 dev veth1.20"+run_cmd"$IP nexthop add id 100 via 2001:db8:91::2 dev veth1.10"+run_cmd"$IP nexthop add id 101 via 2001:db8:92::2 dev veth1.20"+run_cmd"$IP nexthop add id 102 group 100"+run_cmd"$IP route add 2001:db8:101::1/128 nhid 102"++# create per-cpu dsts through nh 100+run_cmd"ip netns exec me mausezahn -6 veth1.10 -B 2001:db8:101::1 -A 2001:db8:91::1 -c 5 -t tcp "dp=1-1023,flags=syn" >/dev/null 2>&1"++# remove nh 100 from the group to delete the route potentially leaving+# a stale per-cpu dst which holds a reference to the nexthop's net+# device and to the IPv6 route+run_cmd"$IP nexthop replace id 102 group 101"+run_cmd"$IP route del 2001:db8:101::1/128"++# add both nexthops to the group so a reference is taken on them+run_cmd"$IP nexthop replace id 102 group 100/101"++# if the bug described in commit "net: nexthop: release IPv6 per-cpu+# dsts when replacing a nexthop group" exists at this point we have+# an unlinked IPv6 route (but not freed due to stale dst) with a+# reference over the group so we delete the group which will again+# only unlink it due to the route reference+run_cmd"$IP nexthop del id 102"++# delete the nexthop with stale dst, since we have an unlinked+# group with a ref to it and an unlinked IPv6 route with ref to the+# group, the nh will only be unlinked and not freed so the stale dst+# remains forever and we get a net device refcount imbalance+run_cmd"$IP nexthop del id 100"++# if a reference was lost this command will hang because the net device+# cannot be removed+timeout-sKILL5ipnetnsexecmeiplinkdelveth1.10>/dev/null2>&1++# we can't cleanup if the command is hung trying to delete the netdev+if[$?-eq137];then+return1+fi++# cleanup+run_cmd"$IP link del veth1.20"+run_cmd"$IP nexthop flush"++return0+}+ ipv6_grp_fcnal(){localrc
@@ -734,6 +794,9 @@ ipv6_grp_fcnal()run_cmd"$IP nexthop add id 108 group 31/24"log_test$?2"Nexthop group can not have a blackhole and another nexthop"++ipv6_grp_refs+log_test$?0"Nexthop group replace refcounts"} ipv6_res_grp_fcnal()
From: Nikolay Aleksandrov <razor@blackwall.org> Date: 2021-11-22 15:15:27
From: Nikolay Aleksandrov <redacted>
When replacing a nexthop group, we must release the IPv6 per-cpu dsts of
the removed nexthop entries after an RCU grace period because they
contain references to the nexthop's net device and to the fib6 info.
With specific series of events[1] we can reach net device refcount
imbalance which is unrecoverable. IPv4 is not affected because dsts
don't take a refcount on the route.
[1]
$ ip nexthop list
id 200 via 2002:db8::2 dev bridge.10 scope link onlink
id 201 via 2002:db8::3 dev bridge scope link onlink
id 203 group 201/200
$ ip -6 route
2001:db8::10 nhid 203 metric 1024 pref medium
nexthop via 2002:db8::3 dev bridge weight 1 onlink
nexthop via 2002:db8::2 dev bridge.10 weight 1 onlink
Create rt6_info through one of the multipath legs, e.g.:
$ taskset -a -c 1 ./pkt_inj 24 bridge.10 2001:db8::10
(pkt_inj is just a custom packet generator, nothing special)
Then remove that leg from the group by replace (let's assume it is id
200 in this case):
$ ip nexthop replace id 203 group 201
Now remove the IPv6 route:
$ ip -6 route del 2001:db8::10/128
The route won't be really deleted due to the stale rt6_info holding 1
refcnt in nexthop id 200.
At this point we have the following reference count dependency:
(deleted) IPv6 route holds 1 reference over nhid 203
nh 203 holds 1 ref over id 201
nh 200 holds 1 ref over the net device and the route due to the stale
rt6_info
Now to create circular dependency between nh 200 and the IPv6 route, and
also to get a reference over nh 200, restore nhid 200 in the group:
$ ip nexthop replace id 203 group 201/200
And now we have a permanent circular dependncy because nhid 203 holds a
reference over nh 200 and 201, but the route holds a ref over nh 203 and
is deleted.
To trigger the bug just delete the group (nhid 203):
$ ip nexthop del id 203
It won't really be deleted due to the IPv6 route dependency, and now we
have 2 unlinked and deleted objects that reference each other: the group
and the IPv6 route. Since the group drops the reference it holds over its
entries at free time (i.e. its own refcount needs to drop to 0) that will
never happen and we get a permanent ref on them, since one of the entries
holds a reference over the IPv6 route it will also never be released.
At this point the dependencies are:
(deleted, only unlinked) IPv6 route holds reference over group nh 203
(deleted, only unlinked) group nh 203 holds reference over nh 201 and 200
nh 200 holds 1 ref over the net device and the route due to the stale
rt6_info
This is the last point where it can be fixed by running traffic through
nh 200, and specifically through the same CPU so the rt6_info (dst) will
get released due to the IPv6 genid, that in turn will free the IPv6
route, which in turn will free the ref count over the group nh 203.
If nh 200 is deleted at this point, it will never be released due to the
ref from the unlinked group 203, it will only be unlinked:
$ ip nexthop del id 200
$ ip nexthop
$
Now we can never release that stale rt6_info, we have IPv6 route with ref
over group nh 203, group nh 203 with ref over nh 200 and 201, nh 200 with
rt6_info (dst) with ref over the net device and the IPv6 route. All of
these objects are only unlinked, and cannot be released, thus they can't
release their ref counts.
Message from syslogd@dev at Nov 19 14:04:10 ...
kernel:[73501.828730] unregister_netdevice: waiting for bridge.10 to become free. Usage count = 3
Message from syslogd@dev at Nov 19 14:04:20 ...
kernel:[73512.068811] unregister_netdevice: waiting for bridge.10 to become free. Usage count = 3
Fixes: 7bf4796dd099 ("nexthops: add support for replace")
Signed-off-by: Nikolay Aleksandrov <redacted>
---
v2: added information about why IPv4 is not affected to the
commit msg, no changes to the patch
net/ipv4/nexthop.c | 25 +++++++++++++++++++++++--
1 file changed, 23 insertions(+), 2 deletions(-)
@@ -1899,15 +1899,36 @@ static void remove_nexthop(struct net *net, struct nexthop *nh,/* if any FIB entries reference this nexthop, any dst entries*needtoberegenerated*/-staticvoidnh_rt_cache_flush(structnet*net,structnexthop*nh)+staticvoidnh_rt_cache_flush(structnet*net,structnexthop*nh,+structnexthop*replaced_nh){structfib6_info*f6i;+structnh_group*nhg;+inti;if(!list_empty(&nh->fi_list))rt_cache_flush(net);list_for_each_entry(f6i,&nh->f6i_list,nh_list)ipv6_stub->fib6_update_sernum(net,f6i);++/* if an IPv6 group was replaced, we have to release all old+*dststomakesureallrefcountsarereleased+*/+if(!replaced_nh->is_group)+return;++/* new dsts must use only the new nexthop group */+synchronize_net();++nhg=rtnl_dereference(replaced_nh->nh_grp);+for(i=0;i<nhg->num_nh;i++){+structnh_grp_entry*nhge=&nhg->nh_entries[i];+structnh_info*nhi=rtnl_dereference(nhge->nh->nh_info);++if(nhi->family==AF_INET6)+ipv6_stub->fib6_nh_release_dsts(&nhi->fib6_nh);+}}staticintreplace_nexthop_grp(structnet*net,structnexthop*old,
@@ -2247,7 +2268,7 @@ static int replace_nexthop(struct net *net, struct nexthop *old,err=replace_nexthop_single(net,old,new,extack);if(!err){-nh_rt_cache_flush(net,old);+nh_rt_cache_flush(net,old,new);__remove_nexthop(net,new,NULL);nexthop_put(new);
Hello:
This series was applied to netdev/net.git (master)
by David S. Miller [off-list ref]:
On Mon, 22 Nov 2021 17:15:11 +0200 you wrote:
From: Nikolay Aleksandrov <redacted>
Hi,
This set fixes a refcount bug when replacing nexthop groups and
modifying routes. It is complex because the objects look valid when
debugging memory dumps, but we end up having refcount dependency between
unlinked objects which can never be released, so in turn they cannot
free their resources and refcounts. The problem happens because we can
have stale IPv6 per-cpu dsts in nexthops which were removed from a
group. Even though the IPv6 gen is bumped, the dsts won't be released
until traffic passes through them or the nexthop is freed, that can take
arbitrarily long time, and even worse we can create a scenario[1] where it
can never be released. The fix is to release the IPv6 per-cpu dsts of
replaced nexthops after an RCU grace period so no new ones can be
created. To do that we add a new IPv6 stub - fib6_nh_release_dsts, which
is used by the nexthop code only when necessary. We can further optimize
group replacement, but that is more suited for net-next as these patches
would have to be backported to stable releases.
[...]
this duplicates fib6_nh_release. Can you send a follow on to have it use
this new function?
It duplicates a part of it but in a safe way because the fib6_nh could still be visible,
while fib6_nh_release does it in a way that assumes it's not. I could re-use
this helper in fib6_nh_release though, since it doesn't matter how the entries are
freed there. I'm guessing that is what you meant?
I'll take care of that and of the few possible optimizations for nexthop in net-next.
Thanks,
Nik
From: David Ahern <hidden> Date: 2021-11-28 21:37:53
On 11/28/21 1:14 PM, Nikolay Aleksandrov wrote:
It duplicates a part of it but in a safe way because the fib6_nh could still be visible,
while fib6_nh_release does it in a way that assumes it's not. I could re-use
this helper in fib6_nh_release though, since it doesn't matter how the entries are
freed there. I'm guessing that is what you meant?
yes, that is what I meant - it duplicates a chunk of the code in
fib6_nh_release.
I'll take care of that and of the few possible optimizations for nexthop in net-next.