• Luo Meng's avatar
    dm thin: Fix UAF in run_timer_softirq() · 88430ebc
    Luo Meng authored
    When dm_resume() and dm_destroy() are concurrent, it will
    lead to UAF, as follows:
    
     BUG: KASAN: use-after-free in __run_timers+0x173/0x710
     Write of size 8 at addr ffff88816d9490f0 by task swapper/0/0
    <snip>
     Call Trace:
      <IRQ>
      dump_stack_lvl+0x73/0x9f
      print_report.cold+0x132/0xaa2
      _raw_spin_lock_irqsave+0xcd/0x160
      __run_timers+0x173/0x710
      kasan_report+0xad/0x110
      __run_timers+0x173/0x710
      __asan_store8+0x9c/0x140
      __run_timers+0x173/0x710
      call_timer_fn+0x310/0x310
      pvclock_clocksource_read+0xfa/0x250
      kvm_clock_read+0x2c/0x70
      kvm_clock_get_cycles+0xd/0x20
      ktime_get+0x5c/0x110
      lapic_next_event+0x38/0x50
      clockevents_program_event+0xf1/0x1e0
      run_timer_softirq+0x49/0x90
      __do_softirq+0x16e/0x62c
      __irq_exit_rcu+0x1fa/0x270
      irq_exit_rcu+0x12/0x20
      sysvec_apic_timer_interrupt+0x8e/0xc0
    
    One of the concurrency UAF can be shown as below:
    
            use                                  free
    do_resume                           |
      __find_device_hash_cell           |
        dm_get                          |
          atomic_inc(&md->holders)      |
                                        | dm_destroy
                                        |   __dm_destroy
                                        |     if (!dm_suspended_md(md))
                                        |     atomic_read(&md->holders)
                                        |     msleep(1)
      dm_resume                         |
        __dm_resume                     |
          dm_table_resume_targets       |
            pool_resume                 |
              do_waker  #add delay work |
      dm_put                            |
        atomic_dec(&md->holders)        |
                                        |     dm_table_destroy
                                        |       pool_dtr
                                        |         __pool_dec
                                        |           __pool_destroy
                                        |             destroy_workqueue
                                        |             kfree(pool) # free pool
            time out
    __do_softirq
      run_timer_softirq # pool has already been freed
    
    This can be easily reproduced using:
      1. create thin-pool
      2. dmsetup suspend pool
      3. dmsetup resume pool
      4. dmsetup remove_all # Concurrent with 3
    
    The root cause of this UAF bug is that dm_resume() adds timer after
    dm_destroy() skips cancelling the timer because of suspend status.
    After timeout, it will call run_timer_softirq(), however pool has
    already been freed. The concurrency UAF bug will happen.
    
    Therefore, cancelling timer again in __pool_destroy().
    
    Cc: stable@vger.kernel.org
    Fixes: 991d9fa0 ("dm: add thin provisioning target")
    Signed-off-by: default avatarLuo Meng <luomeng12@huawei.com>
    Signed-off-by: default avatarMike Snitzer <snitzer@kernel.org>
    88430ebc
dm-thin.c 112 KB