Commit 9a3e1a96 authored by Andrew Morton's avatar Andrew Morton Committed by Linus Torvalds

[PATCH] Fix signed use of i_blocks in ext3 truncate

Patch from "Stephen C. Tweedie" <sct@redhat.com>

Fix "h_buffer_credits<0" assert failure during truncate.

The bug occurs when the "i_blocks" count in the file's inode overflows
past 2^31.  That works fine most of the time, because i_blocks is an
unsigned long, and should go up to 2^32; but there's a place in truncate
where ext3 calculates the size of the next transaction chunk for the
delete, and that mistakenly uses a signed long instead.  Because the
huge i_blocks gets cast to a negative value, ext3 does not reserve
enough credits for the transaction and the above error results.

This is usually only possible on filesystems corrupted for other
reasons, but it is reproducible if you create a single, non-sparse file
larger than 1TB on ext3 and then try to delete it.
parent 4f1cb3ff
...@@ -99,6 +99,34 @@ int ext3_forget(handle_t *handle, int is_metadata, ...@@ -99,6 +99,34 @@ int ext3_forget(handle_t *handle, int is_metadata,
return err; return err;
} }
/*
* Work out how many blocks we need to progress with the next chunk of a
* truncate transaction.
*/
static unsigned long blocks_for_truncate(struct inode *inode)
{
unsigned long needed;
needed = inode->i_blocks >> (inode->i_sb->s_blocksize_bits - 9);
/* Give ourselves just enough room to cope with inodes in which
* i_blocks is corrupt: we've seen disk corruptions in the past
* which resulted in random data in an inode which looked enough
* like a regular file for ext3 to try to delete it. Things
* will go a bit crazy if that happens, but at least we should
* try not to panic the whole kernel. */
if (needed < 2)
needed = 2;
/* But we need to bound the transaction so we don't overflow the
* journal. */
if (needed > EXT3_MAX_TRANS_DATA)
needed = EXT3_MAX_TRANS_DATA;
return EXT3_DATA_TRANS_BLOCKS + needed;
}
/* /*
* Truncate transactions can be complex and absolutely huge. So we need to * Truncate transactions can be complex and absolutely huge. So we need to
* be able to restart the transaction at a conventient checkpoint to make * be able to restart the transaction at a conventient checkpoint to make
...@@ -112,14 +140,9 @@ int ext3_forget(handle_t *handle, int is_metadata, ...@@ -112,14 +140,9 @@ int ext3_forget(handle_t *handle, int is_metadata,
static handle_t *start_transaction(struct inode *inode) static handle_t *start_transaction(struct inode *inode)
{ {
long needed;
handle_t *result; handle_t *result;
needed = inode->i_blocks; result = ext3_journal_start(inode, blocks_for_truncate(inode));
if (needed > EXT3_MAX_TRANS_DATA)
needed = EXT3_MAX_TRANS_DATA;
result = ext3_journal_start(inode, EXT3_DATA_TRANS_BLOCKS + needed);
if (!IS_ERR(result)) if (!IS_ERR(result))
return result; return result;
...@@ -135,14 +158,9 @@ static handle_t *start_transaction(struct inode *inode) ...@@ -135,14 +158,9 @@ static handle_t *start_transaction(struct inode *inode)
*/ */
static int try_to_extend_transaction(handle_t *handle, struct inode *inode) static int try_to_extend_transaction(handle_t *handle, struct inode *inode)
{ {
long needed;
if (handle->h_buffer_credits > EXT3_RESERVE_TRANS_BLOCKS) if (handle->h_buffer_credits > EXT3_RESERVE_TRANS_BLOCKS)
return 0; return 0;
needed = inode->i_blocks; if (!ext3_journal_extend(handle, blocks_for_truncate(inode)))
if (needed > EXT3_MAX_TRANS_DATA)
needed = EXT3_MAX_TRANS_DATA;
if (!ext3_journal_extend(handle, EXT3_RESERVE_TRANS_BLOCKS + needed))
return 0; return 0;
return 1; return 1;
} }
...@@ -154,11 +172,8 @@ static int try_to_extend_transaction(handle_t *handle, struct inode *inode) ...@@ -154,11 +172,8 @@ static int try_to_extend_transaction(handle_t *handle, struct inode *inode)
*/ */
static int ext3_journal_test_restart(handle_t *handle, struct inode *inode) static int ext3_journal_test_restart(handle_t *handle, struct inode *inode)
{ {
long needed = inode->i_blocks;
if (needed > EXT3_MAX_TRANS_DATA)
needed = EXT3_MAX_TRANS_DATA;
jbd_debug(2, "restarting handle %p\n", handle); jbd_debug(2, "restarting handle %p\n", handle);
return ext3_journal_restart(handle, EXT3_DATA_TRANS_BLOCKS + needed); return ext3_journal_restart(handle, blocks_for_truncate(inode));
} }
/* /*
......
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
* indirection blocks, the group and superblock summaries, and the data * indirection blocks, the group and superblock summaries, and the data
* block to complete the transaction. */ * block to complete the transaction. */
#define EXT3_SINGLEDATA_TRANS_BLOCKS 8 #define EXT3_SINGLEDATA_TRANS_BLOCKS 8U
/* Extended attributes may touch two data buffers, two bitmap buffers, /* Extended attributes may touch two data buffers, two bitmap buffers,
* and two group and summaries. */ * and two group and summaries. */
...@@ -58,7 +58,7 @@ extern int ext3_writepage_trans_blocks(struct inode *inode); ...@@ -58,7 +58,7 @@ extern int ext3_writepage_trans_blocks(struct inode *inode);
* start off at the maximum transaction size and grow the transaction * start off at the maximum transaction size and grow the transaction
* optimistically as we go. */ * optimistically as we go. */
#define EXT3_MAX_TRANS_DATA 64 #define EXT3_MAX_TRANS_DATA 64U
/* We break up a large truncate or write transaction once the handle's /* We break up a large truncate or write transaction once the handle's
* buffer credits gets this low, we need either to extend the * buffer credits gets this low, we need either to extend the
...@@ -67,7 +67,7 @@ extern int ext3_writepage_trans_blocks(struct inode *inode); ...@@ -67,7 +67,7 @@ extern int ext3_writepage_trans_blocks(struct inode *inode);
* one block, plus two quota updates. Quota allocations are not * one block, plus two quota updates. Quota allocations are not
* needed. */ * needed. */
#define EXT3_RESERVE_TRANS_BLOCKS 12 #define EXT3_RESERVE_TRANS_BLOCKS 12U
#define EXT3_INDEX_EXTRA_TRANS_BLOCKS 8 #define EXT3_INDEX_EXTRA_TRANS_BLOCKS 8
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment