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

Commit 63edbce1 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull ubifs fixes from Al Viro:
 "A couple of ubifs readdir/lseek race fixes.  Stable fodder, really
  nasty..."

* 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs:
  UBIFS: fix a horrid bug
  UBIFS: prepare to fix a horrid bug
parents a61aef7f 605c912b
Loading
Loading
Loading
Loading
+39 −15
Original line number Original line Diff line number Diff line
@@ -349,31 +349,50 @@ static unsigned int vfs_dent_type(uint8_t type)
static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
{
{
	int err, over = 0;
	int err, over = 0;
	loff_t pos = file->f_pos;
	struct qstr nm;
	struct qstr nm;
	union ubifs_key key;
	union ubifs_key key;
	struct ubifs_dent_node *dent;
	struct ubifs_dent_node *dent;
	struct inode *dir = file_inode(file);
	struct inode *dir = file_inode(file);
	struct ubifs_info *c = dir->i_sb->s_fs_info;
	struct ubifs_info *c = dir->i_sb->s_fs_info;


	dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, file->f_pos);
	dbg_gen("dir ino %lu, f_pos %#llx", dir->i_ino, pos);


	if (file->f_pos > UBIFS_S_KEY_HASH_MASK || file->f_pos == 2)
	if (pos > UBIFS_S_KEY_HASH_MASK || pos == 2)
		/*
		/*
		 * The directory was seek'ed to a senseless position or there
		 * The directory was seek'ed to a senseless position or there
		 * are no more entries.
		 * are no more entries.
		 */
		 */
		return 0;
		return 0;


	if (file->f_version == 0) {
		/*
		 * The file was seek'ed, which means that @file->private_data
		 * is now invalid. This may also be just the first
		 * 'ubifs_readdir()' invocation, in which case
		 * @file->private_data is NULL, and the below code is
		 * basically a no-op.
		 */
		kfree(file->private_data);
		file->private_data = NULL;
	}

	/*
	 * 'generic_file_llseek()' unconditionally sets @file->f_version to
	 * zero, and we use this for detecting whether the file was seek'ed.
	 */
	file->f_version = 1;

	/* File positions 0 and 1 correspond to "." and ".." */
	/* File positions 0 and 1 correspond to "." and ".." */
	if (file->f_pos == 0) {
	if (pos == 0) {
		ubifs_assert(!file->private_data);
		ubifs_assert(!file->private_data);
		over = filldir(dirent, ".", 1, 0, dir->i_ino, DT_DIR);
		over = filldir(dirent, ".", 1, 0, dir->i_ino, DT_DIR);
		if (over)
		if (over)
			return 0;
			return 0;
		file->f_pos = 1;
		file->f_pos = pos = 1;
	}
	}


	if (file->f_pos == 1) {
	if (pos == 1) {
		ubifs_assert(!file->private_data);
		ubifs_assert(!file->private_data);
		over = filldir(dirent, "..", 2, 1,
		over = filldir(dirent, "..", 2, 1,
			       parent_ino(file->f_path.dentry), DT_DIR);
			       parent_ino(file->f_path.dentry), DT_DIR);
@@ -389,7 +408,7 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
			goto out;
			goto out;
		}
		}


		file->f_pos = key_hash_flash(c, &dent->key);
		file->f_pos = pos = key_hash_flash(c, &dent->key);
		file->private_data = dent;
		file->private_data = dent;
	}
	}


@@ -397,17 +416,16 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
	if (!dent) {
	if (!dent) {
		/*
		/*
		 * The directory was seek'ed to and is now readdir'ed.
		 * The directory was seek'ed to and is now readdir'ed.
		 * Find the entry corresponding to @file->f_pos or the
		 * Find the entry corresponding to @pos or the closest one.
		 * closest one.
		 */
		 */
		dent_key_init_hash(c, &key, dir->i_ino, file->f_pos);
		dent_key_init_hash(c, &key, dir->i_ino, pos);
		nm.name = NULL;
		nm.name = NULL;
		dent = ubifs_tnc_next_ent(c, &key, &nm);
		dent = ubifs_tnc_next_ent(c, &key, &nm);
		if (IS_ERR(dent)) {
		if (IS_ERR(dent)) {
			err = PTR_ERR(dent);
			err = PTR_ERR(dent);
			goto out;
			goto out;
		}
		}
		file->f_pos = key_hash_flash(c, &dent->key);
		file->f_pos = pos = key_hash_flash(c, &dent->key);
		file->private_data = dent;
		file->private_data = dent;
	}
	}


@@ -419,7 +437,7 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
			     ubifs_inode(dir)->creat_sqnum);
			     ubifs_inode(dir)->creat_sqnum);


		nm.len = le16_to_cpu(dent->nlen);
		nm.len = le16_to_cpu(dent->nlen);
		over = filldir(dirent, dent->name, nm.len, file->f_pos,
		over = filldir(dirent, dent->name, nm.len, pos,
			       le64_to_cpu(dent->inum),
			       le64_to_cpu(dent->inum),
			       vfs_dent_type(dent->type));
			       vfs_dent_type(dent->type));
		if (over)
		if (over)
@@ -435,9 +453,17 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
		}
		}


		kfree(file->private_data);
		kfree(file->private_data);
		file->f_pos = key_hash_flash(c, &dent->key);
		file->f_pos = pos = key_hash_flash(c, &dent->key);
		file->private_data = dent;
		file->private_data = dent;
		cond_resched();
		cond_resched();

		if (file->f_version == 0)
			/*
			 * The file was seek'ed meanwhile, lets return and start
			 * reading direntries from the new position on the next
			 * invocation.
			 */
			return 0;
	}
	}


out:
out:
@@ -448,15 +474,13 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)


	kfree(file->private_data);
	kfree(file->private_data);
	file->private_data = NULL;
	file->private_data = NULL;
	/* 2 is a special value indicating that there are no more direntries */
	file->f_pos = 2;
	file->f_pos = 2;
	return 0;
	return 0;
}
}


/* If a directory is seeked, we have to free saved readdir() state */
static loff_t ubifs_dir_llseek(struct file *file, loff_t offset, int whence)
static loff_t ubifs_dir_llseek(struct file *file, loff_t offset, int whence)
{
{
	kfree(file->private_data);
	file->private_data = NULL;
	return generic_file_llseek(file, offset, whence);
	return generic_file_llseek(file, offset, whence);
}
}