* [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
2026-06-16 16:42 ` [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io() sashiko-bot
0 siblings, 2 replies; 5+ 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] 5+ 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
2026-06-16 16:36 ` sashiko-bot
2026-06-16 16:42 ` [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io() sashiko-bot
1 sibling, 1 reply; 5+ 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] 5+ messages in thread* Re: [PATCH 2/2] PCI/sysfs: Fix read byte order in pci_read_legacy_io()
2026-06-16 16:31 ` [PATCH 2/2] PCI/sysfs: Fix read byte order in pci_read_legacy_io() Krzysztof Wilczyński
@ 2026-06-16 16:36 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-06-16 16:36 UTC (permalink / raw)
To: Krzysztof Wilczyński; +Cc: 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>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616163131.2763281-1-kwilczynski@kernel.org?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_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 ` [PATCH 2/2] PCI/sysfs: Fix read byte order in pci_read_legacy_io() Krzysztof Wilczyński
@ 2026-06-16 16:42 ` sashiko-bot
2026-06-16 16:47 ` Krzysztof Wilczyński
1 sibling, 1 reply; 5+ messages in thread
From: sashiko-bot @ 2026-06-16 16:42 UTC (permalink / raw)
To: Krzysztof Wilczyński; +Cc: linux-pci
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The Alpha implementation of `pci_legacy_write` passes arguments to `outb`, `outw`, and `outl` in the wrong order, causing arbitrary I/O port writes.
--
commit 21e9260d6465f8f037bb9292c32c44b565eca000
Author: Krzysztof Wilczyński <kwilczynski@kernel.org>
PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io()
This commit addresses an out-of-bounds read in pci_write_legacy_io() by
only reading the requested number of bytes using little-endian accessors.
It also removes byte-shifting workarounds in the PowerPC architecture
code that are no longer necessary.
> 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.
[Severity: High]
This is a pre-existing issue, but the claim that no changes are needed for
Alpha doesn't seem quite right.
Looking at pci_legacy_write() in arch/alpha/kernel/pci-sysfs.c, it calls the
I/O port functions with the port and value reversed:
arch/alpha/kernel/pci-sysfs.c:pci_legacy_write() {
...
switch(size) {
case 1:
outb(port, val);
return 1;
case 2:
if (port & 1)
return -EINVAL;
outw(port, val);
return 2;
case 4:
if (port & 3)
return -EINVAL;
outl(port, val);
return 4;
}
...
}
Since Alpha's outb(), outw(), and outl() expect arguments in the order
(value, port), doesn't passing port as the value and val as the port
address incorrectly use user-supplied data as the target I/O port address?
Could this allow arbitrary I/O port writes system-wide?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260616163131.2763281-1-kwilczynski@kernel.org?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io()
2026-06-16 16:42 ` [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io() sashiko-bot
@ 2026-06-16 16:47 ` Krzysztof Wilczyński
0 siblings, 0 replies; 5+ messages in thread
From: Krzysztof Wilczyński @ 2026-06-16 16:47 UTC (permalink / raw)
To: sashiko-reviews; +Cc: linux-pci
Hello,
> [Severity: High]
> This is a pre-existing issue, but the claim that no changes are needed for
> Alpha doesn't seem quite right.
>
> Looking at pci_legacy_write() in arch/alpha/kernel/pci-sysfs.c, it calls the
> I/O port functions with the port and value reversed:
>
> arch/alpha/kernel/pci-sysfs.c:pci_legacy_write() {
> ...
> switch(size) {
> case 1:
> outb(port, val);
> return 1;
> case 2:
> if (port & 1)
> return -EINVAL;
> outw(port, val);
> return 2;
> case 4:
> if (port & 3)
> return -EINVAL;
> outl(port, val);
> return 4;
> }
> ...
> }
>
> Since Alpha's outb(), outw(), and outl() expect arguments in the order
> (value, port), doesn't passing port as the value and val as the port
> address incorrectly use user-supplied data as the target I/O port address?
A known issue for which a fix has been sent:
https://lore.kernel.org/linux-pci/20260612232400.585195-1-kwilczynski@kernel.org/
Thank you!
Krzysztof
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-06-16 16:47 UTC | newest]
Thread overview: 5+ 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
2026-06-16 16:36 ` sashiko-bot
2026-06-16 16:42 ` [PATCH 1/2] PCI/sysfs: Fix out-of-bounds read in pci_write_legacy_io() sashiko-bot
2026-06-16 16:47 ` Krzysztof Wilczyński
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.