Skip to content

Commit f6007dc

Browse files
Mikulas PatockaMike Snitzer
Mikulas Patocka
authored and
Mike Snitzer
committed
dm: fix a race condition in retrieve_deps
There's a race condition in the multipath target when retrieve_deps races with multipath_message calling dm_get_device and dm_put_device. retrieve_deps walks the list of open devices without holding any lock but multipath may add or remove devices to the list while it is running. The end result may be memory corruption or use-after-free memory access. See this description of a UAF with multipath_message(): https://listman.redhat.com/archives/dm-devel/2022-October/052373.html Fix this bug by introducing a new rw semaphore "devices_lock". We grab devices_lock for read in retrieve_deps and we grab it for write in dm_get_device and dm_put_device. Reported-by: Luo Meng <[email protected]> Signed-off-by: Mikulas Patocka <[email protected]> Cc: [email protected] Tested-by: Li Lingfeng <[email protected]> Signed-off-by: Mike Snitzer <[email protected]>
1 parent 0bb80ec commit f6007dc

File tree

3 files changed

+31
-9
lines changed

3 files changed

+31
-9
lines changed

drivers/md/dm-core.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ struct dm_table {
214214

215215
/* a list of devices used by this table */
216216
struct list_head devices;
217+
struct rw_semaphore devices_lock;
217218

218219
/* events get handed up using this callback */
219220
void (*event_fn)(void *data);

drivers/md/dm-ioctl.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1630,6 +1630,8 @@ static void retrieve_deps(struct dm_table *table,
16301630
struct dm_dev_internal *dd;
16311631
struct dm_target_deps *deps;
16321632

1633+
down_read(&table->devices_lock);
1634+
16331635
deps = get_result_buffer(param, param_size, &len);
16341636

16351637
/*
@@ -1644,7 +1646,7 @@ static void retrieve_deps(struct dm_table *table,
16441646
needed = struct_size(deps, dev, count);
16451647
if (len < needed) {
16461648
param->flags |= DM_BUFFER_FULL_FLAG;
1647-
return;
1649+
goto out;
16481650
}
16491651

16501652
/*
@@ -1656,6 +1658,9 @@ static void retrieve_deps(struct dm_table *table,
16561658
deps->dev[count++] = huge_encode_dev(dd->dm_dev->bdev->bd_dev);
16571659

16581660
param->data_size = param->data_start + needed;
1661+
1662+
out:
1663+
up_read(&table->devices_lock);
16591664
}
16601665

16611666
static int table_deps(struct file *filp, struct dm_ioctl *param, size_t param_size)

drivers/md/dm-table.c

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ int dm_table_create(struct dm_table **result, blk_mode_t mode,
135135
return -ENOMEM;
136136

137137
INIT_LIST_HEAD(&t->devices);
138+
init_rwsem(&t->devices_lock);
138139

139140
if (!num_targets)
140141
num_targets = KEYS_PER_NODE;
@@ -359,16 +360,20 @@ int __ref dm_get_device(struct dm_target *ti, const char *path, blk_mode_t mode,
359360
if (dev == disk_devt(t->md->disk))
360361
return -EINVAL;
361362

363+
down_write(&t->devices_lock);
364+
362365
dd = find_device(&t->devices, dev);
363366
if (!dd) {
364367
dd = kmalloc(sizeof(*dd), GFP_KERNEL);
365-
if (!dd)
366-
return -ENOMEM;
368+
if (!dd) {
369+
r = -ENOMEM;
370+
goto unlock_ret_r;
371+
}
367372

368373
r = dm_get_table_device(t->md, dev, mode, &dd->dm_dev);
369374
if (r) {
370375
kfree(dd);
371-
return r;
376+
goto unlock_ret_r;
372377
}
373378

374379
refcount_set(&dd->count, 1);
@@ -378,12 +383,17 @@ int __ref dm_get_device(struct dm_target *ti, const char *path, blk_mode_t mode,
378383
} else if (dd->dm_dev->mode != (mode | dd->dm_dev->mode)) {
379384
r = upgrade_mode(dd, mode, t->md);
380385
if (r)
381-
return r;
386+
goto unlock_ret_r;
382387
}
383388
refcount_inc(&dd->count);
384389
out:
390+
up_write(&t->devices_lock);
385391
*result = dd->dm_dev;
386392
return 0;
393+
394+
unlock_ret_r:
395+
up_write(&t->devices_lock);
396+
return r;
387397
}
388398
EXPORT_SYMBOL(dm_get_device);
389399

@@ -419,9 +429,12 @@ static int dm_set_device_limits(struct dm_target *ti, struct dm_dev *dev,
419429
void dm_put_device(struct dm_target *ti, struct dm_dev *d)
420430
{
421431
int found = 0;
422-
struct list_head *devices = &ti->table->devices;
432+
struct dm_table *t = ti->table;
433+
struct list_head *devices = &t->devices;
423434
struct dm_dev_internal *dd;
424435

436+
down_write(&t->devices_lock);
437+
425438
list_for_each_entry(dd, devices, list) {
426439
if (dd->dm_dev == d) {
427440
found = 1;
@@ -430,14 +443,17 @@ void dm_put_device(struct dm_target *ti, struct dm_dev *d)
430443
}
431444
if (!found) {
432445
DMERR("%s: device %s not in table devices list",
433-
dm_device_name(ti->table->md), d->name);
434-
return;
446+
dm_device_name(t->md), d->name);
447+
goto unlock_ret;
435448
}
436449
if (refcount_dec_and_test(&dd->count)) {
437-
dm_put_table_device(ti->table->md, d);
450+
dm_put_table_device(t->md, d);
438451
list_del(&dd->list);
439452
kfree(dd);
440453
}
454+
455+
unlock_ret:
456+
up_write(&t->devices_lock);
441457
}
442458
EXPORT_SYMBOL(dm_put_device);
443459

0 commit comments

Comments
 (0)