diff options
| author | Theodore Ts'o <tytso@mit.edu> | 2018-10-12 09:28:09 -0400 |
|---|---|---|
| committer | Theodore Ts'o <tytso@mit.edu> | 2018-10-12 09:28:09 -0400 |
| commit | 33458eaba4dfe778a426df6a19b7aad2ff9f7eec (patch) | |
| tree | f4732bd54c93bd877209855b3148db771da85b55 | |
| parent | 6fd941784b8ac3e74313f7112f0586076dc36544 (diff) | |
ext4: fix use-after-free race in ext4_remount()'s error path
It's possible for ext4_show_quota_options() to try reading
s_qf_names[i] while it is being modified by ext4_remount() --- most
notably, in ext4_remount's error path when the original values of the
quota file name gets restored.
Reported-by: syzbot+a2872d6feea6918008a9@syzkaller.appspotmail.com
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Cc: stable@kernel.org # 3.2+
| -rw-r--r-- | fs/ext4/ext4.h | 3 | ||||
| -rw-r--r-- | fs/ext4/super.c | 73 |
2 files changed, 50 insertions, 26 deletions
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 86e1bacac757..12f90d48ba61 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h | |||
| @@ -1405,7 +1405,8 @@ struct ext4_sb_info { | |||
| 1405 | u32 s_min_batch_time; | 1405 | u32 s_min_batch_time; |
| 1406 | struct block_device *journal_bdev; | 1406 | struct block_device *journal_bdev; |
| 1407 | #ifdef CONFIG_QUOTA | 1407 | #ifdef CONFIG_QUOTA |
| 1408 | char *s_qf_names[EXT4_MAXQUOTAS]; /* Names of quota files with journalled quota */ | 1408 | /* Names of quota files with journalled quota */ |
| 1409 | char __rcu *s_qf_names[EXT4_MAXQUOTAS]; | ||
| 1409 | int s_jquota_fmt; /* Format of quota to use */ | 1410 | int s_jquota_fmt; /* Format of quota to use */ |
| 1410 | #endif | 1411 | #endif |
| 1411 | unsigned int s_want_extra_isize; /* New inodes should reserve # bytes */ | 1412 | unsigned int s_want_extra_isize; /* New inodes should reserve # bytes */ |
diff --git a/fs/ext4/super.c b/fs/ext4/super.c index faf293ed8060..a221f1cdf704 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c | |||
| @@ -914,6 +914,18 @@ static inline void ext4_quota_off_umount(struct super_block *sb) | |||
| 914 | for (type = 0; type < EXT4_MAXQUOTAS; type++) | 914 | for (type = 0; type < EXT4_MAXQUOTAS; type++) |
| 915 | ext4_quota_off(sb, type); | 915 | ext4_quota_off(sb, type); |
| 916 | } | 916 | } |
| 917 | |||
| 918 | /* | ||
| 919 | * This is a helper function which is used in the mount/remount | ||
| 920 | * codepaths (which holds s_umount) to fetch the quota file name. | ||
| 921 | */ | ||
| 922 | static inline char *get_qf_name(struct super_block *sb, | ||
| 923 | struct ext4_sb_info *sbi, | ||
| 924 | int type) | ||
| 925 | { | ||
| 926 | return rcu_dereference_protected(sbi->s_qf_names[type], | ||
| 927 | lockdep_is_held(&sb->s_umount)); | ||
| 928 | } | ||
| 917 | #else | 929 | #else |
| 918 | static inline void ext4_quota_off_umount(struct super_block *sb) | 930 | static inline void ext4_quota_off_umount(struct super_block *sb) |
| 919 | { | 931 | { |
| @@ -965,7 +977,7 @@ static void ext4_put_super(struct super_block *sb) | |||
| 965 | percpu_free_rwsem(&sbi->s_journal_flag_rwsem); | 977 | percpu_free_rwsem(&sbi->s_journal_flag_rwsem); |
| 966 | #ifdef CONFIG_QUOTA | 978 | #ifdef CONFIG_QUOTA |
| 967 | for (i = 0; i < EXT4_MAXQUOTAS; i++) | 979 | for (i = 0; i < EXT4_MAXQUOTAS; i++) |
| 968 | kfree(sbi->s_qf_names[i]); | 980 | kfree(get_qf_name(sb, sbi, i)); |
| 969 | #endif | 981 | #endif |
| 970 | 982 | ||
| 971 | /* Debugging code just in case the in-memory inode orphan list | 983 | /* Debugging code just in case the in-memory inode orphan list |
| @@ -1531,11 +1543,10 @@ static const char deprecated_msg[] = | |||
| 1531 | static int set_qf_name(struct super_block *sb, int qtype, substring_t *args) | 1543 | static int set_qf_name(struct super_block *sb, int qtype, substring_t *args) |
| 1532 | { | 1544 | { |
| 1533 | struct ext4_sb_info *sbi = EXT4_SB(sb); | 1545 | struct ext4_sb_info *sbi = EXT4_SB(sb); |
| 1534 | char *qname; | 1546 | char *qname, *old_qname = get_qf_name(sb, sbi, qtype); |
| 1535 | int ret = -1; | 1547 | int ret = -1; |
| 1536 | 1548 | ||
| 1537 | if (sb_any_quota_loaded(sb) && | 1549 | if (sb_any_quota_loaded(sb) && !old_qname) { |
| 1538 | !sbi->s_qf_names[qtype]) { | ||
| 1539 | ext4_msg(sb, KERN_ERR, | 1550 | ext4_msg(sb, KERN_ERR, |
| 1540 | "Cannot change journaled " | 1551 | "Cannot change journaled " |
| 1541 | "quota options when quota turned on"); | 1552 | "quota options when quota turned on"); |
| @@ -1552,8 +1563,8 @@ static int set_qf_name(struct super_block *sb, int qtype, substring_t *args) | |||
| 1552 | "Not enough memory for storing quotafile name"); | 1563 | "Not enough memory for storing quotafile name"); |
| 1553 | return -1; | 1564 | return -1; |
| 1554 | } | 1565 | } |
| 1555 | if (sbi->s_qf_names[qtype]) { | 1566 | if (old_qname) { |
| 1556 | if (strcmp(sbi->s_qf_names[qtype], qname) == 0) | 1567 | if (strcmp(old_qname, qname) == 0) |
| 1557 | ret = 1; | 1568 | ret = 1; |
| 1558 | else | 1569 | else |
| 1559 | ext4_msg(sb, KERN_ERR, | 1570 | ext4_msg(sb, KERN_ERR, |
| @@ -1566,7 +1577,7 @@ static int set_qf_name(struct super_block *sb, int qtype, substring_t *args) | |||
| 1566 | "quotafile must be on filesystem root"); | 1577 | "quotafile must be on filesystem root"); |
| 1567 | goto errout; | 1578 | goto errout; |
| 1568 | } | 1579 | } |
| 1569 | sbi->s_qf_names[qtype] = qname; | 1580 | rcu_assign_pointer(sbi->s_qf_names[qtype], qname); |
| 1570 | set_opt(sb, QUOTA); | 1581 | set_opt(sb, QUOTA); |
| 1571 | return 1; | 1582 | return 1; |
| 1572 | errout: | 1583 | errout: |
| @@ -1578,15 +1589,16 @@ static int clear_qf_name(struct super_block *sb, int qtype) | |||
| 1578 | { | 1589 | { |
| 1579 | 1590 | ||
| 1580 | struct ext4_sb_info *sbi = EXT4_SB(sb); | 1591 | struct ext4_sb_info *sbi = EXT4_SB(sb); |
| 1592 | char *old_qname = get_qf_name(sb, sbi, qtype); | ||
| 1581 | 1593 | ||
| 1582 | if (sb_any_quota_loaded(sb) && | 1594 | if (sb_any_quota_loaded(sb) && old_qname) { |
| 1583 | sbi->s_qf_names[qtype]) { | ||
| 1584 | ext4_msg(sb, KERN_ERR, "Cannot change journaled quota options" | 1595 | ext4_msg(sb, KERN_ERR, "Cannot change journaled quota options" |
| 1585 | " when quota turned on"); | 1596 | " when quota turned on"); |
| 1586 | return -1; | 1597 | return -1; |
| 1587 | } | 1598 | } |
| 1588 | kfree(sbi->s_qf_names[qtype]); | 1599 | rcu_assign_pointer(sbi->s_qf_names[qtype], NULL); |
| 1589 | sbi->s_qf_names[qtype] = NULL; | 1600 | synchronize_rcu(); |
| 1601 | kfree(old_qname); | ||
| 1590 | return 1; | 1602 | return 1; |
| 1591 | } | 1603 | } |
| 1592 | #endif | 1604 | #endif |
| @@ -1961,7 +1973,7 @@ static int parse_options(char *options, struct super_block *sb, | |||
| 1961 | int is_remount) | 1973 | int is_remount) |
| 1962 | { | 1974 | { |
| 1963 | struct ext4_sb_info *sbi = EXT4_SB(sb); | 1975 | struct ext4_sb_info *sbi = EXT4_SB(sb); |
| 1964 | char *p; | 1976 | char *p, __maybe_unused *usr_qf_name, __maybe_unused *grp_qf_name; |
| 1965 | substring_t args[MAX_OPT_ARGS]; | 1977 | substring_t args[MAX_OPT_ARGS]; |
| 1966 | int token; | 1978 | int token; |
| 1967 | 1979 | ||
| @@ -1992,11 +2004,13 @@ static int parse_options(char *options, struct super_block *sb, | |||
| 1992 | "Cannot enable project quota enforcement."); | 2004 | "Cannot enable project quota enforcement."); |
| 1993 | return 0; | 2005 | return 0; |
| 1994 | } | 2006 | } |
| 1995 | if (sbi->s_qf_names[USRQUOTA] || sbi->s_qf_names[GRPQUOTA]) { | 2007 | usr_qf_name = get_qf_name(sb, sbi, USRQUOTA); |
| 1996 | if (test_opt(sb, USRQUOTA) && sbi->s_qf_names[USRQUOTA]) | 2008 | grp_qf_name = get_qf_name(sb, sbi, GRPQUOTA); |
| 2009 | if (usr_qf_name || grp_qf_name) { | ||
| 2010 | if (test_opt(sb, USRQUOTA) && usr_qf_name) | ||
| 1997 | clear_opt(sb, USRQUOTA); | 2011 | clear_opt(sb, USRQUOTA); |
| 1998 | 2012 | ||
| 1999 | if (test_opt(sb, GRPQUOTA) && sbi->s_qf_names[GRPQUOTA]) | 2013 | if (test_opt(sb, GRPQUOTA) && grp_qf_name) |
| 2000 | clear_opt(sb, GRPQUOTA); | 2014 | clear_opt(sb, GRPQUOTA); |
| 2001 | 2015 | ||
| 2002 | if (test_opt(sb, GRPQUOTA) || test_opt(sb, USRQUOTA)) { | 2016 | if (test_opt(sb, GRPQUOTA) || test_opt(sb, USRQUOTA)) { |
| @@ -2030,6 +2044,7 @@ static inline void ext4_show_quota_options(struct seq_file *seq, | |||
| 2030 | { | 2044 | { |
| 2031 | #if defined(CONFIG_QUOTA) | 2045 | #if defined(CONFIG_QUOTA) |
| 2032 | struct ext4_sb_info *sbi = EXT4_SB(sb); | 2046 | struct ext4_sb_info *sbi = EXT4_SB(sb); |
| 2047 | char *usr_qf_name, *grp_qf_name; | ||
| 2033 | 2048 | ||
| 2034 | if (sbi->s_jquota_fmt) { | 2049 | if (sbi->s_jquota_fmt) { |
| 2035 | char *fmtname = ""; | 2050 | char *fmtname = ""; |
| @@ -2048,11 +2063,14 @@ static inline void ext4_show_quota_options(struct seq_file *seq, | |||
| 2048 | seq_printf(seq, ",jqfmt=%s", fmtname); | 2063 | seq_printf(seq, ",jqfmt=%s", fmtname); |
| 2049 | } | 2064 | } |
| 2050 | 2065 | ||
| 2051 | if (sbi->s_qf_names[USRQUOTA]) | 2066 | rcu_read_lock(); |
| 2052 | seq_show_option(seq, "usrjquota", sbi->s_qf_names[USRQUOTA]); | 2067 | usr_qf_name = rcu_dereference(sbi->s_qf_names[USRQUOTA]); |
| 2053 | 2068 | grp_qf_name = rcu_dereference(sbi->s_qf_names[GRPQUOTA]); | |
| 2054 | if (sbi->s_qf_names[GRPQUOTA]) | 2069 | if (usr_qf_name) |
| 2055 | seq_show_option(seq, "grpjquota", sbi->s_qf_names[GRPQUOTA]); | 2070 | seq_show_option(seq, "usrjquota", usr_qf_name); |
| 2071 | if (grp_qf_name) | ||
| 2072 | seq_show_option(seq, "grpjquota", grp_qf_name); | ||
| 2073 | rcu_read_unlock(); | ||
| 2056 | #endif | 2074 | #endif |
| 2057 | } | 2075 | } |
| 2058 | 2076 | ||
| @@ -5104,6 +5122,7 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data) | |||
| 5104 | int err = 0; | 5122 | int err = 0; |
| 5105 | #ifdef CONFIG_QUOTA | 5123 | #ifdef CONFIG_QUOTA |
| 5106 | int i, j; | 5124 | int i, j; |
| 5125 | char *to_free[EXT4_MAXQUOTAS]; | ||
| 5107 | #endif | 5126 | #endif |
| 5108 | char *orig_data = kstrdup(data, GFP_KERNEL); | 5127 | char *orig_data = kstrdup(data, GFP_KERNEL); |
| 5109 | 5128 | ||
| @@ -5123,8 +5142,9 @@ static int ext4_remount(struct super_block *sb, int *flags, char *data) | |||
| 5123 | old_opts.s_jquota_fmt = sbi->s_jquota_fmt; | 5142 | old_opts.s_jquota_fmt = sbi->s_jquota_fmt; |
| 5124 | for (i = 0; i < EXT4_MAXQUOTAS; i++) | 5143 | for (i = 0; i < EXT4_MAXQUOTAS; i++) |
| 5125 | if (sbi->s_qf_names[i]) { | 5144 | if (sbi->s_qf_names[i]) { |
| 5126 | old_opts.s_qf_names[i] = kstrdup(sbi->s_qf_names[i], | 5145 | char *qf_name = get_qf_name(sb, sbi, i); |
| 5127 | GFP_KERNEL); | 5146 | |
| 5147 | old_opts.s_qf_names[i] = kstrdup(qf_name, GFP_KERNEL); | ||
| 5128 | if (!old_opts.s_qf_names[i]) { | 5148 | if (!old_opts.s_qf_names[i]) { |
| 5129 | for (j = 0; j < i; j++) | 5149 | for (j = 0; j < i; j++) |
| 5130 | kfree(old_opts.s_qf_names[j]); | 5150 | kfree(old_opts.s_qf_names[j]); |
| @@ -5353,9 +5373,12 @@ restore_opts: | |||
| 5353 | #ifdef CONFIG_QUOTA | 5373 | #ifdef CONFIG_QUOTA |
| 5354 | sbi->s_jquota_fmt = old_opts.s_jquota_fmt; | 5374 | sbi->s_jquota_fmt = old_opts.s_jquota_fmt; |
| 5355 | for (i = 0; i < EXT4_MAXQUOTAS; i++) { | 5375 | for (i = 0; i < EXT4_MAXQUOTAS; i++) { |
| 5356 | kfree(sbi->s_qf_names[i]); | 5376 | to_free[i] = get_qf_name(sb, sbi, i); |
| 5357 | sbi->s_qf_names[i] = old_opts.s_qf_names[i]; | 5377 | rcu_assign_pointer(sbi->s_qf_names[i], old_opts.s_qf_names[i]); |
| 5358 | } | 5378 | } |
| 5379 | synchronize_rcu(); | ||
| 5380 | for (i = 0; i < EXT4_MAXQUOTAS; i++) | ||
| 5381 | kfree(to_free[i]); | ||
| 5359 | #endif | 5382 | #endif |
| 5360 | kfree(orig_data); | 5383 | kfree(orig_data); |
| 5361 | return err; | 5384 | return err; |
| @@ -5546,7 +5569,7 @@ static int ext4_write_info(struct super_block *sb, int type) | |||
| 5546 | */ | 5569 | */ |
| 5547 | static int ext4_quota_on_mount(struct super_block *sb, int type) | 5570 | static int ext4_quota_on_mount(struct super_block *sb, int type) |
| 5548 | { | 5571 | { |
| 5549 | return dquot_quota_on_mount(sb, EXT4_SB(sb)->s_qf_names[type], | 5572 | return dquot_quota_on_mount(sb, get_qf_name(sb, EXT4_SB(sb), type), |
| 5550 | EXT4_SB(sb)->s_jquota_fmt, type); | 5573 | EXT4_SB(sb)->s_jquota_fmt, type); |
| 5551 | } | 5574 | } |
| 5552 | 5575 | ||
