Commit f2d5a944 authored by Anton Altaparmakov's avatar Anton Altaparmakov Committed by Linus Torvalds

Fix nasty 32-bit overflow bug in buffer i/o code.

On 32-bit architectures, the legacy buffer_head functions are not always
handling the sector number with the proper 64-bit types, and will thus
fail on 4TB+ disks.

Any code that uses __getblk() (and thus bread(), breadahead(),
sb_bread(), sb_breadahead(), sb_getblk()), and calls it using a 64-bit
block on a 32-bit arch (where "long" is 32-bit) causes an inifinite loop
in __getblk_slow() with an infinite stream of errors logged to dmesg
like this:

  __find_get_block_slow() failed. block=6740375944, b_blocknr=2445408648
  b_state=0x00000020, b_size=512
  device sda1 blocksize: 512

Note how in hex block is 0x191C1F988 and b_blocknr is 0x91C1F988 i.e. the
top 32-bits are missing (in this case the 0x1 at the top).

This is because grow_dev_page() is broken and has a 32-bit overflow due
to shifting the page index value (a pgoff_t - which is just 32 bits on
32-bit architectures) left-shifted as the block number.  But the top
bits to get lost as the pgoff_t is not type cast to sector_t / 64-bit
before the shift.

This patch fixes this issue by type casting "index" to sector_t before
doing the left shift.

Note this is not a theoretical bug but has been seen in the field on a
4TiB hard drive with logical sector size 512 bytes.

This patch has been verified to fix the infinite loop problem on 3.17-rc5
kernel using a 4TB disk image mounted using "-o loop".  Without this patch
doing a "find /nt" where /nt is an NTFS volume causes the inifinite loop
100% reproducibly whilst with the patch it works fine as expected.
Signed-off-by: default avatarAnton Altaparmakov <aia21@cantab.net>
Cc: stable@vger.kernel.org
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 0f33be00
...@@ -1022,7 +1022,8 @@ grow_dev_page(struct block_device *bdev, sector_t block, ...@@ -1022,7 +1022,8 @@ grow_dev_page(struct block_device *bdev, sector_t block,
bh = page_buffers(page); bh = page_buffers(page);
if (bh->b_size == size) { if (bh->b_size == size) {
end_block = init_page_buffers(page, bdev, end_block = init_page_buffers(page, bdev,
index << sizebits, size); (sector_t)index << sizebits,
size);
goto done; goto done;
} }
if (!try_to_free_buffers(page)) if (!try_to_free_buffers(page))
...@@ -1043,7 +1044,8 @@ grow_dev_page(struct block_device *bdev, sector_t block, ...@@ -1043,7 +1044,8 @@ grow_dev_page(struct block_device *bdev, sector_t block,
*/ */
spin_lock(&inode->i_mapping->private_lock); spin_lock(&inode->i_mapping->private_lock);
link_dev_buffers(page, bh); link_dev_buffers(page, bh);
end_block = init_page_buffers(page, bdev, index << sizebits, size); end_block = init_page_buffers(page, bdev, (sector_t)index << sizebits,
size);
spin_unlock(&inode->i_mapping->private_lock); spin_unlock(&inode->i_mapping->private_lock);
done: done:
ret = (block < end_block) ? 1 : -ENXIO; ret = (block < end_block) ? 1 : -ENXIO;
......
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