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: syzbot+8e6fc64255334ccf3ab3@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=8e6fc64255334ccf3ab3 Signed-off-by: Igor Putko --- 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