Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f7db5e76 authored by Ming Lei's avatar Ming Lei Committed by Greg Kroah-Hartman
Browse files

sysfs: fix use after free in case of concurrent read/write and readdir

The inode->i_mutex isn't hold when updating filp->f_pos
in read()/write(), so the filp->f_pos might be read as
0 or 1 in readdir() when there is concurrent read()/write()
on this same file, then may cause use after free in readdir().

The bug can be reproduced with Li Zefan's test code on the
link:

	https://patchwork.kernel.org/patch/2160771/



This patch fixes the use after free under this situation.

Cc: stable <stable@vger.kernel.org>
Reported-by: default avatarLi Zefan <lizefan@huawei.com>
Signed-off-by: default avatarMing Lei <ming.lei@canonical.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 0f8b1a02
Loading
Loading
Loading
Loading
+11 −4
Original line number Diff line number Diff line
@@ -999,6 +999,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
	enum kobj_ns_type type;
	const void *ns;
	ino_t ino;
	loff_t off;

	type = sysfs_ns_type(parent_sd);
	ns = sysfs_info(dentry->d_sb)->ns[type];
@@ -1021,6 +1022,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
			return 0;
	}
	mutex_lock(&sysfs_mutex);
	off = filp->f_pos;
	for (pos = sysfs_dir_pos(ns, parent_sd, filp->f_pos, pos);
	     pos;
	     pos = sysfs_dir_next_pos(ns, parent_sd, filp->f_pos, pos)) {
@@ -1032,19 +1034,24 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
		len = strlen(name);
		ino = pos->s_ino;
		type = dt_type(pos);
		filp->f_pos = pos->s_hash;
		off = filp->f_pos = pos->s_hash;
		filp->private_data = sysfs_get(pos);

		mutex_unlock(&sysfs_mutex);
		ret = filldir(dirent, name, len, filp->f_pos, ino, type);
		ret = filldir(dirent, name, len, off, ino, type);
		mutex_lock(&sysfs_mutex);
		if (ret < 0)
			break;
	}
	mutex_unlock(&sysfs_mutex);
	if ((filp->f_pos > 1) && !pos) { /* EOF */
		filp->f_pos = INT_MAX;

	/* don't reference last entry if its refcount is dropped */
	if (!pos) {
		filp->private_data = NULL;

		/* EOF and not changed as 0 or 1 in read/write path */
		if (off == filp->f_pos && off > 1)
			filp->f_pos = INT_MAX;
	}
	return 0;
}