• Muchun Song's avatar
    driver core: Fix use-after-free and double free on glue directory · e1666bcb
    Muchun Song authored
    commit ac43432c upstream.
    
    There is a race condition between removing glue directory and adding a new
    device under the glue dir. It can be reproduced in following test:
    
    CPU1:                                         CPU2:
    
    device_add()
      get_device_parent()
        class_dir_create_and_add()
          kobject_add_internal()
            create_dir()    // create glue_dir
    
                                                  device_add()
                                                    get_device_parent()
                                                      kobject_get() // get glue_dir
    
    device_del()
      cleanup_glue_dir()
        kobject_del(glue_dir)
    
                                                    kobject_add()
                                                      kobject_add_internal()
                                                        create_dir() // in glue_dir
                                                          sysfs_create_dir_ns()
                                                            kernfs_create_dir_ns(sd)
    
          sysfs_remove_dir() // glue_dir->sd=NULL
          sysfs_put()        // free glue_dir->sd
    
                                                              // sd is freed
                                                              kernfs_new_node(sd)
                                                                kernfs_get(glue_dir)
                                                                kernfs_add_one()
                                                                kernfs_put()
    
    Before CPU1 remove last child device under glue dir, if CPU2 add a new
    device under glue dir, the glue_dir kobject reference count will be
    increase to 2 via kobject_get() in get_device_parent(). And CPU2 has
    been called kernfs_create_dir_ns(), but not call kernfs_new_node().
    Meanwhile, CPU1 call sysfs_remove_dir() and sysfs_put(). This result in
    glue_dir->sd is freed and it's reference count will be 0. Then CPU2 call
    kernfs_get(glue_dir) will trigger a warning in kernfs_get() and increase
    it's reference count to 1. Because glue_dir->sd is freed by CPU1, the next
    call kernfs_add_one() by CPU2 will fail(This is also use-after-free)
    and call kernfs_put() to decrease reference count. Because the reference
    count is decremented to 0, it will also call kmem_cache_free() to free
    the glue_dir->sd again. This will result in double free.
    
    In order to avoid this happening, we also should make sure that kernfs_node
    for glue_dir is released in CPU1 only when refcount for glue_dir kobj is
    1 to fix this race.
    
    The following calltrace is captured in kernel 4.14 with the following patch
    applied:
    
    commit 726e4109 ("drivers: core: Remove glue dirs from sysfs earlier")
    
    --------------------------------------------------------------------------
    [    3.633703] WARNING: CPU: 4 PID: 513 at .../fs/kernfs/dir.c:494
                    Here is WARN_ON(!atomic_read(&kn->count) in kernfs_get().
    ....
    [    3.633986] Call trace:
    [    3.633991]  kernfs_create_dir_ns+0xa8/0xb0
    [    3.633994]  sysfs_create_dir_ns+0x54/0xe8
    [    3.634001]  kobject_add_internal+0x22c/0x3f0
    [    3.634005]  kobject_add+0xe4/0x118
    [    3.634011]  device_add+0x200/0x870
    [    3.634017]  _request_firmware+0x958/0xc38
    [    3.634020]  request_firmware_into_buf+0x4c/0x70
    ....
    [    3.634064] kernel BUG at .../mm/slub.c:294!
                    Here is BUG_ON(object == fp) in set_freepointer().
    ....
    [    3.634346] Call trace:
    [    3.634351]  kmem_cache_free+0x504/0x6b8
    [    3.634355]  kernfs_put+0x14c/0x1d8
    [    3.634359]  kernfs_create_dir_ns+0x88/0xb0
    [    3.634362]  sysfs_create_dir_ns+0x54/0xe8
    [    3.634366]  kobject_add_internal+0x22c/0x3f0
    [    3.634370]  kobject_add+0xe4/0x118
    [    3.634374]  device_add+0x200/0x870
    [    3.634378]  _request_firmware+0x958/0xc38
    [    3.634381]  request_firmware_into_buf+0x4c/0x70
    --------------------------------------------------------------------------
    
    Fixes: 726e4109 ("drivers: core: Remove glue dirs from sysfs earlier")
    Signed-off-by: default avatarMuchun Song <smuchun@gmail.com>
    Reviewed-by: default avatarMukesh Ojha <mojha@codeaurora.org>
    Signed-off-by: default avatarPrateek Sood <prsood@codeaurora.org>
    Link: https://lore.kernel.org/r/20190727032122.24639-1-smuchun@gmail.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
    e1666bcb
core.c 84.1 KB