Commit 2bec5a36 authored by Patrick McHardy's avatar Patrick McHardy Committed by David S. Miller

ipv6: fib: fix crash when changing large fib while dumping it

When the fib size exceeds what can be dumped in a single skb, the
dump is suspended and resumed once the last skb has been received
by userspace. When the fib is changed while the dump is suspended,
the walker might contain stale pointers, causing a crash when the
dump is resumed.

BUG: unable to handle kernel NULL pointer dereference at 0000000000000018
IP: [<ffffffffa01bce04>] fib6_walk_continue+0xbb/0x124 [ipv6]
PGD 5347a067 PUD 65c7067 PMD 0
Oops: 0000 [#1] PREEMPT SMP
...
RIP: 0010:[<ffffffffa01bce04>]
[<ffffffffa01bce04>] fib6_walk_continue+0xbb/0x124 [ipv6]
...
Call Trace:
 [<ffffffff8104aca3>] ? mutex_spin_on_owner+0x59/0x71
 [<ffffffffa01bd105>] inet6_dump_fib+0x11b/0x1b9 [ipv6]
 [<ffffffff81371af4>] netlink_dump+0x5b/0x19e
 [<ffffffff8134f288>] ? consume_skb+0x28/0x2a
 [<ffffffff81373b69>] netlink_recvmsg+0x1ab/0x2c6
 [<ffffffff81372781>] ? netlink_unicast+0xfa/0x151
 [<ffffffff813483e0>] __sock_recvmsg+0x6d/0x79
 [<ffffffff81348a53>] sock_recvmsg+0xca/0xe3
 [<ffffffff81066d4b>] ? autoremove_wake_function+0x0/0x38
 [<ffffffff811ed1f8>] ? radix_tree_lookup_slot+0xe/0x10
 [<ffffffff810b3ed7>] ? find_get_page+0x90/0xa5
 [<ffffffff810b5dc5>] ? filemap_fault+0x201/0x34f
 [<ffffffff810ef152>] ? fget_light+0x2f/0xac
 [<ffffffff813519e7>] ? verify_iovec+0x4f/0x94
 [<ffffffff81349a65>] sys_recvmsg+0x14d/0x223

Store the serial number when beginning to walk the fib and reload
pointers when continuing to walk after a change occured. Similar
to other dumping functions, this might cause unrelated entries to
be missed when entries are deleted.
Tested-by: default avatarBen Greear <greearb@candelatech.com>
Signed-off-by: default avatarPatrick McHardy <kaber@trash.net>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 69a6a0b3
...@@ -129,6 +129,8 @@ struct fib6_walker_t { ...@@ -129,6 +129,8 @@ struct fib6_walker_t {
struct rt6_info *leaf; struct rt6_info *leaf;
unsigned char state; unsigned char state;
unsigned char prune; unsigned char prune;
unsigned int skip;
unsigned int count;
int (*func)(struct fib6_walker_t *); int (*func)(struct fib6_walker_t *);
void *args; void *args;
}; };
......
...@@ -319,12 +319,26 @@ static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb, ...@@ -319,12 +319,26 @@ static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
w->root = &table->tb6_root; w->root = &table->tb6_root;
if (cb->args[4] == 0) { if (cb->args[4] == 0) {
w->count = 0;
w->skip = 0;
read_lock_bh(&table->tb6_lock); read_lock_bh(&table->tb6_lock);
res = fib6_walk(w); res = fib6_walk(w);
read_unlock_bh(&table->tb6_lock); read_unlock_bh(&table->tb6_lock);
if (res > 0) if (res > 0) {
cb->args[4] = 1; cb->args[4] = 1;
cb->args[5] = w->root->fn_sernum;
}
} else { } else {
if (cb->args[5] != w->root->fn_sernum) {
/* Begin at the root if the tree changed */
cb->args[5] = w->root->fn_sernum;
w->state = FWS_INIT;
w->node = w->root;
w->skip = w->count;
} else
w->skip = 0;
read_lock_bh(&table->tb6_lock); read_lock_bh(&table->tb6_lock);
res = fib6_walk_continue(w); res = fib6_walk_continue(w);
read_unlock_bh(&table->tb6_lock); read_unlock_bh(&table->tb6_lock);
...@@ -1250,9 +1264,18 @@ static int fib6_walk_continue(struct fib6_walker_t *w) ...@@ -1250,9 +1264,18 @@ static int fib6_walk_continue(struct fib6_walker_t *w)
w->leaf = fn->leaf; w->leaf = fn->leaf;
case FWS_C: case FWS_C:
if (w->leaf && fn->fn_flags&RTN_RTINFO) { if (w->leaf && fn->fn_flags&RTN_RTINFO) {
int err = w->func(w); int err;
if (w->count < w->skip) {
w->count++;
continue;
}
err = w->func(w);
if (err) if (err)
return err; return err;
w->count++;
continue; continue;
} }
w->state = FWS_U; w->state = FWS_U;
...@@ -1346,6 +1369,8 @@ static void fib6_clean_tree(struct net *net, struct fib6_node *root, ...@@ -1346,6 +1369,8 @@ static void fib6_clean_tree(struct net *net, struct fib6_node *root,
c.w.root = root; c.w.root = root;
c.w.func = fib6_clean_node; c.w.func = fib6_clean_node;
c.w.prune = prune; c.w.prune = prune;
c.w.count = 0;
c.w.skip = 0;
c.func = func; c.func = func;
c.arg = arg; c.arg = arg;
c.net = net; c.net = net;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment