1 ext4: fix potential use after free during resize
3 From: Dmitry Monakhov <dmonakhov@openvz.org>
5 We need some sort of synchronization while updating ->s_group_desc
6 because there are a lot of users which can access old ->s_group_desc
7 array after it was released. It is reasonable to use lightweight
8 seqcount_t here instead of RCU.
10 Signed-off-by: Dmitry Monakhov <dmonakhov@openvz.org>
11 Signed-off-by: Theodore Ts'o <tytso@mit.edu>
13 fs/ext4/balloc.c | 14 ++++++++++----
15 fs/ext4/resize.c | 4 ++++
17 4 files changed, 16 insertions(+), 4 deletions(-)
19 diff --git a/fs/ext4/balloc.c b/fs/ext4/balloc.c
20 index 83a6f49..4746907 100644
21 --- a/fs/ext4/balloc.c
22 +++ b/fs/ext4/balloc.c
23 @@ -280,8 +280,10 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,
25 unsigned int group_desc;
28 ext4_group_t ngroups = ext4_get_groups_count(sb);
29 struct ext4_group_desc *desc;
30 + struct buffer_head *gd_bh;
31 struct ext4_sb_info *sbi = EXT4_SB(sb);
33 if (block_group >= ngroups) {
34 @@ -293,7 +295,11 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,
36 group_desc = block_group >> EXT4_DESC_PER_BLOCK_BITS(sb);
37 offset = block_group & (EXT4_DESC_PER_BLOCK(sb) - 1);
38 - if (!sbi->s_group_desc[group_desc]) {
40 + seq = read_seqcount_begin(&sbi->s_group_desc_seq);
41 + gd_bh = sbi->s_group_desc[group_desc];
42 + } while (unlikely(read_seqcount_retry(&sbi->s_group_desc_seq, seq)));
44 ext4_error(sb, "Group descriptor not loaded - "
45 "block_group = %u, group_desc = %u, desc = %u",
46 block_group, group_desc, offset);
47 @@ -301,10 +307,10 @@ struct ext4_group_desc * ext4_get_group_desc(struct super_block *sb,
50 desc = (struct ext4_group_desc *)(
51 - (__u8 *)sbi->s_group_desc[group_desc]->b_data +
52 - offset * EXT4_DESC_SIZE(sb));
53 + (__u8 *)gd_bh->b_data + offset * EXT4_DESC_SIZE(sb));
55 - *bh = sbi->s_group_desc[group_desc];
61 diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
62 index 4186ec8..84255c0 100644
65 @@ -1190,6 +1190,7 @@ struct ext4_sb_info {
66 struct buffer_head * s_sbh; /* Buffer containing the super block */
67 struct ext4_super_block *s_es; /* Pointer to the super block in the buffer */
68 struct buffer_head **s_group_desc;
69 + seqcount_t s_group_desc_seq; /* protects s_group_desc updates */
70 unsigned int s_mount_opt;
71 unsigned int s_mount_opt2;
72 unsigned int s_mount_flags;
73 diff --git a/fs/ext4/resize.c b/fs/ext4/resize.c
74 index bf76f40..25e822d 100644
75 --- a/fs/ext4/resize.c
76 +++ b/fs/ext4/resize.c
77 @@ -854,8 +854,10 @@ static int add_new_gdb(handle_t *handle, struct inode *inode,
78 memcpy(n_group_desc, o_group_desc,
79 EXT4_SB(sb)->s_gdb_count * sizeof(struct buffer_head *));
80 n_group_desc[gdb_num] = gdb_bh;
81 + write_seqcount_begin(&EXT4_SB(sb)->s_group_desc_seq);
82 EXT4_SB(sb)->s_group_desc = n_group_desc;
83 EXT4_SB(sb)->s_gdb_count++;
84 + write_seqcount_end(&EXT4_SB(sb)->s_group_desc_seq);
87 le16_add_cpu(&es->s_reserved_gdt_blocks, -1);
88 @@ -907,8 +909,10 @@ static int add_new_gdb_meta_bg(struct super_block *sb,
89 memcpy(n_group_desc, o_group_desc,
90 EXT4_SB(sb)->s_gdb_count * sizeof(struct buffer_head *));
91 n_group_desc[gdb_num] = gdb_bh;
92 + write_seqcount_begin(&EXT4_SB(sb)->s_group_desc_seq);
93 EXT4_SB(sb)->s_group_desc = n_group_desc;
94 EXT4_SB(sb)->s_gdb_count++;
95 + write_seqcount_end(&EXT4_SB(sb)->s_group_desc_seq);
97 BUFFER_TRACE(gdb_bh, "get_write_access");
98 err = ext4_journal_get_write_access(handle, gdb_bh);
99 diff --git a/fs/ext4/super.c b/fs/ext4/super.c
100 index a22543d..6dbd05c 100644
101 --- a/fs/ext4/super.c
102 +++ b/fs/ext4/super.c
103 @@ -3871,6 +3871,7 @@ static int ext4_fill_super(struct super_block *sb, void *data, int silent)
104 (EXT4_MAX_BLOCK_FILE_PHYS / EXT4_BLOCKS_PER_GROUP(sb)));
105 db_count = (sbi->s_groups_count + EXT4_DESC_PER_BLOCK(sb) - 1) /
106 EXT4_DESC_PER_BLOCK(sb);
107 + seqcount_init(&sbi->s_group_desc_seq);
108 sbi->s_group_desc = ext4_kvmalloc(db_count *
109 sizeof(struct buffer_head *),