]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - fs/proc/generic.c
Fix rmmod/read/write races in /proc entries
[mirror_ubuntu-zesty-kernel.git] / fs / proc / generic.c
index 8a40e15f5ecb90333f7eca84ede39c40e7adb945..4f8e53568b22d24ba1334fbc95363b8d5b2fd765 100644 (file)
@@ -20,6 +20,7 @@
 #include <linux/namei.h>
 #include <linux/bitops.h>
 #include <linux/spinlock.h>
+#include <linux/completion.h>
 #include <asm/uaccess.h>
 
 #include "internal.h"
@@ -613,6 +614,9 @@ static struct proc_dir_entry *proc_create(struct proc_dir_entry **parent,
        ent->namelen = len;
        ent->mode = mode;
        ent->nlink = nlink;
+       ent->pde_users = 0;
+       spin_lock_init(&ent->pde_unload_lock);
+       ent->pde_unload_completion = NULL;
  out:
        return ent;
 }
@@ -734,9 +738,35 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
                de = *p;
                *p = de->next;
                de->next = NULL;
+
+               spin_lock(&de->pde_unload_lock);
+               /*
+                * Stop accepting new callers into module. If you're
+                * dynamically allocating ->proc_fops, save a pointer somewhere.
+                */
+               de->proc_fops = NULL;
+               /* Wait until all existing callers into module are done. */
+               if (de->pde_users > 0) {
+                       DECLARE_COMPLETION_ONSTACK(c);
+
+                       if (!de->pde_unload_completion)
+                               de->pde_unload_completion = &c;
+
+                       spin_unlock(&de->pde_unload_lock);
+                       spin_unlock(&proc_subdir_lock);
+
+                       wait_for_completion(de->pde_unload_completion);
+
+                       spin_lock(&proc_subdir_lock);
+                       goto continue_removing;
+               }
+               spin_unlock(&de->pde_unload_lock);
+
+continue_removing:
                if (S_ISDIR(de->mode))
                        parent->nlink--;
-               proc_kill_inodes(de);
+               if (!S_ISREG(de->mode))
+                       proc_kill_inodes(de);
                de->nlink = 0;
                WARN_ON(de->subdir);
                if (!atomic_read(&de->count))