Skip to content
  • Kiyoshi Ueda's avatar
    dm: separate device deletion from dm_put · 3f77316d
    Kiyoshi Ueda authored
    This patch separates the device deletion code from dm_put()
    to make sure the deletion happens in the process context.
    
    By this patch, device deletion always occurs in an ioctl (process)
    context and dm_put() can be called in interrupt context.
    As a result, the request-based dm's bad dm_put() usage pointed out
    by Mikulas below disappears.
        http://marc.info/?l=dm-devel&m=126699981019735&w=2
    
    
    
    Without this patch, I confirmed there is a case to crash the system:
        dm_put() => dm_table_destroy() => vfree() => BUG_ON(in_interrupt())
    
    Some more backgrounds and details:
    In request-based dm, a device opener can remove a mapped_device
    while the last request is still completing, because bios in the last
    request complete first and then the device opener can close and remove
    the mapped_device before the last request completes:
      CPU0                                          CPU1
      =================================================================
      <<INTERRUPT>>
      blk_end_request_all(clone_rq)
        blk_update_request(clone_rq)
          bio_endio(clone_bio) == end_clone_bio
            blk_update_request(orig_rq)
              bio_endio(orig_bio)
                                                    <<I/O completed>>
                                                    dm_blk_close()
                                                    dev_remove()
                                                      dm_put(md)
                                                        <<Free md>>
       blk_finish_request(clone_rq)
         ....
         dm_end_request(clone_rq)
           free_rq_clone(clone_rq)
           blk_end_request_all(orig_rq)
           rq_completed(md)
    
    So request-based dm used dm_get()/dm_put() to hold md for each I/O
    until its request completion handling is fully done.
    However, the final dm_put() can call the device deletion code which
    must not be run in interrupt context and may cause kernel panic.
    
    To solve the problem, this patch moves the device deletion code,
    dm_destroy(), to predetermined places that is actually deleting
    the mapped_device in ioctl (process) context, and changes dm_put()
    just to decrement the reference count of the mapped_device.
    By this change, dm_put() can be used in any context and the symmetric
    model below is introduced:
        dm_create():  create a mapped_device
        dm_destroy(): destroy a mapped_device
        dm_get():     increment the reference count of a mapped_device
        dm_put():     decrement the reference count of a mapped_device
    
    dm_destroy() waits for all references of the mapped_device to disappear,
    then deletes the mapped_device.
    
    dm_destroy() uses active waiting with msleep(1), since deleting
    the mapped_device isn't performance-critical task.
    And since at this point, nobody opens the mapped_device and no new
    reference will be taken, the pending counts are just for racing
    completing activity and will eventually decrease to zero.
    
    For the unlikely case of the forced module unload, dm_destroy_immediate(),
    which doesn't wait and forcibly deletes the mapped_device, is also
    introduced and used in dm_hash_remove_all().  Otherwise, "rmmod -f"
    may be stuck and never return.
    And now, because the mapped_device is deleted at this point, subsequent
    accesses to the mapped_device may cause NULL pointer references.
    
    Cc: stable@kernel.org
    Signed-off-by: default avatarKiyoshi Ueda <k-ueda@ct.jp.nec.com>
    Signed-off-by: default avatarJun'ichi Nomura <j-nomura@ce.jp.nec.com>
    Signed-off-by: default avatarAlasdair G Kergon <agk@redhat.com>
    3f77316d