]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
proc: change ->nlink under proc_subdir_lock
authorAlexey Dobriyan <adobriyan@gmail.com>
Thu, 5 Dec 2019 00:49:59 +0000 (16:49 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 12 Jan 2021 19:10:17 +0000 (20:10 +0100)
[ Upstream commit e06689bf57017ac022ccf0f2a5071f760821ce0f ]

Currently gluing PDE into global /proc tree is done under lock, but
changing ->nlink is not.  Additionally struct proc_dir_entry::nlink is
not atomic so updates can be lost.

Link: http://lkml.kernel.org/r/20190925202436.GA17388@avx2
Signed-off-by: Alexey Dobriyan <adobriyan@gmail.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
fs/proc/generic.c

index e39bac94dead0965733eb779758edfae31aacb81..7820fe524cb0356a9cd8dd4aeb61b0bd41725ae5 100644 (file)
@@ -137,8 +137,12 @@ static int proc_getattr(const struct path *path, struct kstat *stat,
 {
        struct inode *inode = d_inode(path->dentry);
        struct proc_dir_entry *de = PDE(inode);
-       if (de && de->nlink)
-               set_nlink(inode, de->nlink);
+       if (de) {
+               nlink_t nlink = READ_ONCE(de->nlink);
+               if (nlink > 0) {
+                       set_nlink(inode, nlink);
+               }
+       }
 
        generic_fillattr(inode, stat);
        return 0;
@@ -361,6 +365,7 @@ struct proc_dir_entry *proc_register(struct proc_dir_entry *dir,
                write_unlock(&proc_subdir_lock);
                goto out_free_inum;
        }
+       dir->nlink++;
        write_unlock(&proc_subdir_lock);
 
        return dp;
@@ -471,10 +476,7 @@ struct proc_dir_entry *proc_mkdir_data(const char *name, umode_t mode,
                ent->data = data;
                ent->proc_fops = &proc_dir_operations;
                ent->proc_iops = &proc_dir_inode_operations;
-               parent->nlink++;
                ent = proc_register(parent, ent);
-               if (!ent)
-                       parent->nlink--;
        }
        return ent;
 }
@@ -504,10 +506,7 @@ struct proc_dir_entry *proc_create_mount_point(const char *name)
                ent->data = NULL;
                ent->proc_fops = NULL;
                ent->proc_iops = NULL;
-               parent->nlink++;
                ent = proc_register(parent, ent);
-               if (!ent)
-                       parent->nlink--;
        }
        return ent;
 }
@@ -665,8 +664,12 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
        len = strlen(fn);
 
        de = pde_subdir_find(parent, fn, len);
-       if (de)
+       if (de) {
                rb_erase(&de->subdir_node, &parent->subdir);
+               if (S_ISDIR(de->mode)) {
+                       parent->nlink--;
+               }
+       }
        write_unlock(&proc_subdir_lock);
        if (!de) {
                WARN(1, "name '%s'\n", name);
@@ -675,9 +678,6 @@ void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
 
        proc_entry_rundown(de);
 
-       if (S_ISDIR(de->mode))
-               parent->nlink--;
-       de->nlink = 0;
        WARN(pde_subdir_first(de),
             "%s: removing non-empty directory '%s/%s', leaking at least '%s'\n",
             __func__, de->parent->name, de->name, pde_subdir_first(de)->name);
@@ -713,13 +713,12 @@ int remove_proc_subtree(const char *name, struct proc_dir_entry *parent)
                        de = next;
                        continue;
                }
-               write_unlock(&proc_subdir_lock);
-
-               proc_entry_rundown(de);
                next = de->parent;
                if (S_ISDIR(de->mode))
                        next->nlink--;
-               de->nlink = 0;
+               write_unlock(&proc_subdir_lock);
+
+               proc_entry_rundown(de);
                if (de == root)
                        break;
                pde_put(de);