[patch 06/86] proc: change ->nlink under proc_subdir_lock

Andrew Morton
2019-12-05T00:50:03Z

From: Alexey Dobriyan <adobriyan@gmail.com>
Subject: proc: change ->nlink under proc_subdir_lock

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>
---

fs/proc/generic.c | 31 +++++++++++++++----------------
1 file changed, 15 insertions(+), 16 deletions(-)

--- a/fs/proc/generic.c~proc-change-nlink-under-proc_subdir_lock
+++ a/fs/proc/generic.c
@@ -138,8 +138,12 @@ static int proc_getattr(const struct pat
{
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;
@@ -362,6 +366,7 @@ struct proc_dir_entry *proc_register(str
write_unlock(&proc_subdir_lock);
goto out_free_inum;
}
+ dir->nlink++;
write_unlock(&proc_subdir_lock);

return dp;
@@ -472,10 +477,7 @@ struct proc_dir_entry *proc_mkdir_data(c
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;
}
@@ -505,10 +507,7 @@ struct proc_dir_entry *proc_create_mount
ent->data = NULL;
ent->proc_fops = NULL;
ent->proc_iops = NULL;
- parent->nlink++;
ent = proc_register(parent, ent);
- if (!ent)
- parent->nlink--;
}
return ent;
}
@@ -666,8 +665,12 @@ void remove_proc_entry(const char *name,
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);
@@ -676,9 +679,6 @@ void remove_proc_entry(const char *name,

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);
@@ -714,13 +714,12 @@ int remove_proc_subtree(const char *name
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);
_