* [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io()
@ 2026-06-16 16:31 Krzysztof Wilczyński
2026-06-16 16:31 ` [PATCH 2/2] PCI/sysfs: Fix read byte order in pci_read_legacy_io() Krzysztof Wilczyński
0 siblings, 1 reply; 2+ messages in thread
From: Krzysztof Wilczyński @ 2026-06-16 16:31 UTC (permalink / raw)
To: Madhavan Srinivasan, Bjorn Helgaas, Michael Ellerman
Cc: Bjorn Helgaas, Nicholas Piggin, Christophe Leroy,
Ilpo Järvinen, Kees Cook, linuxppc-dev, linux-kernel,
linux-pci
pci_write_legacy_io() loads 4 bytes from the kernfs write buffer
regardless of how many bytes userspace wrote:
if (count != 1 && count != 2 && count != 4)
return -EINVAL;
return pci_legacy_write(bus, off, *(u32 *)buf, count);
kernfs_fop_write_iter() allocates the buffer with kmalloc(len + 1),
so a 1-byte write to the legacy_io sysfs file allocates 2 bytes and
the unconditional u32 load reads up to 2 bytes past the end of the
allocation, which KASAN reports as a slab-out-of-bounds read.
Similarly, a 2-byte write overreads by 1 byte.
Thus, read only the number of bytes requested using get_unaligned_le16()
and get_unaligned_le32() for the 2 and 4 byte cases, interpreting the
buffer as little-endian to match the byte ordering of PCI I/O port
space.
The PowerPC implementation previously compensated for the generic
code's native-endian 32-bit load by shifting the value into place
for the 1 and 2 byte cases. The shifts were only correct on
big-endian kernels.
On little-endian PowerPC (POWER8 and later), they extracted the wrong
bytes, so a 1-byte write wrote an out-of-bounds byte instead of the
requested value. On big-endian, the native load also caused out_le16()
and out_le32() to reverse the user's bytes on the wire for 2 and 4 byte
writes. The little-endian helpers resolve both issues, so the shifts
are removed.
No changes are needed for the Alpha platform.
The legacy_io file is root-only and exists only on Alpha and PowerPC,
the two architectures that define HAVE_PCI_LEGACY.
Cc: stable@vger.kernel.org
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
---
arch/powerpc/kernel/pci-common.c | 9 ++-------
drivers/pci/pci-sysfs.c | 18 +++++++++++++++---
2 files changed, 17 insertions(+), 10 deletions(-)
diff --git a/arch/powerpc/kernel/pci-common.c b/arch/powerpc/kernel/pci-common.c
index 8efe95a0c4ff..fdc57fa2ece6 100644
--- a/arch/powerpc/kernel/pci-common.c
+++ b/arch/powerpc/kernel/pci-common.c
@@ -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)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index d37860841260..b56000ba3a33 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -933,12 +933,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);
}
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* [PATCH 2/2] PCI/sysfs: Fix read byte order in pci_read_legacy_io()
2026-06-16 16:31 [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io() Krzysztof Wilczyński
@ 2026-06-16 16:31 ` Krzysztof Wilczyński
0 siblings, 0 replies; 2+ messages in thread
From: Krzysztof Wilczyński @ 2026-06-16 16:31 UTC (permalink / raw)
To: Madhavan Srinivasan, Bjorn Helgaas, Michael Ellerman
Cc: Bjorn Helgaas, Nicholas Piggin, Christophe Leroy,
Ilpo Järvinen, Kees Cook, linuxppc-dev, linux-kernel,
linux-pci
pci_read_legacy_io() passes the sysfs buffer directly to pci_legacy_read():
return pci_legacy_read(bus, off, (u32 *)buf, count);
The PowerPC implementation stores the result as a native-endian integer:
*((u16 *)val) = in_le16(addr);
On big-endian PowerPC this stores the bytes in the wrong order, so
a 2-byte read of a device register returns different bytes than two
1-byte reads at the same addresses. The same applies to 4-byte
reads. On little-endian the native byte order already matches PCI
I/O port byte order, so the conversion is a no-op.
Thus, let pci_legacy_read() store into a local u32 variable, then
copy the I/O port value to the sysfs buffer using put_unaligned_le16()
and put_unaligned_le32() for the 2 and 4 byte cases, converting from
the native integer to little-endian byte order matching PCI I/O port
space.
No changes are needed for the Alpha platform.
The legacy_io file is root-only and exists only on Alpha and PowerPC,
the two architectures that define HAVE_PCI_LEGACY.
Cc: stable@vger.kernel.org
Signed-off-by: Krzysztof Wilczyński <kwilczynski@kernel.org>
---
drivers/pci/pci-sysfs.c | 20 +++++++++++++++++++-
1 file changed, 19 insertions(+), 1 deletion(-)
diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c
index b56000ba3a33..2354d09fd3fa 100644
--- a/drivers/pci/pci-sysfs.c
+++ b/drivers/pci/pci-sysfs.c
@@ -908,12 +908,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;
}
/**
--
2.54.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-06-16 16:31 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-16 16:31 [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io() Krzysztof Wilczyński
2026-06-16 16:31 ` [PATCH 2/2] PCI/sysfs: Fix read byte order in pci_read_legacy_io() Krzysztof Wilczyński
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox