[PATCH v2] misc: ibmasm: Fix static and dynamic out-of-bounds MMIO accesses

From: w15303746062

Date: Tue Jun 23 2026 - 07:41:56 EST


From: Mingyu Wang <25181214217@xxxxxxxxxxxxxxxxx>

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@xxxxxxxxxxxxxxx
Signed-off-by: Mingyu Wang <25181214217@xxxxxxxxxxxxxxxxx>
---
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