Commit 63da022a authored by Theodore Ts'o's avatar Theodore Ts'o Committed by Kleber Sacilotto de Souza

loop: add recursion validation to LOOP_CHANGE_FD

BugLink: https://bugs.launchpad.net/bugs/1790620

commit d2ac838e upstream.

Refactor the validation code used in LOOP_SET_FD so it is also used in
LOOP_CHANGE_FD.  Otherwise it is possible to construct a set of loop
devices that all refer to each other.  This can lead to a infinite
loop in starting with "while (is_loop_device(f)) .." in loop_set_fd().

Fix this by refactoring out the validation code and using it for
LOOP_CHANGE_FD as well as LOOP_SET_FD.

Reported-by: syzbot+4349872271ece473a7c91190b68b4bac7c5dbc87@syzkaller.appspotmail.com
Reported-by: syzbot+40bd32c4d9a3cc12a339@syzkaller.appspotmail.com
Reported-by: syzbot+769c54e66f994b041be7@syzkaller.appspotmail.com
Reported-by: syzbot+0a89a9ce473936c57065@syzkaller.appspotmail.com
Signed-off-by: default avatarTheodore Ts'o <tytso@mit.edu>
Signed-off-by: default avatarJens Axboe <axboe@kernel.dk>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarStefan Bader <stefan.bader@canonical.com>
Signed-off-by: default avatarKleber Sacilotto de Souza <kleber.souza@canonical.com>
parent e9440bf8
...@@ -663,6 +663,36 @@ static void loop_reread_partitions(struct loop_device *lo, ...@@ -663,6 +663,36 @@ static void loop_reread_partitions(struct loop_device *lo,
__func__, lo->lo_number, lo->lo_file_name, rc); __func__, lo->lo_number, lo->lo_file_name, rc);
} }
static inline int is_loop_device(struct file *file)
{
struct inode *i = file->f_mapping->host;
return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR;
}
static int loop_validate_file(struct file *file, struct block_device *bdev)
{
struct inode *inode = file->f_mapping->host;
struct file *f = file;
/* Avoid recursion */
while (is_loop_device(f)) {
struct loop_device *l;
if (f->f_mapping->host->i_bdev == bdev)
return -EBADF;
l = f->f_mapping->host->i_bdev->bd_disk->private_data;
if (l->lo_state == Lo_unbound) {
return -EINVAL;
}
f = l->lo_backing_file;
}
if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
return -EINVAL;
return 0;
}
/* /*
* loop_change_fd switched the backing store of a loopback device to * loop_change_fd switched the backing store of a loopback device to
* a new file. This is useful for operating system installers to free up * a new file. This is useful for operating system installers to free up
...@@ -692,6 +722,11 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev, ...@@ -692,6 +722,11 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev,
file = fget(arg); file = fget(arg);
if (!file) if (!file)
goto out; goto out;
error = loop_validate_file(file, bdev);
if (error)
goto out_putf;
f = loop_real_file(file); f = loop_real_file(file);
if (f) { if (f) {
virt_file = file; virt_file = file;
...@@ -705,9 +740,6 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev, ...@@ -705,9 +740,6 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev,
error = -EINVAL; error = -EINVAL;
if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
goto out_putf;
/* size of the new backing store needs to be the same */ /* size of the new backing store needs to be the same */
if (get_loop_size(lo, file) != get_loop_size(lo, old_file)) if (get_loop_size(lo, file) != get_loop_size(lo, old_file))
goto out_putf; goto out_putf;
...@@ -732,13 +764,6 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev, ...@@ -732,13 +764,6 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev,
return error; return error;
} }
static inline int is_loop_device(struct file *file)
{
struct inode *i = file->f_mapping->host;
return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR;
}
/* /*
* for AUFS * for AUFS
* no get/put for file. * no get/put for file.
...@@ -940,29 +965,13 @@ static int loop_set_fd(struct loop_device *lo, fmode_t mode, ...@@ -940,29 +965,13 @@ static int loop_set_fd(struct loop_device *lo, fmode_t mode,
if (lo->lo_state != Lo_unbound) if (lo->lo_state != Lo_unbound)
goto out_putf; goto out_putf;
/* Avoid recursion */ error = loop_validate_file(file, bdev);
f = file; if (error)
while (is_loop_device(f)) { goto out_putf;
struct loop_device *l;
if (f->f_mapping->host->i_bdev == bdev)
goto out_putf;
l = f->f_mapping->host->i_bdev->bd_disk->private_data;
if (l->lo_state == Lo_unbound) {
error = -EINVAL;
goto out_putf;
}
f = l->lo_backing_file;
}
mapping = file->f_mapping; mapping = file->f_mapping;
inode = mapping->host; inode = mapping->host;
error = -EINVAL;
if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
goto out_putf;
if (!(file->f_mode & FMODE_WRITE) || !(mode & FMODE_WRITE) || if (!(file->f_mode & FMODE_WRITE) || !(mode & FMODE_WRITE) ||
!file->f_op->write_iter) !file->f_op->write_iter)
lo_flags |= LO_FLAGS_READ_ONLY; lo_flags |= LO_FLAGS_READ_ONLY;
......
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