Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <luomeng12@huawei.com>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
Cc: stable@vger.kernel.org
Tested-by: Li Lingfeng <lilingfeng3@huawei.com>
Signed-off-by: Mike Snitzer <snitzer@kernel.org>

authored by

Mikulas Patocka and committed by
Mike Snitzer
f6007dce 0bb80ecc

+31 -9
+1
drivers/md/dm-core.h
··· 214 214 215 215 /* a list of devices used by this table */ 216 216 struct list_head devices; 217 + struct rw_semaphore devices_lock; 217 218 218 219 /* events get handed up using this callback */ 219 220 void (*event_fn)(void *data);
+6 -1
drivers/md/dm-ioctl.c
··· 1630 1630 struct dm_dev_internal *dd; 1631 1631 struct dm_target_deps *deps; 1632 1632 1633 + down_read(&table->devices_lock); 1634 + 1633 1635 deps = get_result_buffer(param, param_size, &len); 1634 1636 1635 1637 /* ··· 1646 1644 needed = struct_size(deps, dev, count); 1647 1645 if (len < needed) { 1648 1646 param->flags |= DM_BUFFER_FULL_FLAG; 1649 - return; 1647 + goto out; 1650 1648 } 1651 1649 1652 1650 /* ··· 1658 1656 deps->dev[count++] = huge_encode_dev(dd->dm_dev->bdev->bd_dev); 1659 1657 1660 1658 param->data_size = param->data_start + needed; 1659 + 1660 + out: 1661 + up_read(&table->devices_lock); 1661 1662 } 1662 1663 1663 1664 static int table_deps(struct file *filp, struct dm_ioctl *param, size_t param_size)
+24 -8
drivers/md/dm-table.c
··· 135 135 return -ENOMEM; 136 136 137 137 INIT_LIST_HEAD(&t->devices); 138 + init_rwsem(&t->devices_lock); 138 139 139 140 if (!num_targets) 140 141 num_targets = KEYS_PER_NODE; ··· 360 359 if (dev == disk_devt(t->md->disk)) 361 360 return -EINVAL; 362 361 362 + down_write(&t->devices_lock); 363 + 363 364 dd = find_device(&t->devices, dev); 364 365 if (!dd) { 365 366 dd = kmalloc(sizeof(*dd), GFP_KERNEL); 366 - if (!dd) 367 - return -ENOMEM; 367 + if (!dd) { 368 + r = -ENOMEM; 369 + goto unlock_ret_r; 370 + } 368 371 369 372 r = dm_get_table_device(t->md, dev, mode, &dd->dm_dev); 370 373 if (r) { 371 374 kfree(dd); 372 - return r; 375 + goto unlock_ret_r; 373 376 } 374 377 375 378 refcount_set(&dd->count, 1); ··· 383 378 } else if (dd->dm_dev->mode != (mode | dd->dm_dev->mode)) { 384 379 r = upgrade_mode(dd, mode, t->md); 385 380 if (r) 386 - return r; 381 + goto unlock_ret_r; 387 382 } 388 383 refcount_inc(&dd->count); 389 384 out: 385 + up_write(&t->devices_lock); 390 386 *result = dd->dm_dev; 391 387 return 0; 388 + 389 + unlock_ret_r: 390 + up_write(&t->devices_lock); 391 + return r; 392 392 } 393 393 EXPORT_SYMBOL(dm_get_device); 394 394 ··· 429 419 void dm_put_device(struct dm_target *ti, struct dm_dev *d) 430 420 { 431 421 int found = 0; 432 - struct list_head *devices = &ti->table->devices; 422 + struct dm_table *t = ti->table; 423 + struct list_head *devices = &t->devices; 433 424 struct dm_dev_internal *dd; 425 + 426 + down_write(&t->devices_lock); 434 427 435 428 list_for_each_entry(dd, devices, list) { 436 429 if (dd->dm_dev == d) { ··· 443 430 } 444 431 if (!found) { 445 432 DMERR("%s: device %s not in table devices list", 446 - dm_device_name(ti->table->md), d->name); 447 - return; 433 + dm_device_name(t->md), d->name); 434 + goto unlock_ret; 448 435 } 449 436 if (refcount_dec_and_test(&dd->count)) { 450 - dm_put_table_device(ti->table->md, d); 437 + dm_put_table_device(t->md, d); 451 438 list_del(&dd->list); 452 439 kfree(dd); 453 440 } 441 + 442 + unlock_ret: 443 + up_write(&t->devices_lock); 454 444 } 455 445 EXPORT_SYMBOL(dm_put_device); 456 446