aboutsummaryrefslogtreecommitdiffstats
path: root/fs/buffer.c
diff options
context:
space:
mode:
authorAnton Altaparmakov <aia21@cam.ac.uk>2014-09-21 20:53:03 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2014-09-22 11:41:16 -0400
commitf2d5a94436cc7cc0221b9a81bba2276a25187dd3 (patch)
tree691709559c324da97da76a958308c1b9546f3009 /fs/buffer.c
parent0f33be009b89d2268e94194dc4fd01a7851b6d51 (diff)
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: Anton Altaparmakov <aia21@cantab.net> Cc: stable@vger.kernel.org Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'fs/buffer.c')
-rw-r--r--fs/buffer.c6
1 files changed, 4 insertions, 2 deletions
diff --git a/fs/buffer.c b/fs/buffer.c
index 8f05111bbb8b..3588a80854b2 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -1022,7 +1022,8 @@ grow_dev_page(struct block_device *bdev, sector_t block,
1022 bh = page_buffers(page); 1022 bh = page_buffers(page);
1023 if (bh->b_size == size) { 1023 if (bh->b_size == size) {
1024 end_block = init_page_buffers(page, bdev, 1024 end_block = init_page_buffers(page, bdev,
1025 index << sizebits, size); 1025 (sector_t)index << sizebits,
1026 size);
1026 goto done; 1027 goto done;
1027 } 1028 }
1028 if (!try_to_free_buffers(page)) 1029 if (!try_to_free_buffers(page))
@@ -1043,7 +1044,8 @@ grow_dev_page(struct block_device *bdev, sector_t block,
1043 */ 1044 */
1044 spin_lock(&inode->i_mapping->private_lock); 1045 spin_lock(&inode->i_mapping->private_lock);
1045 link_dev_buffers(page, bh); 1046 link_dev_buffers(page, bh);
1046 end_block = init_page_buffers(page, bdev, index << sizebits, size); 1047 end_block = init_page_buffers(page, bdev, (sector_t)index << sizebits,
1048 size);
1047 spin_unlock(&inode->i_mapping->private_lock); 1049 spin_unlock(&inode->i_mapping->private_lock);
1048done: 1050done:
1049 ret = (block < end_block) ? 1 : -ENXIO; 1051 ret = (block < end_block) ? 1 : -ENXIO;