Skip to content

Commit

Permalink
UBIFS: prepare to fix a horrid bug
Browse files Browse the repository at this point in the history
commit 33f1a63 upstream.

Al Viro pointed me to the fact that '->readdir()' and '->llseek()' have no
mutual exclusion, which means the 'ubifs_dir_llseek()' can be run while we are
in the middle of 'ubifs_readdir()'.

First of all, this means that 'file->private_data' can be freed while
'ubifs_readdir()' uses it.  But this particular patch does not fix the problem.
This patch is only a preparation, and the fix will follow next.

In this patch we make 'ubifs_readdir()' stop using 'file->f_pos' directly,
because 'file->f_pos' can be changed by '->llseek()' at any point. This may
lead 'ubifs_readdir()' to returning inconsistent data: directory entry names
may correspond to incorrect file positions.

So here we introduce a local variable 'pos', read 'file->f_pose' once at very
the beginning, and then stick to 'pos'. The result of this is that when
'ubifs_dir_llseek()' changes 'file->f_pos' while we are in the middle of
'ubifs_readdir()', the latter "wins".

Reported-by: Al Viro <viro@zeniv.linux.org.uk>
Tested-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: Artem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Artem Bityutskiy authored and Greg Kroah-Hartman committed Jul 3, 2013
1 parent ba94200 commit cc35ca4
Showing 1 changed file with 12 additions and 12 deletions.
24 changes: 12 additions & 12 deletions fs/ubifs/dir.c
Original file line number Diff line number Diff line change
Expand Up @@ -349,31 +349,32 @@ 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;

/* 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);
Expand All @@ -389,25 +390,24 @@ 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;
}

dent = file->private_data;
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;
}

Expand All @@ -419,7 +419,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)
Expand All @@ -435,7 +435,7 @@ 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();
}
Expand Down

0 comments on commit cc35ca4

Please sign in to comment.