CVE-2026-89777: vfio/pci: clear vdev->msi_perm after freeing it on init failure
In the Linux kernel, the following vulnerability has been resolved:
vfio/pci: clear vdev->msiperm after freeing it on init failure
vfiomsicaplen() lazily allocates the per-device MSI permission table:
vdev->msiperm = kmallocobj(struct permbits, GFPKERNELACCOUNT); if (!vdev->msiperm) return -ENOMEM;
ret = initpcicapmsiperm(vdev->msiperm, len, flags); if (ret) { kfree(vdev->msiperm); return ret; / vdev->msiperm left dangling / }
When initpcicapmsiperm() -> allocpermbits() fails with -ENOMEM, the error path frees vdev->msiperm but leaves the freed pointer stored in it. vdev->msiperm is not re-zeroed later because struct vfiopcicoredevice is per-device and persists across open/close cycles, and the vfioconfiginit() error path returns without calling vfioconfigfree(). So the dangling pointer outlives the failed open.
That leads to two use-after-frees on the same device:
1. Reuse. The next vfioconfiginit() sees the stale pointer at "if (vdev->msiperm) return len;" and reuses the freed object. MSI config accesses in vfiopciconfigrwsingle() then dereference and call the freed perm->readfn / perm->writefn function pointers.
2. Double free. A later vfioconfigfree() runs freepermbits() and kfree() on the already-freed object.
Fix it by NULLing vdev->msiperm after the kfree(), matching the NULL-after-free discipline already used in freepermbits() and vfioconfigfree().
BUG: KASAN: slab-use-after-free in vfiopciconfigrwsingle (drivers/vfio/pci/vfiopciconfig.c:1961) Read of size 8 at addr ffff88800fcc88d0 by task exploit/143 Call Trace: ... kasanreport (mm/kasan/report.c:595) vfiopciconfigrwsingle (drivers/vfio/pci/vfiopciconfig.c:1961) vfiopciconfigrw (drivers/vfio/pci/vfiopciconfig.c:1986) vfiopcirw (drivers/vfio/pci/vfiopcicore.c:1599) vfsread (fs/readwrite.c:572) x64syspread64 (fs/readwrite.c:764) dosyscall64 (arch/x86/entry/syscall64.c:94) ...
Followed on device close by a double free of the same object:
Oops: general protection fault, probably for non-canonical address 0x1f63e0e8000008: 0000 [#1] SMP KASAN NOPTI RIP: 0010:kfree (mm/slub.c:6711) Call Trace: vfioconfigfree (drivers/vfio/pci/vfiopciconfig.c:1861) vfiopcicoredisable (drivers/vfio/pci/vfiopcicore.c:685) vfiopcicoreclosedevice (drivers/vfio/pci/vfiopcicore.c:777) vfiodfclose (drivers/vfio/vfiomain.c:602) vfiodevicefopsrelease (drivers/vfio/vfiomain.c:648) fput (fs/filetable.c:512) x64sysclose (fs/open.c:1496) dosyscall64 (arch/x86/entry/syscall64.c:94) ... Kernel panic - not syncing: Fatal exception
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Configuration
In vfio_pci_config.c, follow the NULL-after-free discipline already used in free_perm_bits(): after kfree(vdev->msi_perm) in the init_pci_cap_msi_perm() failure path (when it returns -ENOMEM), set vdev->msi_perm to NULL so later cleanup (vfio_config_free/vfio_device_fops_release) and subsequent config accesses do not dereference or double-free a dangling pointer.
Linux kernel vfio/pci clear vdev->msi_perm after freeing it on init failure = set vdev->msi_perm = NULL after kfree(vdev->msi_perm) in the vfio_config_init / init_pci_cap_msi_perm() error path
Event History
Frequently Asked Questions
What failure condition is required to create the stale MSI permission pointer?
The per-device MSI permission table must be allocated successfully, then init_pci_cap_msi_perm() must fail because alloc_perm_bits() returns -ENOMEM. The error path frees the table but leaves vdev->msi_perm pointing to the freed memory.
Does the condition persist after the failed initialization attempt?
Yes. The vfio_pci_core_device persists across open and close cycles, and the vfio_config_init() error path does not call vfio_config_free(), so vdev->msi_perm is not cleared after the failure.
What can occur on later use of the same device?
A subsequent vfio_config_init() can treat the dangling pointer as a valid MSI permission table. MSI configuration accesses may then dereference freed permission data and invoke freed read or write function pointers; later cleanup can also free the same memory again.