Patchwork Kernel 3.3.8 breaks accidental ext3 mount of extended partition

login
register
mail settings
Submitter Jeff Moyer
Date June 19, 2012, 5:43 p.m.
Message ID <x49ehpbjiyp.fsf@segfault.boston.devel.redhat.com>
Download mbox | patch
Permalink /patch/165818/
State New
Headers show

Comments

Jeff Moyer - June 19, 2012, 5:43 p.m.
Torsten Hilbrich <torsten.hilbrich@secunet.com> writes:

> The system where I reproduced the problem upstream is an amd64 based
> ubuntu 12.04 installation. I used both v3.3.8 and v3.4 for reproducing.
>
> The partition layout is the following:
>
> ======================================================================
>
> Disk /dev/sda: 120.0 GB, 120034123776 bytes
> 255 heads, 63 sectors/track, 14593 cylinders, total 234441648 sectors
> Units = sectors of 1 * 512 = 512 bytes
> Sector size (logical/physical): 512 bytes / 512 bytes
> I/O size (minimum/optimal): 512 bytes / 512 bytes
> Disk identifier: 0x1669c708
>
>    Device Boot      Start         End      Blocks   Id  System
> /dev/sda1   *          63    86285114    43142526   83  Linux
> /dev/sda2       216797175   234436544     8819685   82  Linux swap / Solaris
> /dev/sda3        86285115    87088364      401625   83  Linux
> /dev/sda4        87088426   216797174    64854374+   5  Extended
> /dev/sda5        87088428    91104614     2008093+  83  Linux
> /dev/sda6        91104678   216797174    62846248+  8e  Linux LVM
>
> Partition table entries are not in disk order

OK, got it to reproduce, thanks for the info.  The attached patch fixed
the problem for me.

Note, though, that the patch doesn't make sense to me.  blkdev_max_block
returns i_size_read(blkdev_inode) >> block_size.  This should be the
*size* of the block device, not the max block.  The code in
fs/block_device.c uses the return value in two different ways!

