inet6_dump_fib() saves its progress in cb->args[1] as a positional
index within the current hash chain. Between batches, a concurrent
fib6_new_table() can insert a new table at the chain head, shifting
all existing entries. The saved index then lands on a different
table, causing fib6_dump_table() to set w->root to the wrong table
while w->node still points into the previous one.
fib6_walk_continue() dereferences w->node->parent (NULL) and panics:
BUG: kernel NULL pointer dereference, address: 0000000000000008
RIP: 0010:fib6_walk_continue+0x6e/0x170
Call Trace:
<TASK>
fib6_dump_table.isra.0+0xc5/0x240
inet6_dump_fib+0xf6/0x420
rtnl_dumpit+0x30/0xa0
netlink_dump+0x15b/0x460
netlink_recvmsg+0x1d6/0x2a0
____sys_recvmsg+0x17a/0x190
Fix by storing tb->tb6_id in cb->args[1] instead of a positional
index. On resume, skip entries until the id matches; a concurrent
head-insert can never match the saved id, so the walker always
resumes on the correct table.
Fixes: 1b43af5480c3 ("[IPV6]: Increase number of possible routing tables to 2^32")
Signed-off-by: Pengfei Zhang <zhangfeionline@gmail.com>
---
v3:
- Fix Author/SOB email mismatch (use gmail for both)
- Drop "RTNL lock is released" from commit message (RTNL removed from IPv6 FIB)
- Reorder local variables to follow reverse xmas tree
- Move blank line after continue for readability
v2:
- Add Fixes tag
v2: https://lore.kernel.org/netdev/20260625044101.939070-1-zhangfeionline@gmail.com/
v1: https://lore.kernel.org/netdev/20260624171156.822055-1-zhangfeionline@gmail.com/
net/ipv6/ip6_fib.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
diff --git a/net/ipv6/ip6_fib.c b/net/ipv6/ip6_fib.c
index fc95738de..a130cdfae 100644
--- a/net/ipv6/ip6_fib.c
+++ b/net/ipv6/ip6_fib.c
@@ -636,12 +636,12 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
};
const struct nlmsghdr *nlh = cb->nlh;
struct net *net = sock_net(skb->sk);
- unsigned int e = 0, s_e;
struct hlist_head *head;
struct fib6_walker *w;
struct fib6_table *tb;
unsigned int h, s_h;
int err = 0;
+ u32 s_id;
rcu_read_lock();
if (cb->strict_check) {
@@ -701,23 +701,22 @@ static int inet6_dump_fib(struct sk_buff *skb, struct netlink_callback *cb)
}
s_h = cb->args[0];
- s_e = cb->args[1];
+ s_id = cb->args[1];
- for (h = s_h; h < FIB6_TABLE_HASHSZ; h++, s_e = 0) {
- e = 0;
+ for (h = s_h; h < FIB6_TABLE_HASHSZ; h++, s_id = 0) {
head = &net->ipv6.fib_table_hash[h];
hlist_for_each_entry_rcu(tb, head, tb6_hlist) {
- if (e < s_e)
- goto next;
+ if (s_id && tb->tb6_id != s_id)
+ continue;
+
+ s_id = 0;
+ cb->args[1] = tb->tb6_id;
err = fib6_dump_table(tb, skb, cb);
if (err != 0)
goto out;
-next:
- e++;
}
}
out:
- cb->args[1] = e;
cb->args[0] = h;
unlock:
--
2.34.1
On Thu, Jun 25, 2026 at 03:05:17PM +0800, Pengfei Zhang wrote:
> inet6_dump_fib() saves its progress in cb->args[1] as a positional
> index within the current hash chain. Between batches, a concurrent
> fib6_new_table() can insert a new table at the chain head, shifting
> all existing entries. The saved index then lands on a different
> table, causing fib6_dump_table() to set w->root to the wrong table
> while w->node still points into the previous one.
> fib6_walk_continue() dereferences w->node->parent (NULL) and panics:
>
> BUG: kernel NULL pointer dereference, address: 0000000000000008
> RIP: 0010:fib6_walk_continue+0x6e/0x170
> Call Trace:
> <TASK>
> fib6_dump_table.isra.0+0xc5/0x240
> inet6_dump_fib+0xf6/0x420
> rtnl_dumpit+0x30/0xa0
> netlink_dump+0x15b/0x460
> netlink_recvmsg+0x1d6/0x2a0
> ____sys_recvmsg+0x17a/0x190
>
> Fix by storing tb->tb6_id in cb->args[1] instead of a positional
> index. On resume, skip entries until the id matches; a concurrent
> head-insert can never match the saved id, so the walker always
> resumes on the correct table.
>
> Fixes: 1b43af5480c3 ("[IPV6]: Increase number of possible routing tables to 2^32")
> Signed-off-by: Pengfei Zhang <zhangfeionline@gmail.com>
Reviewed-by: Ido Schimmel <idosch@nvidia.com>
You should have waited at least 24h between versions:
https://docs.kernel.org/process/maintainer-netdev.html
The same pattern exists in IPv4, but there we don't crash because the
per-table resume logic is different. Instead, it is possible that we
restart the dump from the wrong table and re-dump routes from the next
table in the chain.
I'm aware that netlink dumps do not guarantee consistency, but for
parity / robustness reasons I suggest to align IPv4 with IPv6 and use
the same tb_id-based resume logic there. Given we don't crash there,
target the IPv4 patch at net-next (currently closed, should open next
week).
Thanks
Hi Ido, Thank you for the review. Thanks also to Jakub Kicinski, Eric Dumazet and Kuniyuki Iwashima for their feedback on the earlier versions. I will prepare a patch to fix inet_dump_fib() using the same tb_id-based resume logic and send it to net-next once it opens. Thanks, Pengfei
© 2016 - 2026 Red Hat, Inc.