Re: [PATCH v2 2/3] PCI: Prevent overflow in proc_bus_pci_write()

From: duziming

Date: Sun Jan 04 2026 - 02:18:11 EST



在 2026/1/1 1:04, Bjorn Helgaas 写道:
On Wed, Dec 31, 2025 at 11:31:47AM +0200, Ilpo Järvinen wrote:
On Tue, 30 Dec 2025, duziming wrote:
在 2025/12/30 2:07, Bjorn Helgaas 写道:
[+cc Krzysztof; I thought we looked at this long ago?]

On Wed, Dec 24, 2025 at 05:27:18PM +0800, Ziming Du wrote:
From: Yongqiang Liu <liuyongqiang13@xxxxxxxxxx>

When the value of ppos over the INT_MAX, the pos is over set to a negtive
value which will be passed to get_user() or pci_user_write_config_dword().
Unexpected behavior such as a softlock will happen as follows:
s/negtive/negative/
s/softlock/soft lockup/ to match message below
Thanks for pointing out the ambiguous parts.
s/ppos/pos/ (or fix this to refer to "*ppos", which I think is what
you're referring to)

I guess the point is that proc_bus_pci_write() takes a "loff_t *ppos",
loff_t is a signed type, and negative read/write offsets are invalid.
Actually, the *loff_t *ppos *passed in is not a negative value. The root cause
of the issue

lies in the cast *int* *pos = *ppos*. When the value of **ppos* over the
INT_MAX, the pos is over set

to a negative value. This negative *pos* then propagates through subsequent
logic, leading to the observed errors.

If this is easily reproducible with "dd" or similar, could maybe
include a sample command line?
We reproduced the issue using the following POC:

    #include <stdio.h>

    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/uio.h>

    int main() {
    int fd = open("/proc/bus/pci/00/02.0", O_RDWR);
    if (fd < 0) {
        perror("open failed");
        return 1;
    }
    char data[] = "926b7719201054f37a1d9d391e862c";
    off_t offset = 0x80800001;
    struct iovec iov = {
        .iov_base = data,
        .iov_len = 0xf
    };
    pwritev(fd, &iov, 1, offset);
    return 0;
}

watchdog: BUG: soft lockup - CPU#0 stuck for 130s! [syz.3.109:3444]
RIP: 0010:_raw_spin_unlock_irq+0x17/0x30
Call Trace:
<TASK>
pci_user_write_config_dword+0x126/0x1f0
proc_bus_pci_write+0x273/0x470
proc_reg_write+0x1b6/0x280
do_iter_write+0x48e/0x790
vfs_writev+0x125/0x4a0
__x64_sys_pwritev+0x1e2/0x2a0
do_syscall_64+0x59/0x110
entry_SYSCALL_64_after_hwframe+0x78/0xe2

Fix this by add check for the pos.

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Yongqiang Liu <liuyongqiang13@xxxxxxxxxx>
Signed-off-by: Ziming Du <duziming2@xxxxxxxxxx>
---
drivers/pci/proc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/pci/proc.c b/drivers/pci/proc.c
index 9348a0fb8084..200d42feafd8 100644
--- a/drivers/pci/proc.c
+++ b/drivers/pci/proc.c
@@ -121,7 +121,7 @@ static ssize_t proc_bus_pci_write(struct file *file,
const char __user *buf,
if (ret)
return ret;
- if (pos >= size)
+ if (pos >= size || pos < 0)
return 0;
I see a few similar cases that look like this; maybe we should do the
same?

if (pos < 0)
return -EINVAL;

Looks like proc_bus_pci_read() has the same issue?
proc_bus_pci_read() may also trigger similar issue as mentioned by Ilpo
Järvinen in

https://lore.kernel.org/linux-pci/e5a91378-4a41-32fb-00c6-2810084581bd@xxxxxxxxxxxxxxx/

However, it does not result in an overflow to a negative number.
Why does the cast has to happen first here?

This would ensure _correctness_ without any false alignment issues for
large numbers:

int pos;
int size = dev->cfg_size;

...
if (*ppos > INT_MAX)
Isn't *ppos a signed quantity? If so, wouldn't we want to check for
"*ppos < 0"?

If *ppos < 0, it will be discarded in the previous process, just like in do_pwritev(), where it returns -EINVAL

when pos is negative. So we think that here using "*ppos > INT_MAX" might be more reasonable.