Skip to content

Commit

Permalink
blk: avoid divide-by-zero with zero discard granularity
Browse files Browse the repository at this point in the history
Commit 8dd2cb7 ("block: discard granularity might not be power of
2") changed a couple of 'binary and' operations into modulus operations.
Which turned the harmless case of a zero discard_granularity into a
possible divide-by-zero.

The code also had a much more subtle bug: it was doing the modulus of a
value in bytes using 'sector_t'.  That was always conceptually wrong,
but didn't actually matter back when the code assumed a power-of-two
granularity: we only looked at the low bits anyway.

But with potentially arbitrary sector numbers, using a 'sector_t' to
express bytes is very very wrong: depending on configuration it limits
the starting offset of the device to just 32 bits, and any overflow
would result in a wrong value if the modulus wasn't a power-of-two.

So re-write the code to not only protect against the divide-by-zero, but
to do the starting sector arithmetic in sectors, and using the proper
types.

[ For any mathematicians out there: it also looks monumentally stupid to
  do the 'modulo granularity' operation *twice*, never mind having a "+
  granularity" in the second modulus op.

  But that's the easiest way to avoid negative values or overflow, and
  it is how the original code was done. ]

Reported-by: Ingo Molnar <mingo@kernel.org>
Reported-by: Doug Anderson <dianders@chromium.org>
Cc: Neil Brown <neilb@suse.de>
Cc: Shaohua Li <shli@fusionio.com>
Acked-by: Jens Axboe <axboe@kernel.dk>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
  • Loading branch information
torvalds committed Dec 19, 2012
1 parent 752451f commit 5977107
Showing 1 changed file with 15 additions and 4 deletions.
19 changes: 15 additions & 4 deletions include/linux/blkdev.h
Expand Up @@ -1188,14 +1188,25 @@ static inline int queue_discard_alignment(struct request_queue *q)

static inline int queue_limit_discard_alignment(struct queue_limits *lim, sector_t sector)
{
sector_t alignment = sector << 9;
alignment = sector_div(alignment, lim->discard_granularity);
unsigned int alignment, granularity, offset;

if (!lim->max_discard_sectors)
return 0;

alignment = lim->discard_granularity + lim->discard_alignment - alignment;
return sector_div(alignment, lim->discard_granularity);
/* Why are these in bytes, not sectors? */
alignment = lim->discard_alignment >> 9;
granularity = lim->discard_granularity >> 9;
if (!granularity)
return 0;

/* Offset of the partition start in 'granularity' sectors */
offset = sector_div(sector, granularity);

/* And why do we do this modulus *again* in blkdev_issue_discard()? */
offset = (granularity + alignment - offset) % granularity;

/* Turn it back into bytes, gaah */
return offset << 9;
}

static inline int bdev_discard_alignment(struct block_device *bdev)
Expand Down

0 comments on commit 5977107

Please sign in to comment.