When snapshot_ctr() parses target arguments where both origin and COW
paths resolve to the same underlying block device, a slab-use-after-free
occurs.

The origin device is acquired first with origin_mode (e.g. BLK_OPEN_READ
or BLK_OPEN_WRITE). When opening the COW device with dm_table_get_mode()
(typically BLK_OPEN_READ | BLK_OPEN_WRITE), dm_get_device() sees that
the device is already in the table list and calls upgrade_mode() because
the requested mode is broader than the existing one.

upgrade_mode() opens a new table_device, points dd->dm_dev to it, and
calls dm_put_table_device() on the previous one. Since the refcount drops
to zero, the old table_device is freed. However, s->origin still holds a
pointer to that freed table_device. The subsequent check:

    if (s->cow->bdev && s->cow->bdev == s->origin->bdev)

dereferences s->origin->bdev, tripping a KASAN slab-use-after-free read.
Furthermore, under the bad_store error path, dm_put_device(ti, s->origin)
fails to find s->origin in ti->table->devices because dd->dm_dev was
replaced, leaking the device reference count.

Fix this by resolving cow_path to a dev_t via dm_devt_from_path() and
verifying that it does not match s->origin->bdev->bd_dev before calling
dm_get_device() for the COW device.

Fixes: 26110d5afe81 ("dm-snap: simplify the origin_dev == cow_dev check in 
snapshot_ctr")
Reported-by: [email protected]
Closes: https://syzkaller.appspot.com/bug?extid=8e6fc64255334ccf3ab3
Signed-off-by: Igor Putko <[email protected]>
---
 drivers/md/dm-snap.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c
index 7c3725e4ac54..4a7c04c780d1 100644
--- a/drivers/md/dm-snap.c
+++ b/drivers/md/dm-snap.c
@@ -1232,6 +1232,7 @@ static int snapshot_ctr(struct dm_target *ti, unsigned 
int argc, char **argv)
        int i;
        int r = -EINVAL;
        char *origin_path, *cow_path;
+       dev_t cow_dev;
        unsigned int args_used, num_flush_bios = 1;
        blk_mode_t origin_mode = BLK_OPEN_READ;
 
@@ -1274,15 +1275,21 @@ static int snapshot_ctr(struct dm_target *ti, unsigned 
int argc, char **argv)
        argv++;
        argc--;
 
-       r = dm_get_device(ti, cow_path, dm_table_get_mode(ti->table), &s->cow);
+       r = dm_devt_from_path(cow_path, &cow_dev);
        if (r) {
                ti->error = "Cannot get COW device";
                goto bad_cow;
        }
-       if (s->cow->bdev && s->cow->bdev == s->origin->bdev) {
+       if (cow_dev == s->origin->bdev->bd_dev) {
                ti->error = "COW device cannot be the same as origin device";
                r = -EINVAL;
-               goto bad_store;
+               goto bad_cow;
+       }
+
+       r = dm_get_device(ti, cow_path, dm_table_get_mode(ti->table), &s->cow);
+       if (r) {
+               ti->error = "Cannot get COW device";
+               goto bad_cow;
        }
 
        r = dm_exception_store_create(ti, argc, argv, s, &args_used, &s->store);
-- 
2.47.3


Reply via email to