Merge branch 'pci/sysfs'

- In pci_write_legacy_io(), avoid out-of-bounds reads from the user buffer
  and fix incorrect ioport write data (1-byte writes on little-endian
  powerpc, 2- and 4-byte writes on big-endian powerpc) (Krzysztof
  Wilczyński)

- In pci_read_legacy_io(), fix incorrect ioport read data for 2- and 4-byte
  reads on big-endian powerpc (Krzysztof Wilczyński)

- Fix I/O port accessor argument order in Alpha pci_legacy_write()
  (Krzysztof Wilczyński)

- Avoid spurious runtime PM wakeup on config space accesses that are
  outside config space and fail before reaching PCI (Krzysztof Wilczyński)

- Return -EINVAL, not -ENODEV, for mmap of I/O BAR that fails because the
  arch doesn't support it, as we do for procfs (Krzysztof Wilczyński)

- Check for LOCKDOWN_PCI_ACCESS for legacy_io and legacy_mem, as we do for
  other config space accessors (Krzysztof Wilczyński)

- Tidy static resource attribute names and macros that build them
  (Krzysztof Wilczyński)

* pci/sysfs:
  PCI/sysfs: Add legacy I/O and memory attribute macros
  alpha/PCI: Make the suffix the first __pci_dev_resource_attr() parameter
  PCI/sysfs: Add pci_ prefix to static PCI resource attribute names
  PCI/sysfs: Add lockdown checks to legacy I/O and memory handlers
  PCI/sysfs: Return -EINVAL for unsupported I/O BAR mmap
  PCI/sysfs: Avoid spurious runtime PM wakeup on config space accesses
  alpha/PCI: Fix I/O port accessor argument order in pci_legacy_write()
  PCI/sysfs: Fix read byte order in pci_read_legacy_io()
  PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io()
This commit is contained in:
Bjorn Helgaas 2026-08-21 16:40:38 -05:00
commit c1a7b1aad5
3 changed files with 129 additions and 89 deletions

View File

@ -102,25 +102,25 @@ static int pci_mmap_resource_dense(struct file *filp, struct kobject *kobj,
return pci_mmap_resource(kobj, attr, vma, 0);
}
#define __pci_dev_resource_attr(_bar, _name, _suffix, _mmap) \
static const struct bin_attribute \
pci_dev_resource##_bar##_suffix##_attr = { \
.attr = { .name = __stringify(_name), .mode = 0600 }, \
.private = (void *)(unsigned long)(_bar), \
.mmap = (_mmap), \
#define __pci_dev_resource_attr(_suffix, _bar, _name, _mmap) \
static const struct bin_attribute \
pci_dev_resource##_bar##_suffix##_attr = { \
.attr = { .name = __stringify(_name), .mode = 0600 }, \
.private = (void *)(unsigned long)(_bar), \
.mmap = (_mmap), \
}
#define pci_dev_resource_attr(_bar) \
__pci_dev_resource_attr(_bar, resource##_bar,, \
pci_mmap_resource_dense)
#define pci_dev_resource_attr(_bar) \
__pci_dev_resource_attr(, _bar, resource##_bar, \
pci_mmap_resource_dense)
#define pci_dev_resource_sparse_attr(_bar) \
__pci_dev_resource_attr(_bar, resource##_bar##_sparse, _sparse, \
pci_mmap_resource_sparse)
__pci_dev_resource_attr(_sparse, _bar, resource##_bar##_sparse, \
pci_mmap_resource_sparse)
#define pci_dev_resource_dense_attr(_bar) \
__pci_dev_resource_attr(_bar, resource##_bar##_dense, _dense, \
pci_mmap_resource_dense)
__pci_dev_resource_attr(_dense, _bar, resource##_bar##_dense, \
pci_mmap_resource_dense)
static int sparse_mem_mmap_fits(struct pci_dev *pdev, int num)
{
@ -224,17 +224,17 @@ int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, size_t size)
switch(size) {
case 1:
outb(port, val);
outb(val, port);
return 1;
case 2:
if (port & 1)
return -EINVAL;
outw(port, val);
outw(val, port);
return 2;
case 4:
if (port & 3)
return -EINVAL;
outl(port, val);
outl(val, port);
return 4;
}
return -EINVAL;

