Skip to content

Commit 5354b2a

Browse files
committed
ext4: allow ext4_get_group_info() to fail
Previously, ext4_get_group_info() would treat an invalid group number as BUG(), since in theory it should never happen. However, if a malicious attaker (or fuzzer) modifies the superblock via the block device while it is the file system is mounted, it is possible for s_first_data_block to get set to a very large number. In that case, when calculating the block group of some block number (such as the starting block of a preallocation region), could result in an underflow and very large block group number. Then the BUG_ON check in ext4_get_group_info() would fire, resutling in a denial of service attack that can be triggered by root or someone with write access to the block device. For a quality of implementation perspective, it's best that even if the system administrator does something that they shouldn't, that it will not trigger a BUG. So instead of BUG'ing, ext4_get_group_info() will call ext4_error and return NULL. We also add fallback code in all of the callers of ext4_get_group_info() that it might NULL. Also, since ext4_get_group_info() was already borderline to be an inline function, un-inline it. The results in a next reduction of the compiled text size of ext4 by roughly 2k. Cc: [email protected] Link: https://lore.kernel.org/r/[email protected] Reported-by: [email protected] Link: https://syzkaller.appspot.com/bug?id=69b28112e098b070f639efb356393af3ffec4220 Signed-off-by: Theodore Ts'o <[email protected]> Reviewed-by: Jan Kara <[email protected]>
1 parent 949f95f commit 5354b2a

File tree

5 files changed

+82
-29
lines changed

5 files changed

+82
-29
lines changed

fs/ext4/balloc.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,22 @@ static ext4_fsblk_t ext4_valid_block_bitmap_padding(struct super_block *sb,
321321
return (next_zero_bit < bitmap_size ? next_zero_bit : 0);
322322
}
323323

324+
struct ext4_group_info *ext4_get_group_info(struct super_block *sb,
325+
ext4_group_t group)
326+
{
327+
struct ext4_group_info **grp_info;
328+
long indexv, indexh;
329+
330+
if (unlikely(group >= EXT4_SB(sb)->s_groups_count)) {
331+
ext4_error(sb, "invalid group %u", group);
332+
return NULL;
333+
}
334+
indexv = group >> (EXT4_DESC_PER_BLOCK_BITS(sb));
335+
indexh = group & ((EXT4_DESC_PER_BLOCK(sb)) - 1);
336+
grp_info = sbi_array_rcu_deref(EXT4_SB(sb), s_group_info, indexv);
337+
return grp_info[indexh];
338+
}
339+
324340
/*
325341
* Return the block number which was discovered to be invalid, or 0 if
326342
* the block bitmap is valid.
@@ -395,7 +411,7 @@ static int ext4_validate_block_bitmap(struct super_block *sb,
395411

396412
if (buffer_verified(bh))
397413
return 0;
398-
if (EXT4_MB_GRP_BBITMAP_CORRUPT(grp))
414+
if (!grp || EXT4_MB_GRP_BBITMAP_CORRUPT(grp))
399415
return -EFSCORRUPTED;
400416

401417
ext4_lock_group(sb, block_group);

fs/ext4/ext4.h

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2625,6 +2625,8 @@ extern void ext4_check_blocks_bitmap(struct super_block *);
26252625
extern struct ext4_group_desc * ext4_get_group_desc(struct super_block * sb,
26262626
ext4_group_t block_group,
26272627
struct buffer_head ** bh);
2628+
extern struct ext4_group_info *ext4_get_group_info(struct super_block *sb,
2629+
ext4_group_t group);
26282630
extern int ext4_should_retry_alloc(struct super_block *sb, int *retries);
26292631

26302632
extern struct buffer_head *ext4_read_block_bitmap_nowait(struct super_block *sb,
@@ -3232,19 +3234,6 @@ static inline void ext4_isize_set(struct ext4_inode *raw_inode, loff_t i_size)
32323234
raw_inode->i_size_high = cpu_to_le32(i_size >> 32);
32333235
}
32343236

3235-
static inline
3236-
struct ext4_group_info *ext4_get_group_info(struct super_block *sb,
3237-
ext4_group_t group)
3238-
{
3239-
struct ext4_group_info **grp_info;
3240-
long indexv, indexh;
3241-
BUG_ON(group >= EXT4_SB(sb)->s_groups_count);
3242-
indexv = group >> (EXT4_DESC_PER_BLOCK_BITS(sb));
3243-
indexh = group & ((EXT4_DESC_PER_BLOCK(sb)) - 1);
3244-
grp_info = sbi_array_rcu_deref(EXT4_SB(sb), s_group_info, indexv);
3245-
return grp_info[indexh];
3246-
}
3247-
32483237
/*
32493238
* Reading s_groups_count requires using smp_rmb() afterwards. See
32503239
* the locking protocol documented in the comments of ext4_group_add()

fs/ext4/ialloc.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ static int ext4_validate_inode_bitmap(struct super_block *sb,
9191

9292
if (buffer_verified(bh))
9393
return 0;
94-
if (EXT4_MB_GRP_IBITMAP_CORRUPT(grp))
94+
if (!grp || EXT4_MB_GRP_IBITMAP_CORRUPT(grp))
9595
return -EFSCORRUPTED;
9696

9797
ext4_lock_group(sb, block_group);
@@ -293,7 +293,7 @@ void ext4_free_inode(handle_t *handle, struct inode *inode)
293293
}
294294
if (!(sbi->s_mount_state & EXT4_FC_REPLAY)) {
295295
grp = ext4_get_group_info(sb, block_group);
296-
if (unlikely(EXT4_MB_GRP_IBITMAP_CORRUPT(grp))) {
296+
if (!grp || unlikely(EXT4_MB_GRP_IBITMAP_CORRUPT(grp))) {
297297
fatal = -EFSCORRUPTED;
298298
goto error_return;
299299
}
@@ -1046,7 +1046,7 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
10461046
* Skip groups with already-known suspicious inode
10471047
* tables
10481048
*/
1049-
if (EXT4_MB_GRP_IBITMAP_CORRUPT(grp))
1049+
if (!grp || EXT4_MB_GRP_IBITMAP_CORRUPT(grp))
10501050
goto next_group;
10511051
}
10521052

