MediaTek t7xx WWAN Driver: Out-of-Bounds Read via Unchecked port_count (CVE-2026-43495)

MediaTek t7xx WWAN Driver | Linux Kernel Subsystem | Heap Memory Safety | Vulnerability Write-up

1. Summary

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.

2. Vulnerability Execution Paths

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:

A. Primary Vector: DPMAIF RX / SKB Path

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.
    

B. Secondary Vector: CLDMA Handshake Path

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.
    

3. Vulnerability Description & Mechanics

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.

4. Source Code Analysis

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;
}

Why Preexisting Validation Checks Fail

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:

This bypasses the existing integrity checks entirely. The logic assumes a cooperative hardware partner and fails to enforce spatial buffer limits.

5. KASAN Log & Triage Data

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

Anatomy of the Exploit Sink

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.

6. Threat Modeling & Impact Assessment

7. Remediation

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().

A. Updated Handler Implementation

/* 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;
}

B. Required Call-Site Modifications

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);

8. Affected Configurations

Upstream Patch & Timeline

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

Author

Pavitra Jha
Offensive Security Researcher
Kernel / Driver Exploitation Research