CVE-2025-43299: A Stack Buffer Overflow in Libc’s setsourcefilter()
Apple Security Advisory: macOS Tahoe 26
Introduction
This is a stack buffer overflow I found in Libc’s multicast source-filter API, in net/FreeBSD/sourcefilter.c. That file is part of the FreeBSD-derived networking code carried in Libc, and it holds the multicast source-filter functions. setsourcefilter() is a public entry point, so any program that links against Libc, and therefore the closed-source libsystem_c.dylib family, can reach it. That combination of reachability and a large amount of BSD-derived code made it a reasonable place to look.
The function copies a caller-supplied socket address into a fixed-size stack field, and it decides how many bytes to copy by reading the length straight out of that same caller’s socket address. An oversized value walks off the end of the stack buffer.
The Vulnerability
The problem lives in setsourcefilter() inside net/FreeBSD/sourcefilter.c. The function copies the caller-supplied group address into a fixed-size field of a local group_source_req structure (msfr.msfr_group, a sockaddr_storage), and it decides how many bytes to copy by reading the length straight out of the user’s own socket address:
struct __msfilterreq msfr;
struct sockaddr_storage *psu;
...
psu = (struct sockaddr_storage *)group;
...
memcpy(&msfr.msfr_group, psu, psu->ss.ss_len);
The ss_len field (sin_len on an IPv4 sockaddr_in, sin6_len on IPv6) is fully attacker-controlled. Nothing validates it against sizeof(msfr.msfr_group), and nothing checks that it matches the family-specific address size. Whatever value the caller places into ss_len becomes the count handed to memcpy(), so a group address that claims to be larger than the destination overwrites stack memory past the end of msfr.
Root Cause
The length passed to memcpy() comes directly from the caller’s own socket address and is never checked against the size of where it is being copied. msfr.msfr_group is a fixed sockaddr_storage, but the copy count is psu->ss.ss_len, a single byte that the caller sets. There is no comparison against sizeof(msfr.msfr_group) and no check that the length matches the address family’s real structure size. Because the destination size is fixed and the length is not, any ss_len larger than the destination overruns the stack buffer and continues past the stack canary that the compiler placed between the local buffers and the saved return address.
Reproduction
Conceptually, the trigger is to craft a sockaddr_in whose sin_len is larger than the real structure and pass it in. A legitimate IPv4 address sets sin_len to sizeof(struct sockaddr_in), which is 16 bytes. Setting it to 250 pushes the count past both the real structure and the msfr_group destination.
When setsourcefilter() reaches the memcpy(), it copies 250 bytes into a sockaddr_storage-sized slot on the stack. That write runs off the end of msfr and overwrites the stack canary. Because the canary no longer matches its expected value when the function returns, the runtime routes execution into __stack_chk_fail, which aborts the process with SIGABRT and a crash report referencing a stack buffer overflow in setsourcefilter() or __stack_chk_fail. On its own the canary holds this to a denial-of-service, but the same single-byte control over the copy length is the standard setup for overwriting saved registers and the return address if it can be paired with a canary leak or a build without stack protection.