An out-of-bounds (OOB) read vulnerability exists within the MediaTek t7xx WWAN driver implementation (specifically inside t7xx_port_enum_msg_handler()). The flaw is driven by an unchecked port_count field read directly from a modem-supplied control message.
This value acts as an loop bound terminator to walk a flexible array member. Because the driver fails to validate that the backing buffer size matches or exceeds the space required by the declared port_count, a malformed control payload can force the loop to read significantly past the allocation boundary into adjacent kernel slab memory.
The vulnerability can be reached via two distinct logical pathways within the driver initialization and processing stack, depending on the current communication mode of the interface:
drivers/net/wwan/t7xx/t7xx_hif_dpmaif_rx.c
- Modem firmware writes payload directly into host RAM via DPMAIF DMA engine over PCIe.
- No verification of payload content or data architecture happens at this layer.
|
v
drivers/net/wwan/t7xx/t7xx_port_proxy.c : t7xx_port_enqueue_skb()
- Socket Buffer (SKB) is enqueued into port->rx_skb_list.
- The only constraint check evaluated is queue depth threshold; SKB contents are ignored.
|
v
drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c : port_kthread_handler()
- Dequeues the pending SKB via __skb_dequeue() and passes it blindly to control_msg_handler().
|
v
drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c : control_msg_handler()
- Casts skb->data directly to a struct ctrl_msg_header*.
- Matches the CTL_ID_PORT_ENUM case and routes execution directly into the sink function
t7xx_port_enum_msg_handler() without evaluating skb->len boundaries.
drivers/net/wwan/t7xx/t7xx_port_ctrl_msg.c : control_msg_handler()
|
v
drivers/net/wwan/t7xx/t7xx_state_monitor.c : t7xx_fsm_append_event()
|
v
drivers/net/wwan/t7xx/t7xx_modem_ops.c : t7xx_parse_host_rt_data()
- The variable rt_feature->data_len is extracted from raw modem-controlled data and drives
offset memory arithmetic.
- The calculated offset pointer is passed into t7xx_port_enum_msg_handler() with no accompanying
length tracking parameter, leaving the sink with no baseline constraint data.
The communication protocol uses the following structure definition to parse incoming port enumeration messages:
struct port_msg {
__le32 head_pattern; // Offset 0x00 [4 Bytes]
__le32 info; // Offset 0x04 [4 Bytes] -> contains port_count in bits [15:0]
__le32 tail_pattern; // Offset 0x08 [4 Bytes]
__le32 data[]; // Offset 0x0C [Flexible Array Member - 0 Byte base size contribution]
};
// Base structure allocation footprint: 12 Bytes
The driver parses the port_count field out of the info register using bitmask extraction:
port_count = FIELD_GET(PORT_MSG_PRT_CNT, le32_to_cpu(port_msg->info)); // PORT_MSG_PRT_CNT = GENMASK(15, 0) -> Maximum value = 65535
Following this assignment, the driver immediately drops into an evaluation loop to register or modify runtime channel configurations:
for (i = 0; i < port_count; i++) {
u32 port_info = le32_to_cpu(port_msg->data[i]); /* SINK: Out-of-Bounds Read */
...
}
Pointer arithmetic for the assignment translates exactly to:
*(u32*)( (uintptr_t)port_msg + 12 + (i * 4) )
A structural message describing $N$ active channels requires a byte allocation footprint equal to $12 + (N \times 4)$. However, if a compromised or malicious modem transmits a message containing a base size of 12 bytes but specifies a port_count value of 65535, the loop executes up to 65535 times. This causes the kernel to read up to ~262 KB of memory data extending past the legitimate end of the allocated buffer.
The complete vulnerable function implementation from the mainline kernel tree is shown below:
int t7xx_port_enum_msg_handler(struct t7xx_modem *md, void *msg)
{
struct device *dev = &md->t7xx_dev->pdev->dev;
unsigned int version, port_count, i;
struct port_msg *port_msg = msg;
/* EXISTING VALIDATION STEP */
version = FIELD_GET(PORT_MSG_VERSION, le32_to_cpu(port_msg->info));
if (version != PORT_ENUM_VER ||
le32_to_cpu(port_msg->head_pattern) != PORT_ENUM_HEAD_PATTERN ||
le32_to_cpu(port_msg->tail_pattern) != PORT_ENUM_TAIL_PATTERN) {
dev_err(dev, "Invalid port control message %x:%x:%x\n",
version, le32_to_cpu(port_msg->head_pattern),
le32_to_cpu(port_msg->tail_pattern));
return -EFAULT;
}
port_count = FIELD_GET(PORT_MSG_PRT_CNT, le32_to_cpu(port_msg->info));
for (i = 0; i < port_count; i++) {
u32 port_info = le32_to_cpu(port_msg->data[i]); // <- Flawed OOB Read Location
unsigned int ch_id;
bool en_flag;
ch_id = FIELD_GET(PORT_INFO_CH_ID, port_info);
en_flag = port_info & PORT_INFO_ENFLG;
if (t7xx_port_proxy_chl_enable_disable(md->port_prox, ch_id, en_flag))
dev_dbg(dev, "Port:%x not found\n", ch_id);
}
return 0;
}
The verification routine checks version, head_pattern, and tail_pattern. However, because these constants are part of the DMA payload provided by the peripheral, an attacker can simply set them to their expected values:
head_pattern = PORT_ENUM_HEAD_PATTERNinfo = (PORT_ENUM_VER << 16) | 65535tail_pattern = PORT_ENUM_TAIL_PATTERNThis bypasses the existing integrity checks entirely. The logic assumes a cooperative hardware partner and fails to enforce spatial buffer limits.
Vulnerability verification was performed using a test harness executing within a QEMU instance with Kernel Address Sanitizer (KASAN) active. The test allocated a strict 12-byte block matching the raw structure base and assigned a target count of 65535.
BUG: KASAN: slab-out-of-bounds in t7xx_port_enum_msg_handler+0x1ae/0x1c0
Read of size 4 at addr ffff888008654d8c by task insmod/59
Call Trace:
[<ffffffff81b83d8d>] dump_stack_lvl+0x4d/0x70
[<ffffffff815ea710>] print_report+0x170/0x4f3
[<ffffffff815eaad2>] kasan_report+0xda/0x110
[<ffffffff83566ac2>] t7xx_port_enum_msg_handler+0x1ae/0x1c0
[<ffffffffa000215c>] t7xx_oob_harness_init+0x15c/0xff0 [t7xx_oob_harness]
...
[<ffffffff8100219a>] do_one_initcall+0x9a/0x3a0
Allocated by task 59:
[<ffffffff815e7230>] __kasan_kmalloc+0x7f/0x90
[<ffffffffa0002075>] t7xx_oob_harness_init+0x75/0xff0 [t7xx_oob_harness]
The buggy address belongs to the object at ffff888008654d80
which belongs to the cache kmalloc-16 of size 16
The buggy address is located 0 bytes to the right of
allocated 12-byte region [ffff888008654d80, ffff888008654d8c)
Memory state around the buggy address:
ffff888008654d00: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff888008654d80: 00 04 fc fc 00 04 fc fc 00 04 fc fc 00 04 fc fc
^
ffff888008654e00: 00 07 fc fc 00 04 fc fc 00 04 fc fc 00 06 fc fc
The out-of-bounds address (ffff888008654d8c) corresponds exactly to Allocation Base (ffff888008654d80) + 12 Bytes. This marks the initial array element slot data[0].
When kasan_multi_shot testing is continued beyond the initial log entry, the kernel encounters a secondary General Protection Fault (GPF):
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000022 KASAN: null-ptr-deref in range [0x0000000000000110-0x0000000000000117] RIP: 0010:t7xx_port_enum_msg_handler+0x121/0x1c0
This occurs because the arbitrary u32 block extracted out-of-bounds is routed into t7xx_port_proxy_chl_enable_disable() as the ch_id variable. The driver uses this value to calculate active pointer targets, allowing an attacker to inject out-of-bounds data into driver control flows and channel management lookups.
~262 KB across kernel slab caches. This allows exposure of adjacent crypto keys, tracking hashes, or kernel structure pointers.
t7xx family interacts directly with high-end corporate laptop components such as the Intel 5G Solution 5000 series cellular systems, turning this driver bug into an attractive cross-boundary pivot into the host operating system kernel.
Fixing this bug requires passing the actual packet allocation size down to the verification routine and computing required structural space using overflows-safe calculation wrappers like struct_size().
/* Accept explicit message length parameter */
int t7xx_port_enum_msg_handler(struct t7xx_modem *md, void *msg, size_t msg_len)
{
struct device *dev = &md->t7xx_dev->pdev->dev;
unsigned int version, port_count, i;
struct port_msg *port_msg = msg;
/* Check minimal physical baseline constraint first */
if (msg_len < sizeof(struct port_msg))
return -EINVAL;
version = FIELD_GET(PORT_MSG_VERSION, le32_to_cpu(port_msg->info));
if (version != PORT_ENUM_VER ||
le32_to_cpu(port_msg->head_pattern) != PORT_ENUM_HEAD_PATTERN ||
le32_to_cpu(port_msg->tail_pattern) != PORT_ENUM_TAIL_PATTERN) {
return -EFAULT;
}
port_count = FIELD_GET(PORT_MSG_PRT_CNT, le32_to_cpu(port_msg->info));
/* Defense in depth: Check against maximum possible logical channels */
if (port_count > T7XX_MAX_PORTS) {
dev_err(dev, "Excessive port count %u from modem\n", port_count);
return -EINVAL;
}
/* Spatial validation check using overflow-safe size tracking macro */
if (msg_len < struct_size(port_msg, data, port_count)) {
dev_err(dev, "Port enum msg too short: need %zu, have %zu\n",
struct_size(port_msg, data, port_count), msg_len);
return -EINVAL;
}
for (i = 0; i < port_count; i++) {
u32 port_info = le32_to_cpu(port_msg->data[i]);
unsigned int ch_id;
bool en_flag;
ch_id = FIELD_GET(PORT_INFO_CH_ID, port_info);
en_flag = port_info & PORT_INFO_ENFLG;
if (t7xx_port_proxy_chl_enable_disable(md->port_prox, ch_id, en_flag))
dev_dbg(dev, "Port:%x not found\n", ch_id);
}
return 0;
}
The calling routines must pass buffer tracking size attributes explicitly:
/* DPMAIF Path: Pass SKB buffer size track */ ret = t7xx_port_enum_msg_handler(ctl->md, (struct port_msg *)skb->data, skb->len); /* Handshake Path: Pass delta boundary offset */ t7xx_port_enum_msg_handler(ctl->md, rt_feature->data, data_length - offset);
v5.18-rc1 through current mainline release (April 2026).CONFIG_MTK_T7XX and CONFIG_WWAN attributes.The vulnerability was reported upstream through the Linux Kernel Mailing List (LKML), followed by patch review, maintainer discussion, and integration into the upstream kernel tree.
LKML Activity & Patch History: lore.kernel.org/all/?q=Pavitra+Jha
Pavitra Jha
Offensive Security Researcher
Kernel / Driver Exploitation Research