From aad15893cb1ef4982b8439ee9534457e015f19e5 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 28 Feb 2024 20:33:12 +0900 Subject: [PATCH 1/5] hw/nvme: Use pcie_sriov_num_vfs() commit 91bb64a8d2014fda33a81fcf0fce37340f0d3b0c upstream. nvme_sriov_pre_write_ctrl() used to directly inspect SR-IOV configurations to know the number of VFs being disabled due to SR-IOV configuration writes, but the logic was flawed and resulted in out-of-bound memory access. It assumed PCI_SRIOV_NUM_VF always has the number of currently enabled VFs, but it actually doesn't in the following cases: - PCI_SRIOV_NUM_VF has been set but PCI_SRIOV_CTRL_VFE has never been. - PCI_SRIOV_NUM_VF was written after PCI_SRIOV_CTRL_VFE was set. - VFs were only partially enabled because of realization failure. It is a responsibility of pcie_sriov to interpret SR-IOV configurations and pcie_sriov does it correctly, so use pcie_sriov_num_vfs(), which it provides, to get the number of enabled VFs before and after SR-IOV configuration writes. Cc: qemu-stable@nongnu.org Fixes: CVE-2024-26328 Fixes: 11871f53ef8e ("hw/nvme: Add support for the Virtualization Management command") Suggested-by: Michael S. Tsirkin Signed-off-by: Akihiko Odaki Message-Id: <20240228-reuse-v8-1-282660281e60@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Signed-off-by: Bin Guo --- hw/nvme/ctrl.c | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 76fe039704..2860a9bed1 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -8466,36 +8466,26 @@ static void nvme_pci_reset(DeviceState *qdev) nvme_ctrl_reset(n, NVME_RESET_FUNCTION); } -static void nvme_sriov_pre_write_ctrl(PCIDevice *dev, uint32_t address, - uint32_t val, int len) +static void nvme_sriov_post_write_config(PCIDevice *dev, uint16_t old_num_vfs) { NvmeCtrl *n = NVME(dev); NvmeSecCtrlEntry *sctrl; - uint16_t sriov_cap = dev->exp.sriov_cap; - uint32_t off = address - sriov_cap; - int i, num_vfs; + int i; - if (!sriov_cap) { - return; - } - - if (range_covers_byte(off, len, PCI_SRIOV_CTRL)) { - if (!(val & PCI_SRIOV_CTRL_VFE)) { - num_vfs = pci_get_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF); - for (i = 0; i < num_vfs; i++) { - sctrl = &n->sec_ctrl_list.sec[i]; - nvme_virt_set_state(n, le16_to_cpu(sctrl->scid), false); - } - } + for (i = pcie_sriov_num_vfs(dev); i < old_num_vfs; i++) { + sctrl = &n->sec_ctrl_list.sec[i]; + nvme_virt_set_state(n, le16_to_cpu(sctrl->scid), false); } } static void nvme_pci_write_config(PCIDevice *dev, uint32_t address, uint32_t val, int len) { - nvme_sriov_pre_write_ctrl(dev, address, val, len); + uint16_t old_num_vfs = pcie_sriov_num_vfs(dev); + pci_default_write_config(dev, address, val, len); pcie_cap_flr_write_config(dev, address, val, len); + nvme_sriov_post_write_config(dev, old_num_vfs); } static const VMStateDescription nvme_vmstate = { -- Gitee From 07d415b526862256e31888d9c0d3d5871eec39f1 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 28 Feb 2024 20:33:13 +0900 Subject: [PATCH 2/5] pcie_sriov: Validate NumVFs commit 6081b4243cd64dff1b2cf5b0c215c71e9d7e753b upstream. The guest may write NumVFs greater than TotalVFs and that can lead to buffer overflow in VF implementations. Cc: qemu-stable@nongnu.org Fixes: CVE-2024-26327 Fixes: 7c0fa8dff811 ("pcie: Add support for Single Root I/O Virtualization (SR/IOV)") Signed-off-by: Akihiko Odaki Message-Id: <20240228-reuse-v8-2-282660281e60@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Reviewed-by: Sriram Yagnaraman Signed-off-by: Bin Guo --- hw/pci/pcie_sriov.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index a1fe65f5d8..da209b7f47 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -176,6 +176,9 @@ static void register_vfs(PCIDevice *dev) assert(sriov_cap > 0); num_vfs = pci_get_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF); + if (num_vfs > pci_get_word(dev->config + sriov_cap + PCI_SRIOV_TOTAL_VF)) { + return; + } dev->exp.sriov_pf.vf = g_new(PCIDevice *, num_vfs); -- Gitee From dad42928d23079d634b55afc5b3105ecdc49ba05 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 28 Feb 2024 20:33:14 +0900 Subject: [PATCH 3/5] pcie_sriov: Reset SR-IOV extended capability commit c8bc4db403e17663b69d811e69f88c9dfc6f7be2 upstream. pcie_sriov_pf_disable_vfs() is called when resetting the PF, but it only disables VFs and does not reset SR-IOV extended capability, leaking the state and making the VF Enable register inconsistent with the actual state. Replace pcie_sriov_pf_disable_vfs() with pcie_sriov_pf_reset(), which does not only disable VFs but also resets the capability. Signed-off-by: Akihiko Odaki Message-Id: <20240228-reuse-v8-3-282660281e60@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Reviewed-by: Sriram Yagnaraman Signed-off-by: Bin Guo --- hw/net/igb.c | 2 +- hw/nvme/ctrl.c | 2 +- hw/pci/pcie_sriov.c | 26 ++++++++++++++++++-------- include/hw/pci/pcie_sriov.h | 4 ++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/hw/net/igb.c b/hw/net/igb.c index 8089acfea4..18a22d2dd9 100644 --- a/hw/net/igb.c +++ b/hw/net/igb.c @@ -493,7 +493,7 @@ static void igb_qdev_reset_hold(Object *obj) trace_e1000e_cb_qdev_reset_hold(); - pcie_sriov_pf_disable_vfs(d); + pcie_sriov_pf_reset(d); igb_core_reset(&s->core); } diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 2860a9bed1..447c4de6fd 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -7116,7 +7116,7 @@ static void nvme_ctrl_reset(NvmeCtrl *n, NvmeResetType rst) } if (rst != NVME_RESET_CONTROLLER) { - pcie_sriov_pf_disable_vfs(pci_dev); + pcie_sriov_pf_reset(pci_dev); } } diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index da209b7f47..51b66d1bb3 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -249,16 +249,26 @@ void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, } -/* Reset SR/IOV VF Enable bit to trigger an unregister of all VFs */ -void pcie_sriov_pf_disable_vfs(PCIDevice *dev) +/* Reset SR/IOV */ +void pcie_sriov_pf_reset(PCIDevice *dev) { uint16_t sriov_cap = dev->exp.sriov_cap; - if (sriov_cap) { - uint32_t val = pci_get_byte(dev->config + sriov_cap + PCI_SRIOV_CTRL); - if (val & PCI_SRIOV_CTRL_VFE) { - val &= ~PCI_SRIOV_CTRL_VFE; - pcie_sriov_config_write(dev, sriov_cap + PCI_SRIOV_CTRL, val, 1); - } + if (!sriov_cap) { + return; + } + + pci_set_word(dev->config + sriov_cap + PCI_SRIOV_CTRL, 0); + unregister_vfs(dev); + + /* + * Default is to use 4K pages, software can modify it + * to any of the supported bits + */ + pci_set_word(dev->config + sriov_cap + PCI_SRIOV_SYS_PGSIZE, 0x1); + + for (uint16_t i = 0; i < PCI_NUM_REGIONS; i++) { + pci_set_quad(dev->config + sriov_cap + PCI_SRIOV_BAR + i * 4, + dev->exp.sriov_pf.vf_bar_type[i]); } } diff --git a/include/hw/pci/pcie_sriov.h b/include/hw/pci/pcie_sriov.h index 095fb0c9ed..b77eb7bf58 100644 --- a/include/hw/pci/pcie_sriov.h +++ b/include/hw/pci/pcie_sriov.h @@ -58,8 +58,8 @@ void pcie_sriov_pf_add_sup_pgsize(PCIDevice *dev, uint16_t opt_sup_pgsize); void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, uint32_t val, int len); -/* Reset SR/IOV VF Enable bit to unregister all VFs */ -void pcie_sriov_pf_disable_vfs(PCIDevice *dev); +/* Reset SR/IOV */ +void pcie_sriov_pf_reset(PCIDevice *dev); /* Get logical VF number of a VF - only valid for VFs */ uint16_t pcie_sriov_vf_number(PCIDevice *dev); -- Gitee From 291419101c5231899ed68117be5aed16e5666e61 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 28 Feb 2024 20:33:15 +0900 Subject: [PATCH 4/5] pcie_sriov: Do not reset NumVFs after disabling VFs commit 63eb76dda237843582f3616f4403ae795e471e17 upstream. The spec does not NumVFs is reset after disabling VFs except when resetting the PF. Clearing it is guest visible and out of spec, even though Linux doesn't rely on this value being preserved, so we never noticed. Fixes: 7c0fa8dff811 ("pcie: Add support for Single Root I/O Virtualization (SR/IOV)") Signed-off-by: Akihiko Odaki Message-Id: <20240228-reuse-v8-4-282660281e60@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Signed-off-by: Bin Guo --- hw/pci/pcie_sriov.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/pci/pcie_sriov.c b/hw/pci/pcie_sriov.c index 51b66d1bb3..e9b23221d7 100644 --- a/hw/pci/pcie_sriov.c +++ b/hw/pci/pcie_sriov.c @@ -215,7 +215,6 @@ static void unregister_vfs(PCIDevice *dev) g_free(dev->exp.sriov_pf.vf); dev->exp.sriov_pf.vf = NULL; dev->exp.sriov_pf.num_vfs = 0; - pci_set_word(dev->config + dev->exp.sriov_cap + PCI_SRIOV_NUM_VF, 0); } void pcie_sriov_config_write(PCIDevice *dev, uint32_t address, @@ -260,6 +259,8 @@ void pcie_sriov_pf_reset(PCIDevice *dev) pci_set_word(dev->config + sriov_cap + PCI_SRIOV_CTRL, 0); unregister_vfs(dev); + pci_set_word(dev->config + sriov_cap + PCI_SRIOV_NUM_VF, 0); + /* * Default is to use 4K pages, software can modify it * to any of the supported bits -- Gitee From a1a82ba4b16da05ddf591da3e38e130c16370aa0 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Wed, 28 Feb 2024 20:33:16 +0900 Subject: [PATCH 5/5] hw/pci: Always call pcie_sriov_pf_reset() commit 1a909e3dd85d5c57a0e6a7e3285a29e57574f80d upstream. Call pcie_sriov_pf_reset() from pci_do_device_reset() just as we do for msi_reset() and msix_reset() to prevent duplicating code for each SR-IOV PF. Signed-off-by: Akihiko Odaki Message-Id: <20240228-reuse-v8-5-282660281e60@daynix.com> Reviewed-by: Michael S. Tsirkin Signed-off-by: Michael S. Tsirkin Reviewed-by: Sriram Yagnaraman Signed-off-by: Bin Guo --- hw/net/igb.c | 2 -- hw/nvme/ctrl.c | 4 ---- hw/pci/pci.c | 1 + 3 files changed, 1 insertion(+), 6 deletions(-) diff --git a/hw/net/igb.c b/hw/net/igb.c index 18a22d2dd9..a6ad046084 100644 --- a/hw/net/igb.c +++ b/hw/net/igb.c @@ -488,12 +488,10 @@ static void igb_pci_uninit(PCIDevice *pci_dev) static void igb_qdev_reset_hold(Object *obj) { - PCIDevice *d = PCI_DEVICE(obj); IGBState *s = IGB(obj); trace_e1000e_cb_qdev_reset_hold(); - pcie_sriov_pf_reset(d); igb_core_reset(&s->core); } diff --git a/hw/nvme/ctrl.c b/hw/nvme/ctrl.c index 447c4de6fd..40159f39b8 100644 --- a/hw/nvme/ctrl.c +++ b/hw/nvme/ctrl.c @@ -7114,10 +7114,6 @@ static void nvme_ctrl_reset(NvmeCtrl *n, NvmeResetType rst) sctrl = &n->sec_ctrl_list.sec[i]; nvme_virt_set_state(n, le16_to_cpu(sctrl->scid), false); } - - if (rst != NVME_RESET_CONTROLLER) { - pcie_sriov_pf_reset(pci_dev); - } } if (rst != NVME_RESET_CONTROLLER) { diff --git a/hw/pci/pci.c b/hw/pci/pci.c index c49417abb2..b6314b6a1e 100644 --- a/hw/pci/pci.c +++ b/hw/pci/pci.c @@ -407,6 +407,7 @@ static void pci_do_device_reset(PCIDevice *dev) msi_reset(dev); msix_reset(dev); + pcie_sriov_pf_reset(dev); } /* -- Gitee