aboutsummaryrefslogtreecommitdiffstats
path: root/fs/ext4/inode.c
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 /fs/ext4/inode.c
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>
Diffstat (limited to 'fs/ext4/inode.c')
-rw-r--r--fs/ext4/inode.c39
1 files changed, 30 insertions, 9 deletions
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