Commit e06689bf authored by Alexey Dobriyan's avatar Alexey Dobriyan Committed by Linus Torvalds
Browse files

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: default avatarAlexey Dobriyan <adobriyan@gmail.com>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 3cde287b
Loading
Loading
Loading
Loading
+15 −16
Original line number Diff line number Diff line
@@ -138,8 +138,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;
@@ -362,6 +366,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;
@@ -472,10 +477,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;
}
@@ -505,10 +507,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;
}
@@ -666,8 +665,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);
@@ -676,9 +679,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);
@@ -714,13 +714,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);