]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
PCI: dwc: ep: Prevent changing BAR size/flags in pci_epc_set_bar()
authorNiklas Cassel <cassel@kernel.org>
Fri, 13 Dec 2024 14:33:03 +0000 (15:33 +0100)
committerKrzysztof Wilczyński <kwilczynski@kernel.org>
Wed, 18 Dec 2024 21:51:19 +0000 (21:51 +0000)
In commit 4284c88fff0e ("PCI: designware-ep: Allow pci_epc_set_bar() update
inbound map address") set_bar() was modified to support dynamically
changing the backing physical address of a BAR that was already configured.

This means that set_bar() can be called twice, without ever calling
clear_bar() (as calling clear_bar() would clear the BAR's PCI address
assigned by the host).

This can only be done if the new BAR size/flags does not differ from the
existing BAR configuration. Add these missing checks.

If we allow set_bar() to set e.g. a new BAR size that differs from the
existing BAR size, the new address translation range will be smaller than
the BAR size already determined by the host, which would mean that a read
past the new BAR size would pass the iATU untranslated, which could allow
the host to read memory not belonging to the new struct pci_epf_bar.

While at it, add comments which clarifies the support for dynamically
changing the physical address of a BAR. (Which was also missing.)

Fixes: 4284c88fff0e ("PCI: designware-ep: Allow pci_epc_set_bar() update inbound map address")
Link: https://lore.kernel.org/r/20241213143301.4158431-10-cassel@kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
Reviewed-by: Manivannan Sadhasivam <manivannan.sadhasivam@linaro.org>
Cc: stable@vger.kernel.org
drivers/pci/controller/dwc/pcie-designware-ep.c

index bad588ef69a41a685b31edc831d2080be9164376..44a617d54b15f1e197d53ac0613f8db46d958b83 100644 (file)
@@ -222,8 +222,28 @@ static int dw_pcie_ep_set_bar(struct pci_epc *epc, u8 func_no, u8 vfunc_no,
        if ((flags & PCI_BASE_ADDRESS_MEM_TYPE_64) && (bar & 1))
                return -EINVAL;
 
-       if (ep->epf_bar[bar])
+       /*
+        * Certain EPF drivers dynamically change the physical address of a BAR
+        * (i.e. they call set_bar() twice, without ever calling clear_bar(), as
+        * calling clear_bar() would clear the BAR's PCI address assigned by the
+        * host).
+        */
+       if (ep->epf_bar[bar]) {
+               /*
+                * We can only dynamically change a BAR if the new BAR size and
+                * BAR flags do not differ from the existing configuration.
+                */
+               if (ep->epf_bar[bar]->barno != bar ||
+                   ep->epf_bar[bar]->size != size ||
+                   ep->epf_bar[bar]->flags != flags)
+                       return -EINVAL;
+
+               /*
+                * When dynamically changing a BAR, skip writing the BAR reg, as
+                * that would clear the BAR's PCI address assigned by the host.
+                */
                goto config_atu;
+       }
 
        reg = PCI_BASE_ADDRESS_0 + (4 * bar);