Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs
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
This commit is contained in:
commit
63edbce160
@ -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)
|
||||
{
|
||||
int err, over = 0;
|
||||
loff_t pos = file->f_pos;
|
||||
struct qstr nm;
|
||||
union ubifs_key key;
|
||||
struct ubifs_dent_node *dent;
|
||||
struct inode *dir = file_inode(file);
|
||||
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
|
||||
* are no more entries.
|
||||
*/
|
||||
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 ".." */
|
||||
if (file->f_pos == 0) {
|
||||
if (pos == 0) {
|
||||
ubifs_assert(!file->private_data);
|
||||
over = filldir(dirent, ".", 1, 0, dir->i_ino, DT_DIR);
|
||||
if (over)
|
||||
return 0;
|
||||
file->f_pos = 1;
|
||||
file->f_pos = pos = 1;
|
||||
}
|
||||
|
||||
if (file->f_pos == 1) {
|
||||
if (pos == 1) {
|
||||
ubifs_assert(!file->private_data);
|
||||
over = filldir(dirent, "..", 2, 1,
|
||||
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;
|
||||
}
|
||||
|
||||
file->f_pos = key_hash_flash(c, &dent->key);
|
||||
file->f_pos = pos = key_hash_flash(c, &dent->key);
|
||||
file->private_data = dent;
|
||||
}
|
||||
|
||||
@ -397,17 +416,16 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
|
||||
if (!dent) {
|
||||
/*
|
||||
* The directory was seek'ed to and is now readdir'ed.
|
||||
* Find the entry corresponding to @file->f_pos or the
|
||||
* closest one.
|
||||
* Find the entry corresponding to @pos or the 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;
|
||||
dent = ubifs_tnc_next_ent(c, &key, &nm);
|
||||
if (IS_ERR(dent)) {
|
||||
err = PTR_ERR(dent);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -419,7 +437,7 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
|
||||
ubifs_inode(dir)->creat_sqnum);
|
||||
|
||||
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),
|
||||
vfs_dent_type(dent->type));
|
||||
if (over)
|
||||
@ -435,9 +453,17 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
|
||||
}
|
||||
|
||||
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;
|
||||
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:
|
||||
@ -448,15 +474,13 @@ out:
|
||||
|
||||
kfree(file->private_data);
|
||||
file->private_data = NULL;
|
||||
/* 2 is a special value indicating that there are no more direntries */
|
||||
file->f_pos = 2;
|
||||
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)
|
||||
{
|
||||
kfree(file->private_data);
|
||||
file->private_data = NULL;
|
||||
return generic_file_llseek(file, offset, whence);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user