* [PATCH v2] misc: ibmasm: Fix static and dynamic out-of-bounds MMIO accesses
2026-06-23 7:16 ` Greg KH
@ 2026-06-23 11:40 ` w15303746062
2026-06-23 11:53 ` Greg KH
2026-06-23 11:47 ` [PATCH] misc: ibmasm: Fix out-of-bounds MMIO access during module load Mingyu Wang
1 sibling, 1 reply; 6+ messages in thread
From: w15303746062 @ 2026-06-23 11:40 UTC (permalink / raw)
To: arnd, gregkh; +Cc: linux-kernel, kees, stable, Mingyu Wang
From: Mingyu Wang <25181214217@stu.xidian.edu.cn>
The ibmasm driver maps PCI BAR 0 without verifying if the hardware-provided
resource length is sufficient.
When evaluating the driver against emulated hardware or during virtual
device fuzzing, a malformed device may expose a significantly undersized
BAR 0. This leads to two distinct out-of-bounds (OOB) MMIO access vectors:
1. Static OOB: The driver hardcodes access to INTR_CONTROL_REGISTER
(offset 0x13A4) during probe.
2. Dynamic OOB: The driver reads dynamic Message Frame Addresses (MFA)
from hardware queues and uses them directly as offsets to dereference
I2O messages via get_i2o_message(). A malicious MFA can cause the
driver to access memory far beyond the mapped BAR.
If an OOB access triggers a #PF during module probe while holding the
idempotent_init_module() lock, it leaves the module loading subsystem
in a corrupted state, leading to a cascading global soft lockup.
Fix this comprehensively by:
- Storing the mapped resource size in 'struct service_processor'.
- Ensuring the BAR size covers the highest statically accessed register
(INTR_CONTROL_REGISTER) during probe.
- Validating all dynamic MFA offsets against the mapped size before
dereferencing to prevent dynamic OOB accesses.
Fixes: bdbeed75b288 ("pci: use pci_ioremap_bar() in drivers/misc")
Cc: stable@vger.kernel.org
Signed-off-by: Mingyu Wang <25181214217@stu.xidian.edu.cn>
---
Changes in v2:
- Added dynamic MFA bounds checking in get_i2o_message() to prevent runtime OOB (prompted by Greg KH).
- Implemented hardware mailbox deadlock prevention by releasing MFA if bounds check fails.
- Fixed potential unsigned integer underflow in bounds check arithmetic.
drivers/misc/ibmasm/ibmasm.h | 1 +
drivers/misc/ibmasm/lowlevel.c | 19 +++++++++++++++----
drivers/misc/ibmasm/lowlevel.h | 27 +++++++++++++++++++++++++--
drivers/misc/ibmasm/module.c | 13 +++++++++++++
4 files changed, 54 insertions(+), 6 deletions(-)
diff --git a/drivers/misc/ibmasm/ibmasm.h b/drivers/misc/ibmasm/ibmasm.h
index a5ced88ca923..8d69198bf10f 100644
--- a/drivers/misc/ibmasm/ibmasm.h
+++ b/drivers/misc/ibmasm/ibmasm.h
@@ -140,6 +140,7 @@ struct service_processor {
struct list_head node;
spinlock_t lock;
void __iomem *base_address;
+ resource_size_t mapped_size;
unsigned int irq;
struct command *current_command;
struct command *heartbeat;
diff --git a/drivers/misc/ibmasm/lowlevel.c b/drivers/misc/ibmasm/lowlevel.c
index 5313230f36ad..db84e5f827cb 100644
--- a/drivers/misc/ibmasm/lowlevel.c
+++ b/drivers/misc/ibmasm/lowlevel.c
@@ -9,7 +9,6 @@
#include "ibmasm.h"
#include "lowlevel.h"
-#include "i2o.h"
#include "dot_command.h"
#include "remote.h"
@@ -34,7 +33,14 @@ int ibmasm_send_i2o_message(struct service_processor *sp)
return 1;
header.message_size = outgoing_message_size((unsigned int)command_size);
- message = get_i2o_message(sp->base_address, mfa);
+ message = get_i2o_message(sp->base_address, sp->mapped_size, mfa);
+ if (!message) {
+ /* MFA was reserved for us; must release it to avoid
+ * deadlocking the hardware mailbox.
+ */
+ set_mfa_inbound(sp->base_address, mfa);
+ return 1;
+ }
memcpy_toio(&message->header, &header, sizeof(struct i2o_header));
memcpy_toio(&message->data, command->buffer, command_size);
@@ -63,8 +69,13 @@ irqreturn_t ibmasm_interrupt_handler(int irq, void * dev_id)
mfa = get_mfa_outbound(base_address);
if (valid_mfa(mfa)) {
- struct i2o_message *msg = get_i2o_message(base_address, mfa);
- ibmasm_receive_message(sp, &msg->data, incoming_data_size(msg));
+ struct i2o_message *msg = get_i2o_message(base_address,
+ sp->mapped_size, mfa);
+ if (msg)
+ ibmasm_receive_message(sp, &msg->data,
+ incoming_data_size(msg));
+ else
+ dbg("received mfa out of bounds\n");
} else
dbg("didn't get a valid MFA\n");
diff --git a/drivers/misc/ibmasm/lowlevel.h b/drivers/misc/ibmasm/lowlevel.h
index 25f1ed07c3c5..8c9700fc70bb 100644
--- a/drivers/misc/ibmasm/lowlevel.h
+++ b/drivers/misc/ibmasm/lowlevel.h
@@ -13,6 +13,9 @@
#define __IBMASM_CONDOR_H__
#include <asm/io.h>
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include "i2o.h"
#define VENDORID_IBM 0x1014
#define DEVICEID_RSA 0x010F
@@ -33,6 +36,9 @@
#define INTR_STATUS_REGISTER 0x13A0
#define INTR_CONTROL_REGISTER 0x13A4
+/* Highest statically accessed register offset */
+#define IBMASM_MAX_REG_OFFSET INTR_CONTROL_REGISTER
+
#define SCOUT_COM_A_BASE 0x0000
#define SCOUT_COM_B_BASE 0x0100
#define SCOUT_COM_C_BASE 0x0200
@@ -115,9 +121,26 @@ static inline void set_mfa_inbound(void __iomem *base_address, u32 mfa)
writel(mfa, base_address + INBOUND_QUEUE_PORT);
}
-static inline struct i2o_message *get_i2o_message(void __iomem *base_address, u32 mfa)
+/**
+ * get_i2o_message - Convert MFA to i2o_message pointer with bounds check
+ * @base_address: BAR 0 virtual address
+ * @mapped_size: actual size of BAR 0 mapping
+ * @mfa: Message Frame Address from hardware
+ *
+ * Returns NULL if the offset derived from @mfa does not fit within
+ * the mapped BAR (including the i2o_message header).
+ */
+static inline struct i2o_message *get_i2o_message(void __iomem *base_address,
+ resource_size_t mapped_size,
+ u32 mfa)
{
- return (struct i2o_message *)(GET_MFA_ADDR(mfa) + base_address);
+ u32 offset = GET_MFA_ADDR(mfa);
+
+ /* Prevent read/write beyond the ioremap region */
+ if (unlikely(offset + sizeof(struct i2o_message) > mapped_size))
+ return NULL;
+
+ return (struct i2o_message *)(offset + base_address);
}
#endif /* __IBMASM_CONDOR_H__ */
diff --git a/drivers/misc/ibmasm/module.c b/drivers/misc/ibmasm/module.c
index 4509c15a76a8..87d4d698a5ff 100644
--- a/drivers/misc/ibmasm/module.c
+++ b/drivers/misc/ibmasm/module.c
@@ -93,6 +93,19 @@ static int ibmasm_init_one(struct pci_dev *pdev, const struct pci_device_id *id)
}
sp->irq = pdev->irq;
+ sp->mapped_size = pci_resource_len(pdev, 0);
+
+ /*
+ * Ensure BAR 0 is large enough to cover the highest statically
+ * accessed hardware register (IBMASM_MAX_REG_OFFSET).
+ */
+ if (sp->mapped_size < IBMASM_MAX_REG_OFFSET + 4) {
+ dev_err(sp->dev, "PCI BAR0 too small, need at least %zu bytes\n",
+ (size_t)(IBMASM_MAX_REG_OFFSET + 4));
+ result = -ENODEV;
+ goto error_ioremap;
+ }
+
sp->base_address = pci_ioremap_bar(pdev, 0);
if (!sp->base_address) {
dev_err(sp->dev, "Failed to ioremap pci memory\n");
--
2.34.1
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH] misc: ibmasm: Fix out-of-bounds MMIO access during module load
2026-06-23 7:16 ` Greg KH
2026-06-23 11:40 ` [PATCH v2] misc: ibmasm: Fix static and dynamic out-of-bounds MMIO accesses w15303746062
@ 2026-06-23 11:47 ` Mingyu Wang
1 sibling, 0 replies; 6+ messages in thread
From: Mingyu Wang @ 2026-06-23 11:47 UTC (permalink / raw)
To: Greg KH, w15303746062; +Cc: arnd, linux-kernel, kees, stable
Hi Greg,
> The kernel trusts the hardware to not do foolish things like this 🙂
>> When evaluating the driver against emulated hardware or during virtual
>> device fuzzing, a malformed device may expose a significantly undersized
>> BAR 0 (e.g., 4KB). In this scenario, the readl() in enable_sp_interrupts()
>> crosses the mapped page boundary into unmapped memory, causing a page fault
>> during probe.
> Are you sure this is the only code path for this type of issue for this
> device/driver? Why just worry about this one?
You were absolutely right to ask. Prompted by your question, I did a full
audit of the MMIO access paths in this driver.
I found that the dynamic MFA (Message Frame Address) reads from the
hardware
queues, which are used directly as offsets in `get_i2o_message()`, are also
highly vulnerable to OOB accesses if the fuzzed hardware returns malicious
offsets during runtime.
I have just submitted a v2 patch in this thread that comprehensively fixes
both the static OOB during probe and the dynamic OOB during runtime.
Thank you very much for catching this and pointing me in the right
direction!
Best regards,
Mingyu Wang
^ permalink raw reply [flat|nested] 6+ messages in thread