afs: Fix server record deletion
authorDavid Howells <dhowells@redhat.com>
Wed, 18 Apr 2018 08:38:34 +0000 (09:38 +0100)
committerLinus Torvalds <torvalds@linux-foundation.org>
Fri, 20 Apr 2018 16:59:33 +0000 (09:59 -0700)
AFS server records get removed from the net->fs_servers tree when
they're deleted, but not from the net->fs_addresses{4,6} lists, which
can lead to an oops in afs_find_server() when a server record has been
removed, for instance during rmmod.

Fix this by deleting the record from the by-address lists before posting
it for RCU destruction.

The reason this hasn't been noticed before is that the fileserver keeps
probing the local cache manager, thereby keeping the service record
alive, so the oops would only happen when a fileserver eventually gets
bored and stops pinging or if the module gets rmmod'd and a call comes
in from the fileserver during the window between the server records
being destroyed and the socket being closed.

The oops looks something like:

  BUG: unable to handle kernel NULL pointer dereference at 000000000000001c
  ...
  Workqueue: kafsd afs_process_async_call [kafs]
  RIP: 0010:afs_find_server+0x271/0x36f [kafs]
  ...
  Call Trace:
   afs_deliver_cb_init_call_back_state3+0x1f2/0x21f [kafs]
   afs_deliver_to_call+0x1ee/0x5e8 [kafs]
   afs_process_async_call+0x5b/0xd0 [kafs]
   process_one_work+0x2c2/0x504
   worker_thread+0x1d4/0x2ac
   kthread+0x11f/0x127
   ret_from_fork+0x24/0x30

Fixes: d2ddc776a458 ("afs: Overhaul volume and server record caching and fileserver rotation")
Signed-off-by: David Howells <dhowells@redhat.com>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
fs/afs/server.c

index e23be63998a809189c4b967072f9e2eeaa95b42f..629c74986cff4ad1535fcc3c814b6150011c5dec 100644 (file)
@@ -428,8 +428,15 @@ static void afs_gc_servers(struct afs_net *net, struct afs_server *gc_list)
                }
                write_sequnlock(&net->fs_lock);
 
-               if (deleted)
+               if (deleted) {
+                       write_seqlock(&net->fs_addr_lock);
+                       if (!hlist_unhashed(&server->addr4_link))
+                               hlist_del_rcu(&server->addr4_link);
+                       if (!hlist_unhashed(&server->addr6_link))
+                               hlist_del_rcu(&server->addr6_link);
+                       write_sequnlock(&net->fs_addr_lock);
                        afs_destroy_server(net, server);
+               }
        }
 }