Commit 605c912b authored by Artem Bityutskiy's avatar Artem Bityutskiy Committed by Al Viro

UBIFS: fix a horrid bug

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()'.

This means that 'file->private_data' can be freed while 'ubifs_readdir()' uses
it, and this is a very bad bug: not only 'ubifs_readdir()' can return garbage,
but this may corrupt memory and lead to all kinds of problems like crashes an
security holes.

This patch fixes the problem by using the 'file->f_version' field, which
'->llseek()' always unconditionally sets to zero. We set it to 1 in
'ubifs_readdir()' and whenever we detect that it became 0, we know there was a
seek and it is time to clear the state saved in 'file->private_data'.

I tested this patch by writing a user-space program which runds readdir and
seek in parallell. I could easily crash the kernel without these patches, but
could not crash it with these patches.

Cc: stable@vger.kernel.org
Reported-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
Tested-by: default avatarArtem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: default avatarArtem Bityutskiy <artem.bityutskiy@linux.intel.com>
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 33f1a63a
...@@ -365,6 +365,24 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) ...@@ -365,6 +365,24 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
*/ */
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 (pos == 0) { if (pos == 0) {
ubifs_assert(!file->private_data); ubifs_assert(!file->private_data);
...@@ -438,6 +456,14 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir) ...@@ -438,6 +456,14 @@ static int ubifs_readdir(struct file *file, void *dirent, filldir_t filldir)
file->f_pos = 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) ...@@ -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);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment