nbd: fix null-ptr-dereference while accessing 'nbd->config'
authorLi Nan <linan122@huawei.com>
Thu, 16 Nov 2023 16:23:16 +0000 (00:23 +0800)
committerJens Axboe <axboe@kernel.dk>
Mon, 20 Nov 2023 17:16:44 +0000 (10:16 -0700)
Memory reordering may occur in nbd_genl_connect(), causing config_refs
to be set to 1 while nbd->config is still empty. Opening nbd at this
time will cause null-ptr-dereference.

   T1                      T2
   nbd_open
    nbd_get_config_unlocked
                     nbd_genl_connect
                      nbd_alloc_and_init_config
                       //memory reordered
                        refcount_set(&nbd->config_refs, 1)  // 2
     nbd->config
      ->null point
     nbd->config = config  // 1

Fix it by adding smp barrier to guarantee the execution sequence.

Signed-off-by: Li Nan <linan122@huawei.com>
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Link: https://lore.kernel.org/r/20231116162316.1740402-4-linan666@huaweicloud.com
Signed-off-by: Jens Axboe <axboe@kernel.dk>
drivers/block/nbd.c

index daaf880..3f03cb3 100644 (file)
@@ -397,8 +397,16 @@ static u32 req_to_nbd_cmd_type(struct request *req)
 
 static struct nbd_config *nbd_get_config_unlocked(struct nbd_device *nbd)
 {
-       if (refcount_inc_not_zero(&nbd->config_refs))
+       if (refcount_inc_not_zero(&nbd->config_refs)) {
+               /*
+                * Add smp_mb__after_atomic to ensure that reading nbd->config_refs
+                * and reading nbd->config is ordered. The pair is the barrier in
+                * nbd_alloc_and_init_config(), avoid nbd->config_refs is set
+                * before nbd->config.
+                */
+               smp_mb__after_atomic();
                return nbd->config;
+       }
 
        return NULL;
 }
@@ -1559,7 +1567,15 @@ static int nbd_alloc_and_init_config(struct nbd_device *nbd)
        init_waitqueue_head(&config->conn_wait);
        config->blksize_bits = NBD_DEF_BLKSIZE_BITS;
        atomic_set(&config->live_connections, 0);
+
        nbd->config = config;
+       /*
+        * Order refcount_set(&nbd->config_refs, 1) and nbd->config assignment,
+        * its pair is the barrier in nbd_get_config_unlocked().
+        * So nbd_get_config_unlocked() won't see nbd->config as null after
+        * refcount_inc_not_zero() succeed.
+        */
+       smp_mb__before_atomic();
        refcount_set(&nbd->config_refs, 1);
 
        return 0;