aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTheodore Ts'o <tytso@mit.edu>2010-01-22 17:40:42 -0500
committerTheodore Ts'o <tytso@mit.edu>2010-01-22 17:40:42 -0500
commit1f2acb6017d8528135ec3b01ab7cd2be6ea0630b (patch)
tree7263a5c1402237166cf425bc8f5336426159622c
parent15121c18a22ae483279f76dc9e554334b800d0f7 (diff)
ext4: Add block validity check when truncating indirect block mapped inodes
Add checks to ext4_free_branches() to make sure a block number found in an indirect block are valid before trying to free it. If a bad block number is found, stop freeing the indirect block immediately, since the file system is corrupt and we will need to run fsck anyway. This also avoids spamming the logs, and specifically avoids driver-level "attempt to access beyond end of device" errors obscure what is really going on. If you get *really*, *really*, *really* unlucky, without this patch, a supposed indirect block containing garbage might contain a reference to a primary block group descriptor, in which case ext4_free_branches() could end up zero'ing out a block group descriptor block, and if then one of the block bitmaps for a block group described by that bg descriptor block is not in memory, and is read in by ext4_read_block_bitmap(). This function calls ext4_valid_block_bitmap(), which assumes that bg_inode_table() was validated at mount time and hasn't been modified since. Since this assumption is no longer valid, it's possible for the value (ext4_inode_table(sb, desc) - group_first_block) to go negative, which will cause ext4_find_next_zero_bit() to trigger a kernel GPF. Addresses-Google-Bug: #2220436 Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
-rw-r--r--fs/ext4/ext4.h1
-rw-r--r--fs/ext4/inode.c39
-rw-r--r--fs/ext4/mballoc.c7
3 files changed, 35 insertions, 12 deletions
diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 602d5ad6f5e7..307ecd13a762 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -377,6 +377,7 @@ struct ext4_new_group_data {
377 */ 377 */
378#define EXT4_FREE_BLOCKS_METADATA 0x0001 378#define EXT4_FREE_BLOCKS_METADATA 0x0001
379#define EXT4_FREE_BLOCKS_FORGET 0x0002 379#define EXT4_FREE_BLOCKS_FORGET 0x0002
380#define EXT4_FREE_BLOCKS_VALIDATED 0x0004
380 381
381/* 382/*
382 * ioctl commands 383 * ioctl commands
diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index 2059c34ac4c8..3e8afd969236 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -4130,18 +4130,27 @@ no_top:
4130 * We release `count' blocks on disk, but (last - first) may be greater 4130 * We release `count' blocks on disk, but (last - first) may be greater
4131 * than `count' because there can be holes in there. 4131 * than `count' because there can be holes in there.
4132 */ 4132 */
4133static void ext4_clear_blocks(handle_t *handle, struct inode *inode, 4133static int ext4_clear_blocks(handle_t *handle, struct inode *inode,
4134 struct buffer_head *bh, 4134 struct buffer_head *bh,
4135 ext4_fsblk_t block_to_free, 4135 ext4_fsblk_t block_to_free,
4136 unsigned long count, __le32 *first, 4136 unsigned long count, __le32 *first,
4137 __le32 *last) 4137 __le32 *last)
4138{ 4138{
4139 __le32 *p; 4139 __le32 *p;
4140 int flags = EXT4_FREE_BLOCKS_FORGET; 4140 int flags = EXT4_FREE_BLOCKS_FORGET | EXT4_FREE_BLOCKS_VALIDATED;
4141 4141
4142 if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) 4142 if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode))
4143 flags |= EXT4_FREE_BLOCKS_METADATA; 4143 flags |= EXT4_FREE_BLOCKS_METADATA;
4144 4144
4145 if (!ext4_data_block_valid(EXT4_SB(inode->i_sb), block_to_free,
4146 count)) {
4147 ext4_error(inode->i_sb, __func__, "inode #%lu: "
4148 "attempt to clear blocks %llu len %lu, invalid",
4149 inode->i_ino, (unsigned long long) block_to_free,
4150 count);
4151 return 1;
4152 }
4153
4145 if (try_to_extend_transaction(handle, inode)) { 4154 if (try_to_extend_transaction(handle, inode)) {
4146 if (bh) { 4155 if (bh) {
4147 BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata"); 4156 BUFFER_TRACE(bh, "call ext4_handle_dirty_metadata");
@@ -4160,6 +4169,7 @@ static void ext4_clear_blocks(handle_t *handle, struct inode *inode,
4160 *p = 0; 4169 *p = 0;
4161 4170
4162 ext4_free_blocks(handle, inode, 0, block_to_free, count, flags); 4171 ext4_free_blocks(handle, inode, 0, block_to_free, count, flags);
4172 return 0;
4163} 4173}
4164 4174
4165/** 4175/**
@@ -4215,9 +4225,10 @@ static void ext4_free_data(handle_t *handle, struct inode *inode,
4215 } else if (nr == block_to_free + count) { 4225 } else if (nr == block_to_free + count) {
4216 count++; 4226 count++;
4217 } else { 4227 } else {
4218 ext4_clear_blocks(handle, inode, this_bh, 4228 if (ext4_clear_blocks(handle, inode, this_bh,
4219 block_to_free, 4229 block_to_free, count,
4220 count, block_to_free_p, p); 4230 block_to_free_p, p))
4231 break;
4221 block_to_free = nr; 4232 block_to_free = nr;
4222 block_to_free_p = p; 4233 block_to_free_p = p;
4223 count = 1; 4234 count = 1;
@@ -4281,6 +4292,16 @@ static void ext4_free_branches(handle_t *handle, struct inode *inode,
4281 if (!nr) 4292 if (!nr)
4282 continue; /* A hole */ 4293 continue; /* A hole */
4283 4294
4295 if (!ext4_data_block_valid(EXT4_SB(inode->i_sb),
4296 nr, 1)) {
4297 ext4_error(inode->i_sb, __func__,
4298 "indirect mapped block in inode "
4299 "#%lu invalid (level %d, blk #%lu)",
4300 inode->i_ino, depth,
4301 (unsigned long) nr);
4302 break;
4303 }
4304
4284 /* Go read the buffer for the next level down */ 4305 /* Go read the buffer for the next level down */
4285 bh = sb_bread(inode->i_sb, nr); 4306 bh = sb_bread(inode->i_sb, nr);
4286 4307
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index d34afad3e137..d129c1039f1d 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -4476,10 +4476,11 @@ void ext4_free_blocks(handle_t *handle, struct inode *inode,
4476 4476
4477 sbi = EXT4_SB(sb); 4477 sbi = EXT4_SB(sb);
4478 es = EXT4_SB(sb)->s_es; 4478 es = EXT4_SB(sb)->s_es;
4479 if (!ext4_data_block_valid(sbi, block, count)) { 4479 if (!(flags & EXT4_FREE_BLOCKS_VALIDATED) &&
4480 !ext4_data_block_valid(sbi, block, count)) {
4480 ext4_error(sb, __func__, 4481 ext4_error(sb, __func__,
4481 "Freeing blocks not in datazone - " 4482 "Freeing blocks not in datazone - "
4482 "block = %llu, count = %lu", block, count); 4483 "block = %llu, count = %lu", block, count);
4483 goto error_return; 4484 goto error_return;
4484 } 4485 }
4485 4486