ipv6: fib: fix crash when changing large fib while dumping it
authorPatrick McHardy <kaber@trash.net>
Mon, 8 Feb 2010 05:19:03 +0000 (05:19 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 12 Feb 2010 20:06:35 +0000 (12:06 -0800)
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: Ben Greear <greearb@candelatech.com>
Signed-off-by: Patrick McHardy <kaber@trash.net>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/net/ip6_fib.h
net/ipv6/ip6_fib.c

index 257808188add82c22d275e0fd3139c128e8ec4eb..8f279ddb359398a9c5ed44a6f64a179c34abedf6 100644 (file)
@@ -129,6 +129,8 @@ struct fib6_walker_t {
        struct rt6_info *leaf;
        unsigned char state;
        unsigned char prune;
+       unsigned int skip;
+       unsigned int count;
        int (*func)(struct fib6_walker_t *);
        void *args;
 };
index f626ea2b304f7fcf8d4f630252e51df2656ec54e..77e122f53ea667a5def263270c48333285eb268e 100644 (file)
@@ -319,12 +319,26 @@ static int fib6_dump_table(struct fib6_table *table, struct sk_buff *skb,
        w->root = &table->tb6_root;
 
        if (cb->args[4] == 0) {
+               w->count = 0;
+               w->skip = 0;
+
                read_lock_bh(&table->tb6_lock);
                res = fib6_walk(w);
                read_unlock_bh(&table->tb6_lock);
-               if (res > 0)
+               if (res > 0) {
                        cb->args[4] = 1;
+                       cb->args[5] = w->root->fn_sernum;
+               }
        } 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);
                res = fib6_walk_continue(w);
                read_unlock_bh(&table->tb6_lock);
@@ -1250,9 +1264,18 @@ static int fib6_walk_continue(struct fib6_walker_t *w)
                        w->leaf = fn->leaf;
                case FWS_C:
                        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)
                                        return err;
+
+                               w->count++;
                                continue;
                        }
                        w->state = FWS_U;
@@ -1346,6 +1369,8 @@ static void fib6_clean_tree(struct net *net, struct fib6_node *root,
        c.w.root = root;
        c.w.func = fib6_clean_node;
        c.w.prune = prune;
+       c.w.count = 0;
+       c.w.skip = 0;
        c.func = func;
        c.arg = arg;
        c.net = net;