aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTheodore Ts'o <tytso@mit.edu>2018-05-07 11:37:58 -0400
committerJens Axboe <axboe@kernel.dk>2018-06-05 11:08:21 -0400
commitd2ac838e4cd7e5e9891ecc094d626734b0245c99 (patch)
treeb93df5a59844ad585b25dad0ee85890a2cc0fbae
parentd377535405686f735b90a8ad4ba269484cd7c96e (diff)
loop: add recursion validation to LOOP_CHANGE_FD
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: Theodore Ts'o <tytso@mit.edu> Signed-off-by: Jens Axboe <axboe@kernel.dk>
-rw-r--r--drivers/block/loop.c68
1 files changed, 38 insertions, 30 deletions
diff --git a/drivers/block/loop.c b/drivers/block/loop.c
index 4838b0dbaad3..f8f3ca6e77fd 100644
--- a/drivers/block/loop.c
+++ b/drivers/block/loop.c
@@ -644,6 +644,36 @@ static void loop_reread_partitions(struct loop_device *lo,
644 __func__, lo->lo_number, lo->lo_file_name, rc); 644 __func__, lo->lo_number, lo->lo_file_name, rc);
645} 645}
646 646
647static inline int is_loop_device(struct file *file)
648{
649 struct inode *i = file->f_mapping->host;
650
651 return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR;
652}
653
654static int loop_validate_file(struct file *file, struct block_device *bdev)
655{
656 struct inode *inode = file->f_mapping->host;
657 struct file *f = file;
658
659 /* Avoid recursion */
660 while (is_loop_device(f)) {
661 struct loop_device *l;
662
663 if (f->f_mapping->host->i_bdev == bdev)
664 return -EBADF;
665
666 l = f->f_mapping->host->i_bdev->bd_disk->private_data;
667 if (l->lo_state == Lo_unbound) {
668 return -EINVAL;
669 }
670 f = l->lo_backing_file;
671 }
672 if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
673 return -EINVAL;
674 return 0;
675}
676
647/* 677/*
648 * loop_change_fd switched the backing store of a loopback device to 678 * loop_change_fd switched the backing store of a loopback device to
649 * a new file. This is useful for operating system installers to free up 679 * a new file. This is useful for operating system installers to free up
@@ -673,14 +703,15 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev,
673 if (!file) 703 if (!file)
674 goto out; 704 goto out;
675 705
706 error = loop_validate_file(file, bdev);
707 if (error)
708 goto out_putf;
709
676 inode = file->f_mapping->host; 710 inode = file->f_mapping->host;
677 old_file = lo->lo_backing_file; 711 old_file = lo->lo_backing_file;
678 712
679 error = -EINVAL; 713 error = -EINVAL;
680 714
681 if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
682 goto out_putf;
683
684 /* size of the new backing store needs to be the same */ 715 /* size of the new backing store needs to be the same */
685 if (get_loop_size(lo, file) != get_loop_size(lo, old_file)) 716 if (get_loop_size(lo, file) != get_loop_size(lo, old_file))
686 goto out_putf; 717 goto out_putf;
@@ -706,13 +737,6 @@ static int loop_change_fd(struct loop_device *lo, struct block_device *bdev,
706 return error; 737 return error;
707} 738}
708 739
709static inline int is_loop_device(struct file *file)
710{
711 struct inode *i = file->f_mapping->host;
712
713 return i && S_ISBLK(i->i_mode) && MAJOR(i->i_rdev) == LOOP_MAJOR;
714}
715
716/* loop sysfs attributes */ 740/* loop sysfs attributes */
717 741
718static ssize_t loop_attr_show(struct device *dev, char *page, 742static ssize_t loop_attr_show(struct device *dev, char *page,
@@ -878,7 +902,7 @@ static int loop_prepare_queue(struct loop_device *lo)
878static int loop_set_fd(struct loop_device *lo, fmode_t mode, 902static int loop_set_fd(struct loop_device *lo, fmode_t mode,
879 struct block_device *bdev, unsigned int arg) 903 struct block_device *bdev, unsigned int arg)
880{ 904{
881 struct file *file, *f; 905 struct file *file;
882 struct inode *inode; 906 struct inode *inode;
883 struct address_space *mapping; 907 struct address_space *mapping;
884 int lo_flags = 0; 908 int lo_flags = 0;
@@ -897,29 +921,13 @@ static int loop_set_fd(struct loop_device *lo, fmode_t mode,
897 if (lo->lo_state != Lo_unbound) 921 if (lo->lo_state != Lo_unbound)
898 goto out_putf; 922 goto out_putf;
899 923
900 /* Avoid recursion */ 924 error = loop_validate_file(file, bdev);
901 f = file; 925 if (error)
902 while (is_loop_device(f)) { 926 goto out_putf;
903 struct loop_device *l;
904
905 if (f->f_mapping->host->i_bdev == bdev)
906 goto out_putf;
907
908 l = f->f_mapping->host->i_bdev->bd_disk->private_data;
909 if (l->lo_state == Lo_unbound) {
910 error = -EINVAL;
911 goto out_putf;
912 }
913 f = l->lo_backing_file;
914 }
915 927
916 mapping = file->f_mapping; 928 mapping = file->f_mapping;
917 inode = mapping->host; 929 inode = mapping->host;
918 930
919 error = -EINVAL;
920 if (!S_ISREG(inode->i_mode) && !S_ISBLK(inode->i_mode))
921 goto out_putf;
922
923 if (!(file->f_mode & FMODE_WRITE) || !(mode & FMODE_WRITE) || 931 if (!(file->f_mode & FMODE_WRITE) || !(mode & FMODE_WRITE) ||
924 !file->f_op->write_iter) 932 !file->f_op->write_iter)
925 lo_flags |= LO_FLAGS_READ_ONLY; 933 lo_flags |= LO_FLAGS_READ_ONLY;