rbd: avoid use-after-free in do_rbd_add() when rbd_dev_create() fails
authorIlya Dryomov <idryomov@gmail.com>
Fri, 24 Feb 2023 17:48:54 +0000 (18:48 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 10 Mar 2023 08:40:08 +0000 (09:40 +0100)
commit f7c4d9b133c7a04ca619355574e96b6abf209fba upstream.

If getting an ID or setting up a work queue in rbd_dev_create() fails,
use-after-free on rbd_dev->rbd_client, rbd_dev->spec and rbd_dev->opts
is triggered in do_rbd_add().  The root cause is that the ownership of
these structures is transfered to rbd_dev prematurely and they all end
up getting freed when rbd_dev_create() calls rbd_dev_free() prior to
returning to do_rbd_add().

Found by Linux Verification Center (linuxtesting.org) with SVACE, an
incomplete patch submitted by Natalia Petrova <n.petrova@fintech.ru>.

Cc: stable@vger.kernel.org
Fixes: 1643dfa4c2c8 ("rbd: introduce a per-device ordered workqueue")
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/block/rbd.c

index c4a52f33604dcf8aaff23233f914ddfe62258244..f9d298c5a2ab64aa0f408996fb37e5500c1f64f4 100644 (file)
@@ -5296,8 +5296,7 @@ static void rbd_dev_release(struct device *dev)
                module_put(THIS_MODULE);
 }
 
-static struct rbd_device *__rbd_dev_create(struct rbd_client *rbdc,
-                                          struct rbd_spec *spec)
+static struct rbd_device *__rbd_dev_create(struct rbd_spec *spec)
 {
        struct rbd_device *rbd_dev;
 
@@ -5342,9 +5341,6 @@ static struct rbd_device *__rbd_dev_create(struct rbd_client *rbdc,
        rbd_dev->dev.parent = &rbd_root_dev;
        device_initialize(&rbd_dev->dev);
 
-       rbd_dev->rbd_client = rbdc;
-       rbd_dev->spec = spec;
-
        return rbd_dev;
 }
 
@@ -5357,12 +5353,10 @@ static struct rbd_device *rbd_dev_create(struct rbd_client *rbdc,
 {
        struct rbd_device *rbd_dev;
 
-       rbd_dev = __rbd_dev_create(rbdc, spec);
+       rbd_dev = __rbd_dev_create(spec);
        if (!rbd_dev)
                return NULL;
 
-       rbd_dev->opts = opts;
-
        /* get an id and fill in device name */
        rbd_dev->dev_id = ida_simple_get(&rbd_dev_id_ida, 0,
                                         minor_to_rbd_dev_id(1 << MINORBITS),
@@ -5379,6 +5373,10 @@ static struct rbd_device *rbd_dev_create(struct rbd_client *rbdc,
        /* we have a ref from do_rbd_add() */
        __module_get(THIS_MODULE);
 
+       rbd_dev->rbd_client = rbdc;
+       rbd_dev->spec = spec;
+       rbd_dev->opts = opts;
+
        dout("%s rbd_dev %p dev_id %d\n", __func__, rbd_dev, rbd_dev->dev_id);
        return rbd_dev;
 
@@ -6739,7 +6737,7 @@ static int rbd_dev_probe_parent(struct rbd_device *rbd_dev, int depth)
                goto out_err;
        }
 
-       parent = __rbd_dev_create(rbd_dev->rbd_client, rbd_dev->parent_spec);
+       parent = __rbd_dev_create(rbd_dev->parent_spec);
        if (!parent) {
                ret = -ENOMEM;
                goto out_err;
@@ -6749,8 +6747,8 @@ static int rbd_dev_probe_parent(struct rbd_device *rbd_dev, int depth)
         * Images related by parent/child relationships always share
         * rbd_client and spec/parent_spec, so bump their refcounts.
         */
-       __rbd_get_client(rbd_dev->rbd_client);
-       rbd_spec_get(rbd_dev->parent_spec);
+       parent->rbd_client = __rbd_get_client(rbd_dev->rbd_client);
+       parent->spec = rbd_spec_get(rbd_dev->parent_spec);
 
        __set_bit(RBD_DEV_FLAG_READONLY, &parent->flags);