summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Thibault <samuel.thibault@ens-lyon.org>2021-08-17 00:05:17 +0200
committerSamuel Thibault <samuel.thibault@ens-lyon.org>2021-08-17 00:05:17 +0200
commit4a704a0ad95973249544f3f95e30e328e701a871 (patch)
tree861ef383f6de782b5f09c13d2d9d5d65836c03e1
parentf67a2a46931028753364ab6bcb6a43c12f8303b2 (diff)
block: Look out for disk sector number overflow
* linux/dev/drivers/block/ahci.c (ahci_do_port_request): Reject sectors beyond LBA48 or LBA28. * linux/dev/glue/block.c (check_rw_block): New function. (rdwr_partial, rdwr_full): Use check_rw_block to reject block number overflows. * linux/src/drivers/block/ide.c (do_rw_disk): Reject sectors beyond LBA28 or CHS.
-rw-r--r--linux/dev/drivers/block/ahci.c19
-rw-r--r--linux/dev/glue/block.c50
-rw-r--r--linux/src/drivers/block/ide.c12
3 files changed, 72 insertions, 9 deletions
diff --git a/linux/dev/drivers/block/ahci.c b/linux/dev/drivers/block/ahci.c
index ce2ac403..b8fd9dae 100644
--- a/linux/dev/drivers/block/ahci.c
+++ b/linux/dev/drivers/block/ahci.c
@@ -288,7 +288,7 @@ static void ahci_end_request(int uptodate)
}
/* Push the request to the controler port */
-static void ahci_do_port_request(struct port *port, unsigned long long sector, struct request *rq)
+static int ahci_do_port_request(struct port *port, unsigned long long sector, struct request *rq)
{
struct ahci_command *command = port->command;
struct ahci_cmd_tbl *prdtl = port->prdtl;
@@ -305,16 +305,25 @@ static void ahci_do_port_request(struct port *port, unsigned long long sector, s
fis_h2d = (void*) &prdtl[slot].cfis;
fis_h2d->fis_type = FIS_TYPE_REG_H2D;
fis_h2d->flags = 128;
- if (port->lba48)
+ if (port->lba48) {
+ if (sector >= 1ULL << 48) {
+ printk("sector %llu beyond LBA48\n", sector);
+ return -EOVERFLOW;
+ }
if (rq->cmd == READ)
fis_h2d->command = WIN_READDMA_EXT;
else
fis_h2d->command = WIN_WRITEDMA_EXT;
- else
+ } else {
+ if (sector >= 1ULL << 28) {
+ printk("sector %llu beyond LBA28\n", sector);
+ return -EOVERFLOW;
+ }
if (rq->cmd == READ)
fis_h2d->command = WIN_READDMA;
else
fis_h2d->command = WIN_WRITEDMA;
+ }
fis_h2d->device = 1<<6; /* LBA */
@@ -353,6 +362,7 @@ static void ahci_do_port_request(struct port *port, unsigned long long sector, s
writel(1 << slot, &port->ahci_port->ci);
/* TODO: IRQ timeout handler */
+ return 0;
}
/* Called by block core to push a request */
@@ -409,7 +419,8 @@ static void ahci_do_request() /* invoked with cli() */
}
/* Push this to the port */
- ahci_do_port_request(port, block, rq);
+ if (ahci_do_port_request(port, block, rq))
+ goto kill_rq;
return;
kill_rq:
diff --git a/linux/dev/glue/block.c b/linux/dev/glue/block.c
index a8cb9b3f..ee5ef7bc 100644
--- a/linux/dev/glue/block.c
+++ b/linux/dev/glue/block.c
@@ -442,6 +442,22 @@ enqueue_request (struct request *req)
sti ();
}
+int
+check_rw_block (int nr, struct buffer_head **bh)
+{
+ int i, bshift, bsize;
+ get_block_size (bh[0]->b_dev, &bsize, &bshift);
+ loff_t sectorl = bh[0]->b_blocknr << (bshift - 9);
+
+ for (i = 0; i < nr; i++)
+ {
+ sectorl += bh[i]->b_size >> 9;
+ unsigned long sector = sectorl;
+ if (sector != sectorl)
+ return -EOVERFLOW;
+ }
+}
+
/* Perform the I/O operation RW on the buffer list BH
containing NR buffers. */
void
@@ -506,11 +522,15 @@ rdwr_partial (int rw, kdev_t dev, loff_t *off,
long sect, nsect;
struct buffer_head bhead, *bh = &bhead;
struct gendisk *gd;
+ loff_t blkl;
memset (bh, 0, sizeof (struct buffer_head));
bh->b_state = 1 << BH_Lock;
bh->b_dev = dev;
- bh->b_blocknr = *off >> bshift;
+ blkl = *off >> bshift;
+ bh->b_blocknr = blkl;
+ if (bh->b_blocknr != blkl)
+ return -EOVERFLOW;
bh->b_size = BSIZE;
/* Check if this device has non even number of blocks. */
@@ -522,7 +542,9 @@ rdwr_partial (int rw, kdev_t dev, loff_t *off,
}
if (nsect > 0)
{
- sect = bh->b_blocknr << (bshift - 9);
+ loff_t sectl;
+ sectl = bh->b_blocknr << (bshift - 9);
+ sect = sectl;
assert ((nsect - sect) > 0);
if (nsect - sect < (BSIZE >> 9))
bh->b_size = (nsect - sect) << 9;
@@ -530,6 +552,9 @@ rdwr_partial (int rw, kdev_t dev, loff_t *off,
bh->b_data = alloc_buffer (bh->b_size);
if (! bh->b_data)
return -ENOMEM;
+ err = check_rw_block (1, &bh);
+ if (err)
+ goto out;
ll_rw_block (READ, 1, &bh, 0);
wait_on_buffer (bh);
if (buffer_uptodate (bh))
@@ -544,6 +569,9 @@ rdwr_partial (int rw, kdev_t dev, loff_t *off,
{
memcpy (bh->b_data + o, *buf, c);
bh->b_state = (1 << BH_Dirty) | (1 << BH_Lock);
+ err = check_rw_block (1, &bh);
+ if (err)
+ goto out;
ll_rw_block (WRITE, 1, &bh, 0);
wait_on_buffer (bh);
if (! buffer_uptodate (bh))
@@ -576,14 +604,18 @@ static int
rdwr_full (int rw, kdev_t dev, loff_t *off, char **buf, int *resid, int bshift)
{
int cc, err = 0, i, j, nb, nbuf;
- long blk;
+ loff_t blkl;
+ long blk, newblk;
struct buffer_head bhead[MAX_BUF], *bh, *bhp[MAX_BUF];
phys_addr_t pa;
assert ((*off & BMASK) == 0);
nbuf = *resid >> bshift;
- blk = *off >> bshift;
+ blkl = *off >> bshift;
+ blk = blkl;
+ if (blk != blkl)
+ return -EOVERFLOW;
for (i = nb = 0, bh = bhead; nb < nbuf; bh++)
{
memset (bh, 0, sizeof (*bh));
@@ -621,11 +653,19 @@ rdwr_full (int rw, kdev_t dev, loff_t *off, char **buf, int *resid, int bshift)
bh->b_size = cc;
bhp[i] = bh;
nb += cc >> bshift;
- blk += cc >> bshift;
+ newblk = blk + (cc >> bshift);
+ if (newblk < blk)
+ {
+ err = -EOVERFLOW;
+ break;
+ }
+ blk = newblk;
if (++i == MAX_BUF)
break;
}
if (! err)
+ err = check_rw_block (i, bhp);
+ if (! err)
{
assert (i > 0);
ll_rw_block (rw, i, bhp, 0);
diff --git a/linux/src/drivers/block/ide.c b/linux/src/drivers/block/ide.c
index 170e4e13..2d0fc77e 100644
--- a/linux/src/drivers/block/ide.c
+++ b/linux/src/drivers/block/ide.c
@@ -1475,6 +1475,11 @@ static inline void do_rw_disk (ide_drive_t *drive, struct request *rq, unsigned
#else /* !CONFIG_BLK_DEV_PROMISE */
if (drive->select.b.lba) {
#endif /* CONFIG_BLK_DEV_PROMISE */
+ if (block >= 1UL << 28) {
+ printk("block %lu beyond LBA28\n", block);
+ ide_end_request(0, hwif->hwgroup);
+ return;
+ }
#ifdef DEBUG
printk("%s: %sing: LBAsect=%ld, sectors=%ld, buffer=0x%08lx\n",
drive->name, (rq->cmd==READ)?"read":"writ",
@@ -1491,6 +1496,13 @@ static inline void do_rw_disk (ide_drive_t *drive, struct request *rq, unsigned
OUT_BYTE(sect,io_base+IDE_SECTOR_OFFSET);
head = track % drive->head;
cyl = track / drive->head;
+
+ if (cyl >= 1 << 16) {
+ printk("block %lu cylinder %u beyond CHS\n", block, cyl);
+ ide_end_request(0, hwif->hwgroup);
+ return;
+ }
+
OUT_BYTE(cyl,io_base+IDE_LCYL_OFFSET);
OUT_BYTE(cyl>>8,io_base+IDE_HCYL_OFFSET);
OUT_BYTE(head|drive->select.all,io_base+IDE_SELECT_OFFSET);