View File

@ -626,19 +626,14 @@ int pci_legacy_write(struct pci_bus *bus, loff_t port, u32 val, size_t size)
return -ENXIO;
addr = hose->io_base_virt + port;
/* WARNING: The generic code is idiotic. It gets passed a pointer
* to what can be a 1, 2 or 4 byte quantity and always reads that
* as a u32, which means that we have to correct the location of
* the data read within those 32 bits for size 1 and 2
*/
switch(size) {
case 1:
out_8(addr, val >> 24);
out_8(addr, val);
return 1;
case 2:
if (port & 1)
return -EINVAL;
out_le16(addr, val >> 16);
out_le16(addr, val);
return 2;
case 4:
if (port & 3)

View File

@ -718,7 +718,7 @@ static ssize_t pci_read_config(struct file *filp, struct kobject *kobj,
else if (dev->hdr_type == PCI_HEADER_TYPE_CARDBUS)
size = 128;
if (off > size)
if (off >= size)
return 0;
if (off + count > size) {
size -= off;
@ -799,7 +799,7 @@ static ssize_t pci_write_config(struct file *filp, struct kobject *kobj,
add_taint(TAINT_USER, LOCKDEP_STILL_OK);
}
if (off > dev->cfg_size)
if (off >= dev->cfg_size)
return 0;
if (off + count > dev->cfg_size) {
size = dev->cfg_size - off;
@ -871,6 +871,27 @@ static const struct attribute_group pci_dev_config_attr_group = {
};
#ifdef HAVE_PCI_LEGACY
#define pci_legacy_resource_io_attr(_suffix, _size) \
static const struct bin_attribute pci_legacy_io##_suffix##_attr = { \
.attr = { .name = "legacy_io" __stringify(_suffix), .mode = 0600 }, \
.size = (_size), \
.read = pci_read_legacy_io, \
.write = pci_write_legacy_io, \
.f_mapping = iomem_get_mapping, \
.llseek = pci_llseek_resource_legacy, \
.mmap = pci_mmap_legacy_io, \
}
#define pci_legacy_resource_mem_attr(_suffix, _size) \
static const struct bin_attribute pci_legacy_mem##_suffix##_attr = { \
.attr = { .name = "legacy_mem" __stringify(_suffix), .mode = 0600 }, \
.size = (_size), \
.f_mapping = iomem_get_mapping, \
.llseek = pci_llseek_resource_legacy, \
.mmap = pci_mmap_legacy_mem, \
}
/**
* pci_read_legacy_io - read byte(s) from legacy I/O port space
* @filp: open sysfs file
@ -888,12 +909,30 @@ static ssize_t pci_read_legacy_io(struct file *filp, struct kobject *kobj,
char *buf, loff_t off, size_t count)
{
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
u32 val = 0;
int ret;
/* Only support 1, 2 or 4 byte accesses */
if (count != 1 && count != 2 && count != 4)
return -EINVAL;
return pci_legacy_read(bus, off, (u32 *)buf, count);
ret = pci_legacy_read(bus, off, &val, count);
if (ret < 0)
return ret;
switch (count) {
case 1:
buf[0] = *(u8 *)&val;
break;
case 2:
put_unaligned_le16(*(u16 *)&val, buf);
break;
case 4:
put_unaligned_le32(val, buf);
break;
}
return ret;
}
/**
@ -913,12 +952,24 @@ static ssize_t pci_write_legacy_io(struct file *filp, struct kobject *kobj,
char *buf, loff_t off, size_t count)
{
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
u32 val;
/* Only support 1, 2 or 4 byte accesses */
if (count != 1 && count != 2 && count != 4)
/* Only support 1, 2 or 4 byte accesses. */
switch (count) {
case 1:
val = *(u8 *)buf;
break;
case 2:
val = get_unaligned_le16(buf);
break;
case 4:
val = get_unaligned_le32(buf);
break;
default:
return -EINVAL;
}
return pci_legacy_write(bus, off, *(u32 *)buf, count);
return pci_legacy_write(bus, off, val, count);
}
/**
@ -937,6 +988,11 @@ static int pci_mmap_legacy_mem(struct file *filp, struct kobject *kobj,
struct vm_area_struct *vma)
{
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
int ret;
ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;
return pci_mmap_legacy_page_range(bus, vma, pci_mmap_mem);
}
@ -957,6 +1013,11 @@ static int pci_mmap_legacy_io(struct file *filp, struct kobject *kobj,
struct vm_area_struct *vma)
{
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
int ret;
ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;
return pci_mmap_legacy_page_range(bus, vma, pci_mmap_io);
}
@ -973,6 +1034,11 @@ static inline umode_t __pci_legacy_is_visible(struct kobject *kobj,
bool sparse)
{
struct pci_bus *bus = to_pci_bus(kobj_to_dev(kobj));
int ret;
ret = security_locked_down(LOCKDOWN_PCI_ACCESS);
if (ret)
return ret;
if (pci_legacy_has_sparse(bus, type) != sparse)
return 0;
@ -1014,41 +1080,11 @@ static loff_t pci_llseek_resource_legacy(struct file *filep,
return fixed_size_llseek(filep, offset, whence, attr->size);
}
static const struct bin_attribute pci_legacy_io_attr = {
.attr = { .name = "legacy_io", .mode = 0600 },
.size = PCI_LEGACY_IO_SIZE,
.read = pci_read_legacy_io,
.write = pci_write_legacy_io,
.mmap = pci_mmap_legacy_io,
.llseek = pci_llseek_resource_legacy,
.f_mapping = iomem_get_mapping,
};
pci_legacy_resource_io_attr(, PCI_LEGACY_IO_SIZE);
pci_legacy_resource_io_attr(_sparse, PCI_LEGACY_IO_SIZE << 5);
static const struct bin_attribute pci_legacy_io_sparse_attr = {
.attr = { .name = "legacy_io_sparse", .mode = 0600 },
.size = PCI_LEGACY_IO_SIZE << 5,
.read = pci_read_legacy_io,
.write = pci_write_legacy_io,
.mmap = pci_mmap_legacy_io,
.llseek = pci_llseek_resource_legacy,
.f_mapping = iomem_get_mapping,
};
static const struct bin_attribute pci_legacy_mem_attr = {
.attr = { .name = "legacy_mem", .mode = 0600 },
.size = PCI_LEGACY_MEM_SIZE,
.mmap = pci_mmap_legacy_mem,
.llseek = pci_llseek_resource_legacy,
.f_mapping = iomem_get_mapping,
};
static const struct bin_attribute pci_legacy_mem_sparse_attr = {
.attr = { .name = "legacy_mem_sparse", .mode = 0600 },
.size = PCI_LEGACY_MEM_SIZE << 5,
.mmap = pci_mmap_legacy_mem,
.llseek = pci_llseek_resource_legacy,
.f_mapping = iomem_get_mapping,
};
pci_legacy_resource_mem_attr(, PCI_LEGACY_MEM_SIZE);
pci_legacy_resource_mem_attr(_sparse, PCI_LEGACY_MEM_SIZE << 5);
static const struct bin_attribute *const pci_legacy_io_attrs[] = {
&pci_legacy_io_attr,
@ -1239,16 +1275,25 @@ static loff_t pci_llseek_resource(struct file *filep,
* attribute, it's not going to work, so override it as well.
*/
#if arch_can_pci_mmap_io()
# define __PCI_RESOURCE_IO_MMAP_ATTRS \
.f_mapping = iomem_get_mapping, \
.llseek = pci_llseek_resource, \
# define __PCI_RESOURCE_IO_MMAP_ATTRS \
.f_mapping = iomem_get_mapping, \
.llseek = pci_llseek_resource, \
.mmap = pci_mmap_resource_uc,
#else
# define __PCI_RESOURCE_IO_MMAP_ATTRS
static int pci_mmap_resource_io_unsupported(struct file *filp,
struct kobject *kobj,
const struct bin_attribute *attr,
struct vm_area_struct *vma)
{
return -EINVAL;
}
# define __PCI_RESOURCE_IO_MMAP_ATTRS \
.mmap = pci_mmap_resource_io_unsupported,
#endif
#define pci_dev_resource_io_attr(_bar) \
static const struct bin_attribute dev_resource##_bar##_io_attr = { \
static const struct bin_attribute pci_dev_resource##_bar##_io_attr = { \
.attr = { .name = "resource" __stringify(_bar), .mode = 0600 }, \
.private = (void *)(unsigned long)(_bar), \
.read = pci_read_resource, \
@ -1257,7 +1302,7 @@ static const struct bin_attribute dev_resource##_bar##_io_attr = { \
}
#define pci_dev_resource_uc_attr(_bar) \
static const struct bin_attribute dev_resource##_bar##_uc_attr = { \
static const struct bin_attribute pci_dev_resource##_bar##_uc_attr = { \
.attr = { .name = "resource" __stringify(_bar), .mode = 0600 }, \
.private = (void *)(unsigned long)(_bar), \
.f_mapping = iomem_get_mapping, \
@ -1265,8 +1310,8 @@ static const struct bin_attribute dev_resource##_bar##_uc_attr = { \
.mmap = pci_mmap_resource_uc, \
}
#define pci_dev_resource_wc_attr(_bar) \
static const struct bin_attribute dev_resource##_bar##_wc_attr = { \
#define pci_dev_resource_wc_attr(_bar) \
static const struct bin_attribute pci_dev_resource##_bar##_wc_attr = { \
.attr = { .name = "resource" __stringify(_bar) "_wc", .mode = 0600 }, \
.private = (void *)(unsigned long)(_bar), \
.f_mapping = iomem_get_mapping, \
@ -1352,32 +1397,32 @@ pci_dev_resource_wc_attr(4);
pci_dev_resource_wc_attr(5);
static const struct bin_attribute *const pci_dev_resource_io_attrs[] = {
&dev_resource0_io_attr,
&dev_resource1_io_attr,
&dev_resource2_io_attr,
&dev_resource3_io_attr,
&dev_resource4_io_attr,
&dev_resource5_io_attr,
&pci_dev_resource0_io_attr,
&pci_dev_resource1_io_attr,
&pci_dev_resource2_io_attr,
&pci_dev_resource3_io_attr,
&pci_dev_resource4_io_attr,
&pci_dev_resource5_io_attr,
NULL,
};
static const struct bin_attribute *const pci_dev_resource_uc_attrs[] = {
&dev_resource0_uc_attr,
&dev_resource1_uc_attr,
&dev_resource2_uc_attr,
&dev_resource3_uc_attr,
&dev_resource4_uc_attr,
&dev_resource5_uc_attr,
&pci_dev_resource0_uc_attr,
&pci_dev_resource1_uc_attr,
&pci_dev_resource2_uc_attr,
&pci_dev_resource3_uc_attr,
&pci_dev_resource4_uc_attr,
&pci_dev_resource5_uc_attr,
NULL,
};
static const struct bin_attribute *const pci_dev_resource_wc_attrs[] = {
&dev_resource0_wc_attr,
&dev_resource1_wc_attr,
&dev_resource2_wc_attr,
&dev_resource3_wc_attr,
&dev_resource4_wc_attr,
&dev_resource5_wc_attr,
&pci_dev_resource0_wc_attr,
&pci_dev_resource1_wc_attr,
&pci_dev_resource2_wc_attr,
&pci_dev_resource3_wc_attr,
&pci_dev_resource4_wc_attr,
&pci_dev_resource5_wc_attr,
NULL,
};