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