Commit cbd140b7 authored by Amir Goldstein's avatar Amir Goldstein Committed by Zhihao Cheng
Browse files

locks: fix TOCTOU race when granting write lease

mainline inclusion
from mainline-v6.1-rc1
commit d6da19c9
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/IAWXV9
CVE: NA

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d6da19c9cace63290ccfccb1fc35151ffefc0bec



--------------------------------

Thread A trying to acquire a write lease checks the value of i_readcount
and i_writecount in check_conflicting_open() to verify that its own fd
is the only fd referencing the file.

Thread B trying to open the file for read will call break_lease() in
do_dentry_open() before incrementing i_readcount, which leaves a small
window where thread A can acquire the write lease and then thread B
completes the open of the file for read without breaking the write lease
that was acquired by thread A.

Fix this race by incrementing i_readcount before checking for existing
leases, same as the case with i_writecount.

Use a helper put_file_access() to decrement i_readcount or i_writecount
in do_dentry_open() and __fput().

Fixes: 387e3746 ("locks: eliminate false positive conflicts for write lease")
Reviewed-by: default avatarJeff Layton <jlayton@kernel.org>
Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Signed-off-by: default avatarZhihao Cheng <chengzhihao1@huawei.com>
parent d0ed1153
Loading
Loading
Loading
Loading
+1 −6
Original line number Diff line number Diff line
@@ -316,12 +316,7 @@ static void __fput(struct file *file)
	}
	fops_put(file->f_op);
	put_pid(file->f_owner.pid);
	if ((mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
		i_readcount_dec(inode);
	if (mode & FMODE_WRITER) {
		put_write_access(inode);
		__mnt_drop_write(mnt);
	}
	put_file_access(file);
	dput(dentry);
	if (unlikely(mode & FMODE_NEED_UNMOUNT))
		dissolve_on_fput(mnt);
+10 −0
Original line number Diff line number Diff line
@@ -110,6 +110,16 @@ extern void chroot_fs_refs(const struct path *, const struct path *);
extern struct file *alloc_empty_file(int, const struct cred *);
extern struct file *alloc_empty_file_noaccount(int, const struct cred *);

static inline void put_file_access(struct file *file)
{
	if ((file->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) {
		i_readcount_dec(file->f_inode);
	} else if (file->f_mode & FMODE_WRITER) {
		put_write_access(file->f_inode);
		__mnt_drop_write(file->f_path.mnt);
	}
}

/*
 * super.c
 */
+4 −7
Original line number Diff line number Diff line
@@ -779,7 +779,9 @@ static int do_dentry_open(struct file *f,
		return 0;
	}

	if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
	if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ) {
		i_readcount_inc(inode);
	} else if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
		error = get_write_access(inode);
		if (unlikely(error))
			goto cleanup_file;
@@ -819,8 +821,6 @@ static int do_dentry_open(struct file *f,
			goto cleanup_all;
	}
	f->f_mode |= FMODE_OPENED;
	if ((f->f_mode & (FMODE_READ | FMODE_WRITE)) == FMODE_READ)
		i_readcount_inc(inode);
	if ((f->f_mode & FMODE_READ) &&
	     likely(f->f_op->read || f->f_op->read_iter))
		f->f_mode |= FMODE_CAN_READ;
@@ -852,10 +852,7 @@ static int do_dentry_open(struct file *f,
	if (WARN_ON_ONCE(error > 0))
		error = -EINVAL;
	fops_put(f->f_op);
	if (f->f_mode & FMODE_WRITER) {
		put_write_access(inode);
		__mnt_drop_write(f->f_path.mnt);
	}
	put_file_access(f);
cleanup_file:
	path_put(&f->f_path);
	f->f_path.mnt = NULL;