static int
blkdev_get_block(struct inode *inode, sector_t iblock,
                struct buffer_head *bh, int create)
{
        if (iblock >= blkdev_max_block(I_BDEV(inode))) {

Here, the return value from blkdev_max_block is interpreted as the size
of the device, so actually max_block + 1.

static int
blkdev_get_blocks(struct inode *inode, sector_t iblock,
                struct buffer_head *bh, int create)
{
        sector_t end_block = blkdev_max_block(I_BDEV(inode));
        unsigned long max_blocks = bh->b_size >> inode->i_blkbits;

        if ((iblock + max_blocks) > end_block) {

Here, the return value is treated as the maximum addressable block
number.  Given the fact that I had to modify init_page_buffers to treat
the return value from blkdev_max_block as the maximum addressable block,
I clearly have something wrong with my logic.  Nick, Jens, care to set
my head straight?

Cheers,
Jeff

--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Torsten Hilbrich - June 20, 2012, 6:14 a.m.
Am 19.06.2012 19:43, schrieb Jeff Moyer:

[...]

> diff --git a/fs/buffer.c b/fs/buffer.c
> index 838a9cf..769b30b 100644
> --- a/fs/buffer.c
> +++ b/fs/buffer.c
> @@ -930,7 +930,7 @@ init_page_buffers(struct page *page, struct block_device *bdev,
>  			bh->b_blocknr = block;
>  			if (uptodate)
>  				set_buffer_uptodate(bh);
> -			if (block < end_block)
> +			if (block <= end_block)
>  				set_buffer_mapped(bh);
>  		}
>  		block++;

I can confirm that this patch fixes the problem for my test case too.
Here is the kernel output when performing the mount operation:

attempt to access beyond end of device
sda4: rw=0, want=4, limit=2
EXT3-fs (sda4): error: unable to read superblock

	Torsten
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Jan Kara - June 22, 2012, 12:12 a.m.
On Tue 19-06-12 13:43:26, Jeff Moyer wrote:
> Torsten Hilbrich <torsten.hilbrich@secunet.com> writes:
> 
> > The system where I reproduced the problem upstream is an amd64 based
> > ubuntu 12.04 installation. I used both v3.3.8 and v3.4 for reproducing.
> >
> > The partition layout is the following:
> >
> > ======================================================================
> >
> > Disk /dev/sda: 120.0 GB, 120034123776 bytes
> > 255 heads, 63 sectors/track, 14593 cylinders, total 234441648 sectors
> > Units = sectors of 1 * 512 = 512 bytes
> > Sector size (logical/physical): 512 bytes / 512 bytes
> > I/O size (minimum/optimal): 512 bytes / 512 bytes
> > Disk identifier: 0x1669c708
> >
> >    Device Boot      Start         End      Blocks   Id  System
> > /dev/sda1   *          63    86285114    43142526   83  Linux
> > /dev/sda2       216797175   234436544     8819685   82  Linux swap / Solaris
> > /dev/sda3        86285115    87088364      401625   83  Linux
> > /dev/sda4        87088426   216797174    64854374+   5  Extended
> > /dev/sda5        87088428    91104614     2008093+  83  Linux
> > /dev/sda6        91104678   216797174    62846248+  8e  Linux LVM
> >
> > Partition table entries are not in disk order
> 
> OK, got it to reproduce, thanks for the info.  The attached patch fixed
> the problem for me.
> 
> Note, though, that the patch doesn't make sense to me.  blkdev_max_block
> returns i_size_read(blkdev_inode) >> block_size.  This should be the
> *size* of the block device, not the max block.  The code in
> fs/block_device.c uses the return value in two different ways!
> 
> static int
> blkdev_get_block(struct inode *inode, sector_t iblock,
>                 struct buffer_head *bh, int create)
> {
>         if (iblock >= blkdev_max_block(I_BDEV(inode))) {
> 
> Here, the return value from blkdev_max_block is interpreted as the size
> of the device, so actually max_block + 1.
> 
> static int
> blkdev_get_blocks(struct inode *inode, sector_t iblock,
>                 struct buffer_head *bh, int create)
> {
>         sector_t end_block = blkdev_max_block(I_BDEV(inode));
>         unsigned long max_blocks = bh->b_size >> inode->i_blkbits;
> 
>         if ((iblock + max_blocks) > end_block) {
> 
> Here, the return value is treated as the maximum addressable block
> number.  Given the fact that I had to modify init_page_buffers to treat
> the return value from blkdev_max_block as the maximum addressable block,
> I clearly have something wrong with my logic.  Nick, Jens, care to set
> my head straight?
  I think it can have something to do with the fact that the partition size
is not a multiple of 4k (i.e. expected block size)?

  BTW: blkdev_max_block() is a terrible name for something that intends to
return size in blocks...

								Honza
Jeff Moyer - June 22, 2012, 12:33 p.m.
Jan Kara <jack@suse.cz> writes:

>   I think it can have something to do with the fact that the partition size
> is not a multiple of 4k (i.e. expected block size)?

Ahh, yes.  Except that ext3 sets the blocksize to 1k by default.
However, I still can't explain why we don't get an error attempting to
access beyond the end of the device.  I'll keep digging on that.

>   BTW: blkdev_max_block() is a terrible name for something that intends to
> return size in blocks...

Fully agreed here, and I'll fix that up when I get my head completely
wrapped around this one.

Thanks for taking a look, Jan!

-Jeff
--
To unsubscribe from this list: send the line "unsubscribe linux-ext4" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Patch

diff --git a/fs/buffer.c b/fs/buffer.c
index 838a9cf..769b30b 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -930,7 +930,7 @@  init_page_buffers(struct page *page, struct block_device *bdev,
 			bh->b_blocknr = block;
 			if (uptodate)
 				set_buffer_uptodate(bh);
-			if (block < end_block)
+			if (block <= end_block)
 				set_buffer_mapped(bh);
 		}
 		block++;