@@ -1183,6 +1183,10 @@ struct inode *__ext4_new_inode(struct mnt_idmap *idmap,
11831183

11841184
if (!(sbi->s_mount_state & EXT4_FC_REPLAY)) {
11851185
grp = ext4_get_group_info(sb, group);
1186+
if (!grp) {
1187+
err = -EFSCORRUPTED;
1188+
goto out;
1189+
}
11861190
down_read(&grp->alloc_sem); /*
11871191
* protect vs itable
11881192
* lazyinit
@@ -1526,7 +1530,7 @@ int ext4_init_inode_table(struct super_block *sb, ext4_group_t group,
15261530
}
15271531

15281532
gdp = ext4_get_group_desc(sb, group, &group_desc_bh);
1529-
if (!gdp)
1533+
if (!gdp || !grp)
15301534
goto out;
15311535

15321536
/*

fs/ext4/mballoc.c

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,8 @@ static int __mb_check_buddy(struct ext4_buddy *e4b, char *file,
745745
MB_CHECK_ASSERT(e4b->bd_info->bb_fragments == fragments);
746746

747747
grp = ext4_get_group_info(sb, e4b->bd_group);
748+
if (!grp)
749+
return NULL;
748750
list_for_each(cur, &grp->bb_prealloc_list) {
749751
ext4_group_t groupnr;
750752
struct ext4_prealloc_space *pa;
@@ -1060,9 +1062,9 @@ mb_set_largest_free_order(struct super_block *sb, struct ext4_group_info *grp)
10601062

10611063
static noinline_for_stack
10621064
void ext4_mb_generate_buddy(struct super_block *sb,
1063-
void *buddy, void *bitmap, ext4_group_t group)
1065+
void *buddy, void *bitmap, ext4_group_t group,
1066+
struct ext4_group_info *grp)
10641067
{
1065-
struct ext4_group_info *grp = ext4_get_group_info(sb, group);
10661068
struct ext4_sb_info *sbi = EXT4_SB(sb);
10671069
ext4_grpblk_t max = EXT4_CLUSTERS_PER_GROUP(sb);
10681070
ext4_grpblk_t i = 0;
@@ -1181,6 +1183,8 @@ static int ext4_mb_init_cache(struct page *page, char *incore, gfp_t gfp)
11811183
break;
11821184

11831185
grinfo = ext4_get_group_info(sb, group);
1186+
if (!grinfo)
1187+
continue;
11841188
/*
11851189
* If page is uptodate then we came here after online resize
11861190
* which added some new uninitialized group info structs, so
@@ -1246,6 +1250,10 @@ static int ext4_mb_init_cache(struct page *page, char *incore, gfp_t gfp)
12461250
group, page->index, i * blocksize);
12471251
trace_ext4_mb_buddy_bitmap_load(sb, group);
12481252
grinfo = ext4_get_group_info(sb, group);
1253+
if (!grinfo) {
1254+
err = -EFSCORRUPTED;
1255+
goto out;
1256+
}
12491257
grinfo->bb_fragments = 0;
12501258
memset(grinfo->bb_counters, 0,
12511259
sizeof(*grinfo->bb_counters) *
@@ -1256,7 +1264,7 @@ static int ext4_mb_init_cache(struct page *page, char *incore, gfp_t gfp)
12561264
ext4_lock_group(sb, group);
12571265
/* init the buddy */
12581266
memset(data, 0xff, blocksize);
1259-
ext4_mb_generate_buddy(sb, data, incore, group);
1267+
ext4_mb_generate_buddy(sb, data, incore, group, grinfo);
12601268
ext4_unlock_group(sb, group);
12611269
incore = NULL;
12621270
} else {
@@ -1370,6 +1378,9 @@ int ext4_mb_init_group(struct super_block *sb, ext4_group_t group, gfp_t gfp)
13701378
might_sleep();
13711379
mb_debug(sb, "init group %u\n", group);
13721380
this_grp = ext4_get_group_info(sb, group);
1381+
if (!this_grp)
1382+
return -EFSCORRUPTED;
1383+
13731384
/*
13741385
* This ensures that we don't reinit the buddy cache
13751386
* page which map to the group from which we are already
@@ -1444,6 +1455,8 @@ ext4_mb_load_buddy_gfp(struct super_block *sb, ext4_group_t group,
14441455

14451456
blocks_per_page = PAGE_SIZE / sb->s_blocksize;
14461457
grp = ext4_get_group_info(sb, group);
1458+
if (!grp)
1459+
return -EFSCORRUPTED;
14471460

14481461
e4b->bd_blkbits = sb->s_blocksize_bits;
14491462
e4b->bd_info = grp;
@@ -2159,6 +2172,8 @@ int ext4_mb_find_by_goal(struct ext4_allocation_context *ac,
21592172
struct ext4_group_info *grp = ext4_get_group_info(ac->ac_sb, group);
21602173
struct ext4_free_extent ex;
21612174

2175+
if (!grp)
2176+
return -EFSCORRUPTED;
21622177
if (!(ac->ac_flags & (EXT4_MB_HINT_TRY_GOAL | EXT4_MB_HINT_GOAL_ONLY)))
21632178
return 0;
21642179
if (grp->bb_free == 0)
@@ -2385,7 +2400,7 @@ static bool ext4_mb_good_group(struct ext4_allocation_context *ac,
23852400

23862401
BUG_ON(cr < 0 || cr >= 4);
23872402

2388-
if (unlikely(EXT4_MB_GRP_BBITMAP_CORRUPT(grp)))
2403+
if (unlikely(EXT4_MB_GRP_BBITMAP_CORRUPT(grp) || !grp))
23892404
return false;
23902405

23912406
free = grp->bb_free;
@@ -2454,6 +2469,8 @@ static int ext4_mb_good_group_nolock(struct ext4_allocation_context *ac,
24542469
ext4_grpblk_t free;
24552470
int ret = 0;
24562471

2472+
if (!grp)
2473+
return -EFSCORRUPTED;
24572474
if (sbi->s_mb_stats)
24582475
atomic64_inc(&sbi->s_bal_cX_groups_considered[ac->ac_criteria]);
24592476
if (should_lock) {
@@ -2534,7 +2551,7 @@ ext4_group_t ext4_mb_prefetch(struct super_block *sb, ext4_group_t group,
25342551
* prefetch once, so we avoid getblk() call, which can
25352552
* be expensive.
25362553
*/
2537-
if (!EXT4_MB_GRP_TEST_AND_SET_READ(grp) &&
2554+
if (gdp && grp && !EXT4_MB_GRP_TEST_AND_SET_READ(grp) &&
25382555
EXT4_MB_GRP_NEED_INIT(grp) &&
25392556
ext4_free_group_clusters(sb, gdp) > 0 &&
25402557
!(ext4_has_group_desc_csum(sb) &&
@@ -2578,7 +2595,7 @@ void ext4_mb_prefetch_fini(struct super_block *sb, ext4_group_t group,
25782595
gdp = ext4_get_group_desc(sb, group, NULL);
25792596
grp = ext4_get_group_info(sb, group);
25802597

2581-
if (EXT4_MB_GRP_NEED_INIT(grp) &&
2598+
if (grp && gdp && EXT4_MB_GRP_NEED_INIT(grp) &&
25822599
ext4_free_group_clusters(sb, gdp) > 0 &&
25832600
!(ext4_has_group_desc_csum(sb) &&
25842601
(gdp->bg_flags & cpu_to_le16(EXT4_BG_BLOCK_UNINIT)))) {
@@ -2837,6 +2854,8 @@ static int ext4_mb_seq_groups_show(struct seq_file *seq, void *v)
28372854
sizeof(struct ext4_group_info);
28382855

28392856
grinfo = ext4_get_group_info(sb, group);
2857+
if (!grinfo)
2858+
return 0;
28402859
/* Load the group info in memory only if not already loaded. */
28412860
if (unlikely(EXT4_MB_GRP_NEED_INIT(grinfo))) {
28422861
err = ext4_mb_load_buddy(sb, group, &e4b);
@@ -2847,7 +2866,7 @@ static int ext4_mb_seq_groups_show(struct seq_file *seq, void *v)
28472866
buddy_loaded = 1;
28482867
}
28492868

2850-
memcpy(&sg, ext4_get_group_info(sb, group), i);
2869+
memcpy(&sg, grinfo, i);
28512870

28522871
if (buddy_loaded)
28532872
ext4_mb_unload_buddy(&e4b);
@@ -3208,8 +3227,12 @@ static int ext4_mb_init_backend(struct super_block *sb)
32083227

32093228
err_freebuddy:
32103229
cachep = get_groupinfo_cache(sb->s_blocksize_bits);
3211-
while (i-- > 0)
3212-
kmem_cache_free(cachep, ext4_get_group_info(sb, i));
3230+
while (i-- > 0) {
3231+
struct ext4_group_info *grp = ext4_get_group_info(sb, i);
3232+
3233+
if (grp)
3234+
kmem_cache_free(cachep, grp);
3235+
}
32133236
i = sbi->s_group_info_size;
32143237
rcu_read_lock();
32153238
group_info = rcu_dereference(sbi->s_group_info);
@@ -3522,6 +3545,8 @@ int ext4_mb_release(struct super_block *sb)
35223545
for (i = 0; i < ngroups; i++) {
35233546
cond_resched();
35243547
grinfo = ext4_get_group_info(sb, i);
3548+
if (!grinfo)
3549+
continue;
35253550
mb_group_bb_bitmap_free(grinfo);
35263551
ext4_lock_group(sb, i);
35273552
count = ext4_mb_cleanup_pa(grinfo);
@@ -4606,6 +4631,8 @@ static void ext4_mb_generate_from_freelist(struct super_block *sb, void *bitmap,
46064631
struct ext4_free_data *entry;
46074632

46084633
grp = ext4_get_group_info(sb, group);
4634+
if (!grp)
4635+
return;
46094636
n = rb_first(&(grp->bb_free_root));
46104637

46114638
while (n) {
@@ -4633,6 +4660,9 @@ void ext4_mb_generate_from_pa(struct super_block *sb, void *bitmap,
46334660
int preallocated = 0;
46344661
int len;
46354662

4663+
if (!grp)
4664+
return;
4665+
46364666
/* all form of preallocation discards first load group,
46374667
* so the only competing code is preallocation use.
46384668
* we don't need any locking here
@@ -4869,6 +4899,8 @@ ext4_mb_new_inode_pa(struct ext4_allocation_context *ac)
48694899

48704900
ei = EXT4_I(ac->ac_inode);
48714901
grp = ext4_get_group_info(sb, ac->ac_b_ex.fe_group);
4902+
if (!grp)
4903+
return;
48724904

48734905
pa->pa_node_lock.inode_lock = &ei->i_prealloc_lock;
48744906
pa->pa_inode = ac->ac_inode;
@@ -4918,6 +4950,8 @@ ext4_mb_new_group_pa(struct ext4_allocation_context *ac)
49184950
atomic_add(pa->pa_free, &EXT4_SB(sb)->s_mb_preallocated);
49194951

49204952
grp = ext4_get_group_info(sb, ac->ac_b_ex.fe_group);
4953+
if (!grp)
4954+
return;
49214955
lg = ac->ac_lg;
49224956
BUG_ON(lg == NULL);
49234957

@@ -5043,6 +5077,8 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
50435077
int err;
50445078
int free = 0;
50455079

5080+
if (!grp)
5081+
return 0;
50465082
mb_debug(sb, "discard preallocation for group %u\n", group);
50475083
if (list_empty(&grp->bb_prealloc_list))
50485084
goto out_dbg;
@@ -5297,6 +5333,9 @@ static inline void ext4_mb_show_pa(struct super_block *sb)
52975333
struct ext4_prealloc_space *pa;
52985334
ext4_grpblk_t start;
52995335
struct list_head *cur;
5336+
5337+
if (!grp)
5338+
continue;
53005339
ext4_lock_group(sb, i);
53015340
list_for_each(cur, &grp->bb_prealloc_list) {
53025341
pa = list_entry(cur, struct ext4_prealloc_space,
@@ -6064,6 +6103,7 @@ static void ext4_mb_clear_bb(handle_t *handle, struct inode *inode,
60646103
struct buffer_head *bitmap_bh = NULL;
60656104
struct super_block *sb = inode->i_sb;
60666105
struct ext4_group_desc *gdp;
6106+
struct ext4_group_info *grp;
60676107
unsigned int overflow;
60686108
ext4_grpblk_t bit;
60696109
struct buffer_head *gd_bh;
@@ -6089,8 +6129,8 @@ static void ext4_mb_clear_bb(handle_t *handle, struct inode *inode,
60896129
overflow = 0;
60906130
ext4_get_group_no_and_offset(sb, block, &block_group, &bit);
60916131

6092-
if (unlikely(EXT4_MB_GRP_BBITMAP_CORRUPT(
6093-
ext4_get_group_info(sb, block_group))))
6132+
grp = ext4_get_group_info(sb, block_group);
6133+
if (unlikely(!grp || EXT4_MB_GRP_BBITMAP_CORRUPT(grp)))
60946134
return;
60956135

60966136
/*
@@ -6692,6 +6732,8 @@ int ext4_trim_fs(struct super_block *sb, struct fstrim_range *range)
66926732

66936733
for (group = first_group; group <= last_group; group++) {
66946734
grp = ext4_get_group_info(sb, group);
6735+
if (!grp)
6736+
continue;
66956737
/* We only do this if the grp has never been initialized */
66966738
if (unlikely(EXT4_MB_GRP_NEED_INIT(grp))) {
66976739
ret = ext4_mb_init_group(sb, group, GFP_NOFS);

fs/ext4/super.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,8 @@ void ext4_mark_group_bitmap_corrupted(struct super_block *sb,
10481048
struct ext4_group_desc *gdp = ext4_get_group_desc(sb, group, NULL);
10491049
int ret;
10501050

1051+
if (!grp || !gdp)
1052+
return;
10511053
if (flags & EXT4_GROUP_INFO_BBITMAP_CORRUPT) {
10521054
ret = ext4_test_and_set_bit(EXT4_GROUP_INFO_BBITMAP_CORRUPT_BIT,
10531055
&grp->bb_state);

0 commit comments

Comments
 (0)