@samitouri / QOSamiQemu / commits / 90aacd5bc4

hw/ufs: avoid double unref of wrapped scsi-hd

ufs_init_scsi_device() creates an internal scsi-hd and adds it as a child of lu->bus. qdev_realize_and_unref() then drops the construction reference, leaving the bus child ownership to tear it down. ufs_lu_unrealize() still unrefs lu->scsi_dev directly. If the UFS controller is ejected through ACPI PCI hotplug, the scsi-hd object can be finalized there and then the bus child removal RCU callback later unrefs the same object again. Keep lu->scsi_dev as a borrowed pointer and clear it during unrealize without unreffing it. Add a qtest that ejects the UFS controller through the x86 ACPI PCI hotplug eject register. On an ASAN build, the test reproduces the UAF before the fix. Fixes: 096434fea13a ("hw/ufs: Modify lu.c to share codes with SCSI subsystem") Cc: qemu-stable@nongnu.org Signed-off-by: Jia Jia <physicalmtea@gmail.com> Signed-off-by: Jeuk Kim <jeuk20.kim@samsung.com>

Jia Jia committed May 31, 2026 at 09:34 UTC 90aacd5bc405cb71f77472616093f9e5ad9afd2b
2 files changed +19 -4
hw/ufs/lu.c
+1 -4
@@ -530,10 +530,7 @@ static void ufs_lu_unrealize(DeviceState *dev)
530 {
531 UfsLu *lu = DO_UPCAST(UfsLu, qdev, dev);
532
533 - if (lu->scsi_dev) {
534 - object_unref(OBJECT(lu->scsi_dev));
535 - lu->scsi_dev = NULL;
536 - }
533 + lu->scsi_dev = NULL;
534 }
535
536 static void ufs_lu_class_init(ObjectClass *oc, const void *data)
tests/qtest/ufs-test.c
+18
@@ -34,6 +34,8 @@
34 #define TEST_QID 0
35 #define QUEUE_SIZE 32
36 #define UFS_MCQ_MAX_QNUM 32
37 +#define ACPI_PCIHP_ADDR 0xae00
38 +#define PCI_EJ_BASE 0x0008
39
40 typedef struct QUfs QUfs;
41
@@ -635,6 +637,17 @@ static void ufstest_reg_read(void *obj, void *data, QGuestAllocator *alloc)
637 qpci_iounmap(&ufs->dev, ufs->bar);
638 }
639
640 +static void ufstest_acpi_eject(void *obj, void *data, QGuestAllocator *alloc)
641 +{
642 + QUfs *ufs = obj;
643 + QTestState *qts = ufs->dev.bus->qts;
644 +
645 + qtest_outl(qts, ACPI_PCIHP_ADDR + PCI_EJ_BASE, 1 << 4);
646 + qtest_qmp_assert_success(qts, "{ 'execute': 'query-status' }");
647 + g_usleep(3 * G_USEC_PER_SEC);
648 + qtest_qmp_assert_success(qts, "{ 'execute': 'query-status' }");
649 +}
650 +
651 static void ufstest_init(void *obj, void *data, QGuestAllocator *alloc)
652 {
653 QUfs *ufs = obj;
@@ -1682,6 +1695,7 @@ static void ufs_register_nodes(void)
1695 "mcq=false,nutrs=32,nutmrs=8,"
1696 "wb-max-size=1024,"
1697 "wb-min-size=256" };
1698 + QOSGraphTestOptions acpi_eject_test_opts = { .subprocess = true };
1699
1700 add_qpci_address(&edge_opts, &(QPCIAddress){ .devfn = QPCI_DEVFN(4, 0) });
1701
@@ -1700,6 +1714,10 @@ static void ufs_register_nodes(void)
1714 g_test_message("Skipping ufs io tests for ppc64");
1715 return;
1716 }
1717 + if (!strcmp(arch, "i386") || !strcmp(arch, "x86_64")) {
1718 + qos_add_test("acpi-eject", "ufs", ufstest_acpi_eject,
1719 + &acpi_eject_test_opts);
1720 + }
1721 qos_add_test("init", "ufs", ufstest_init, NULL);
1722 qos_add_test("legacy-read-write", "ufs", ufstest_read_write, &io_test_opts);
1723 qos_add_test("mcq-read-write", "ufs", ufstest_read_write, &mcq_test_opts);