Harden PCI resource array indexing
Subsystems (2)
v3 cover Harden PCI resource array indexing
This is a v3, spiritually successing "v1" the original implementation of length checking [1], and "v2", the fix squashed in by Krzyzstof [2].
Changes in v3:
- Use PCI_NUM_RESOURCES as the devres array length and provide that as a bugfix to be backported. (Damien)
[1] https://lore.kernel.org/linux-pci/[email protected]/
[2] https://lore.kernel.org/linux-pci/[email protected]/
Philipp Stanner (2):
PCI: Fix wrong length of devres array
PCI: Check BAR index for validity
drivers/pci/devres.c | 18 +++++++++++++++--- drivers/pci/iomap.c | 29 +++++++++++++++++++++-------- drivers/pci/pci.c | 6 ++++++ drivers/pci/pci.h | 16 ++++++++++++++++ 4 files changed, 58 insertions(+), 11 deletions(-)
Signature · 1 line
2.48.1
PATCH v3 1/2 PCI: Fix wrong length of devres array
The array for the iomapping cookie addresses has a length of PCI_STD_NUM_BARS. This constant, however, only describes standard BARs; while PCI can allow for additional, special BARs.
The total number of PCI resources is described by constant PCI_NUM_RESOURCES, which is also used in, e.g., pci_select_bars().
Thus, the devres array has so far been too small.
Change the length of the devres array to PCI_NUM_RESOURCES.
--- drivers/pci/devres.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
drivers/pci/devres.c +1 -1
@@ -40,7 +40,7 @@* Legacy struct storing addresses to whole mapped BARs. */ struct pcim_iomap_devres { -void __iomem *table[PCI_STD_NUM_BARS ]; +void __iomem *table[PCI_NUM_RESOURCES ]; };/* Used to restore the old INTx state on driver detach. */
Signature · 1 line
2.48.1
PATCH v3 2/2 PCI: Check BAR index for validity
In reply to Philipp StannerMany functions in PCI use accessor macros such as pci_resource_len(), which take a BAR index. That index, however, is never checked for validity, potentially resulting in undefined behavior by overflowing the array pci_dev.resource in the macro pci_resource_n().
Since many users of those macros directly assign the accessed value to an unsigned integer, the macros cannot be changed easily anymore to return -EINVAL for invalid indexes. Consequently, the problem has to be mitigated in higher layers.
Add pci_bar_index_valid(). Use it where appropriate.
--- drivers/pci/devres.c | 16 ++++++++++++++-- drivers/pci/iomap.c | 29 +++++++++++++++++++++-------- drivers/pci/pci.c | 6 ++++++ drivers/pci/pci.h | 16 ++++++++++++++++ 4 files changed, 57 insertions(+), 10 deletions(-)
drivers/pci/devres.c +14 -2
@@ -577,7 +577,7 @@ static int pcim_add_mapping_to_legacy_table(struct pci_dev *pdev, {void __iomem **legacy_iomap_table; -if (bar >=PCI_STD_NUM_BARS ) +if (!pci_bar_index_is_valid (bar))return -EINVAL ; legacy_iomap_table = (void __iomem **)pcim_iomap_table (pdev);
@@ -622,7 +622,7 @@ static void pcim_remove_bar_from_legacy_table(struct pci_dev *pdev, int bar) {void __iomem **legacy_iomap_table; -if (bar >=PCI_STD_NUM_BARS ) +if (!pci_bar_index_is_valid (bar))return ; legacy_iomap_table = (void __iomem **)pcim_iomap_table (pdev);
@@ -655,6 +655,9 @@ void __iomem *pcim_iomap(struct pci_dev *pdev, int bar, unsigned long maxlen)void __iomem *mapping;struct pcim_addr_devres *res; +if (!pci_bar_index_is_valid (bar)) +return NULL ; + res =pcim_addr_devres_alloc (pdev);if (!res)return NULL ;
@@ -722,6 +725,9 @@ void __iomem *pcim_iomap_region(struct pci_dev *pdev, int bar,int ret;struct pcim_addr_devres *res; +if (!pci_bar_index_is_valid (bar)) +return IOMEM_ERR_PTR (-EINVAL ); + res =pcim_addr_devres_alloc (pdev);if (!res)return IOMEM_ERR_PTR (-ENOMEM );
@@ -823,6 +829,9 @@ static int _pcim_request_region(struct pci_dev *pdev, int bar, const char *name,int ret;struct pcim_addr_devres *res; +if (!pci_bar_index_is_valid (bar)) +return -EINVAL ; + res =pcim_addr_devres_alloc (pdev);if (!res)return -ENOMEM ;
@@ -991,6 +1000,9 @@ void __iomem *pcim_iomap_range(struct pci_dev *pdev, int bar,void __iomem *mapping;struct pcim_addr_devres *res; +if (!pci_bar_index_is_valid (bar)) +return IOMEM_ERR_PTR (-EINVAL ); + res =pcim_addr_devres_alloc (pdev);if (!res)return IOMEM_ERR_PTR (-ENOMEM );
drivers/pci/iomap.c +21 -8
@@ -9,6 +9,8 @@#include <linux/export.h> +#include "pci.h" /* for pci_bar_index_is_valid() */ +/** * pci_iomap_range - create a virtual mapping cookie for a PCI BAR * @dev: PCI device that owns the BAR
@@ -33,12 +35,19 @@ void __iomem *pci_iomap_range(struct pci_dev *dev, unsigned long offset, unsigned long maxlen) { -resource_size_t start =pci_resource_start (dev, bar); -resource_size_t len =pci_resource_len (dev, bar); -unsigned long flags =pci_resource_flags (dev, bar); +resource_size_t start, len; +unsigned long flags; + +if (!pci_bar_index_is_valid (bar)) +return NULL ; + + start =pci_resource_start (dev, bar); + len =pci_resource_len (dev, bar); + flags =pci_resource_flags (dev, bar);if (len <= offset || !start)return NULL ; + len -= offset; start += offset;if (maxlen && len > maxlen)
@@ -77,16 +86,20 @@ void __iomem *pci_iomap_wc_range(struct pci_dev *dev, unsigned long offset, unsigned long maxlen) { -resource_size_t start =pci_resource_start (dev, bar); -resource_size_t len =pci_resource_len (dev, bar); -unsigned long flags =pci_resource_flags (dev, bar); +resource_size_t start, len; +unsigned long flags; - -if (flags &IORESOURCE_IO ) +if (!pci_bar_index_is_valid (bar))return NULL ; + start =pci_resource_start (dev, bar); + len =pci_resource_len (dev, bar); + flags =pci_resource_flags (dev, bar); +if (len <= offset || !start)return NULL ; +if (flags &IORESOURCE_IO ) +return NULL ; len -= offset; start += offset;
drivers/pci/pci.c +6 -0
@@ -3921,6 +3921,9 @@ EXPORT_SYMBOL(pci_enable_atomic_ops_to_root);*/ void pci_release_region (struct pci_dev *pdev,int bar) { +if (!pci_bar_index_is_valid (bar)) +return ; +/* * This is done for backwards compatibility, because the old PCI devres * API had a mode in which the function became managed if it had been
@@ -3965,6 +3968,9 @@ EXPORT_SYMBOL(pci_release_region);static int __pci_request_region (struct pci_dev *pdev,int bar,const char *name,int exclusive) { +if (!pci_bar_index_is_valid (bar)) +return -EINVAL ; +if (pci_is_managed (pdev)) {if (exclusive ==IORESOURCE_EXCLUSIVE )return pcim_request_region_exclusive (pdev, bar, name);
drivers/pci/pci.h +16 -0
@@ -167,6 +167,22 @@ static inline void pci_wakeup_event(struct pci_dev *dev)pm_wakeup_event (&dev->dev,100 ); } +/** +* pci_bar_index_is_valid - check wehether a BAR index is within valid range +* @bar: the bar index +* +* Protects against overflowing &struct pci_dev.resource array. +* +* Return: true for valid index, false otherwise +*/ +static inline bool pci_bar_index_is_valid (int bar) +{ +if (bar >=0 || bar <PCI_NUM_RESOURCES ) +return true; + +return false; +} +static inline bool pci_has_subordinate (struct pci_dev *pci_dev) {return !!(pci_dev->subordinate);
Signature · 1 line
2.48.1
Bjorn Helgaas
AppliedQuoted 14 lines · Philipp Stanner
This is a v3, spiritually successing "v1" the original implementation of length checking [1], and "v2", the fix squashed in by Krzyzstof [2].
Changes in v3:
- UsePCI_NUM_RESOURCESas the devres array length and provide that as a bugfix to be backported. (Damien)[1] https://lore.kernel.org/linux-pci/[email protected]/
[2] https://lore.kernel.org/linux-pci/[email protected]/Philipp Stanner (2):
PCI: Fix wrong length of devres array
PCI: Check BAR index for validity
Quoted 5 lines
drivers/pci/devres.c | 18 +++++++++++++++--- drivers/pci/iomap.c | 29 +++++++++++++++++++++-------- drivers/pci/pci.c | 6 ++++++ drivers/pci/pci.h | 16 ++++++++++++++++ 4 files changed, 58 insertions(+), 11 deletions(-)
Applied to pci/devres for v6.15, thanks, Philipp!