diff options
| author | Mikulas Patocka <mpatocka@redhat.com> | 2013-09-13 17:42:24 -0400 |
|---|---|---|
| committer | Mike Snitzer <snitzer@redhat.com> | 2013-09-18 14:41:06 -0400 |
| commit | bbf3f8cbdc139860a14c4fc2bb25432427045aaa (patch) | |
| tree | 49c29342acc975d30d5d7399c9ba2ceacd718a01 | |
| parent | cc9d3c382bc1674884c2e5e468d51230a9503dee (diff) | |
dm stats: fix possible counter corruption on 32-bit systems
There was a deliberate race condition in dm_stat_for_entry() to avoid the
overhead of disabling and enabling interrupts. The race could result in
some events not being counted on 64-bit architectures.
However, on 32-bit architectures, operations on long long variables are
not atomic, so the race condition could cause the counter to jump by 2^32.
Such jumps could be disruptive, so we need to do proper locking on 32-bit
architectures.
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: Alasdair G. Kergon <agk@redhat.com>
Signed-off-by: Mike Snitzer <snitzer@redhat.com>
| -rw-r--r-- | drivers/md/dm-stats.c | 23 |
1 files changed, 17 insertions, 6 deletions
diff --git a/drivers/md/dm-stats.c b/drivers/md/dm-stats.c index 8ae31e8d3d64..3d404c1371ed 100644 --- a/drivers/md/dm-stats.c +++ b/drivers/md/dm-stats.c | |||
| @@ -451,19 +451,26 @@ static void dm_stat_for_entry(struct dm_stat *s, size_t entry, | |||
| 451 | struct dm_stat_percpu *p; | 451 | struct dm_stat_percpu *p; |
| 452 | 452 | ||
| 453 | /* | 453 | /* |
| 454 | * For strict correctness we should use local_irq_disable/enable | 454 | * For strict correctness we should use local_irq_save/restore |
| 455 | * instead of preempt_disable/enable. | 455 | * instead of preempt_disable/enable. |
| 456 | * | 456 | * |
| 457 | * This is racy if the driver finishes bios from non-interrupt | 457 | * preempt_disable/enable is racy if the driver finishes bios |
| 458 | * context as well as from interrupt context or from more different | 458 | * from non-interrupt context as well as from interrupt context |
| 459 | * interrupts. | 459 | * or from more different interrupts. |
| 460 | * | 460 | * |
| 461 | * However, the race only results in not counting some events, | 461 | * On 64-bit architectures the race only results in not counting some |
| 462 | * so it is acceptable. | 462 | * events, so it is acceptable. On 32-bit architectures the race could |
| 463 | * cause the counter going off by 2^32, so we need to do proper locking | ||
| 464 | * there. | ||
| 463 | * | 465 | * |
| 464 | * part_stat_lock()/part_stat_unlock() have this race too. | 466 | * part_stat_lock()/part_stat_unlock() have this race too. |
| 465 | */ | 467 | */ |
| 468 | #if BITS_PER_LONG == 32 | ||
| 469 | unsigned long flags; | ||
| 470 | local_irq_save(flags); | ||
| 471 | #else | ||
| 466 | preempt_disable(); | 472 | preempt_disable(); |
| 473 | #endif | ||
| 467 | p = &s->stat_percpu[smp_processor_id()][entry]; | 474 | p = &s->stat_percpu[smp_processor_id()][entry]; |
| 468 | 475 | ||
| 469 | if (!end) { | 476 | if (!end) { |
| @@ -478,7 +485,11 @@ static void dm_stat_for_entry(struct dm_stat *s, size_t entry, | |||
| 478 | p->ticks[idx] += duration; | 485 | p->ticks[idx] += duration; |
| 479 | } | 486 | } |
| 480 | 487 | ||
| 488 | #if BITS_PER_LONG == 32 | ||
| 489 | local_irq_restore(flags); | ||
| 490 | #else | ||
| 481 | preempt_enable(); | 491 | preempt_enable(); |
| 492 | #endif | ||
| 482 | } | 493 | } |
| 483 | 494 | ||
| 484 | static void __dm_stat_bio(struct dm_stat *s, unsigned long bi_rw, | 495 | static void __dm_stat_bio(struct dm_stat *s, unsigned long bi_rw, |
