On May 3, 2026, I reported a remotely triggered kernel vulnerability in the
macOS SMB filesystem. A malicious SMB server could leave the kernel with a
supplementary-group count but no corresponding group array. When a Mac mounted
the share, the next file-attribute lookup trusted that inconsistent state,
read through a NULL pointer, and panicked the machine.
During testing, Apple's boot-randomized kernel Target Flag value also appeared in multiple general-purpose registers at the point of the panic.
I reproduced the issue on Apple silicon running macOS Tahoe 26.4 (25E246). The same proof of concept also reproduced on macOS 26.5 beta 4 (25F5068a), where the relevant code was unchanged. Apple assigned CVE-2026-84543; its advisory says that connecting to a malicious SMB server may cause unexpected system termination or corrupt kernel memory.
The bug
During an SMB1 mount, macOS asks the server for POSIX identity information
using SMB_QFS_POSIX_WHOAMI (0x0202). Among other things, the response
contains a count of supplementary group IDs. _smbfs_unix_whoami parses that
count directly into the live smbmount object:
smbmount->ntwrk_cnt_gid number of supplementary groups
smbmount->ntwrk_gids pointer to the group array
The parser checks that ntwrk_cnt_gid * 8 does not exceed the negotiated SMB
transaction size before allocating the array. That check works as an
allocation limit. The problem is what happens when it fails.
The error path only clears ntwrk_cnt_gid when ntwrk_gids is non-NULL.
An oversized count fails validation before the allocation takes place, so the
pointer is still NULL and the cleanup skips the count reset. The mount is
left in this state:
ntwrk_cnt_gid = attacker-controlled nonzero value
ntwrk_gids = NULL
This is visible in Apple's pre-fix open-source implementation of
smbfs_unix_whoami.
The relevant logic reduces to:
md_get_uint32le(mdp, &smp->ntwrk_cnt_gid);
total_bytes = smp->ntwrk_cnt_gid * sizeof(uint64_t);
if (total_bytes > session_txmax) {
error = EBADRPC;
goto done; /* allocation has not happened */
}
smp->ntwrk_gids = allocate(total_bytes);
done:
if (error && smp->ntwrk_gids) {
free(smp->ntwrk_gids);
smp->ntwrk_cnt_gid = 0; /* skipped when the pointer is NULL */
}
My server returned 0x00100000 as the count. Multiplying it by eight gives an
eight-megabyte allocation request, well over the negotiated transaction size,
so the parser reliably takes the pre-allocation error path.
The bounds check rejects the oversized allocation, but the error path leaves the rejected count in the mount structure. A value rejected by one function remains live and is trusted later by another.
The crash
The bad state is consumed when SMBFS caches attributes for a path.
_smb_get_uid_gid_mode walks the mount's supplementary groups to decide which
permission bits apply. Its loop checks the count, but not the pointer:
ldr w12, [x1, #0x10] ; ntwrk_cnt_gid
cbz w12, skip_loop
ldr x13, [x1, #0x18] ; ntwrk_gids
sub x14, x12, #1
ldr x12, [x13], #8 ; first iteration, x13 == NULL
cmp x11, x12
cset w12, eq
On every reproduced panic, x13 was zero at the faulting ldr, while x12
or its pre-load value reflected the count supplied by the server. The crash is
therefore a direct consequence of the count/pointer mismatch rather than an
integer-overflow side effect.
The complete path from the wire to the panic was:
malicious SMB server
-> POSIX WHOAMI response (0x0202)
-> oversized ntwrk_cnt_gid is stored in smbmount
-> validation fails before ntwrk_gids is allocated
-> cleanup leaves the count behind
-> UNIX_INFO2 attributes are parsed for a path (0x020B)
-> _smbfs_attr_cacheenter
-> _smb_get_uid_gid_mode
-> load through ntwrk_gids == NULL
-> kernel data abort
POSIX WHOAMI is 0x0202; 0x020B is the later UNIX_INFO2 path-information
response. An early version of my report incorrectly listed WHOAMI as 0x202B.
What the server controls
The server controls the 32-bit supplementary-group count in the WHOAMI
response. It also controls the UID, GID, and permission fields returned in
the UNIX_INFO2 attributes for each path. Those values cross the network/file
system boundary and are parsed in kernel context by smbfs.kext.
The PoC uses a guest SMB session, so it does not require a username or password. The Mac still has to connect to the malicious share.
I collected four panics while working through the bug. In the final capture,
the server placed the same per-boot _COMM_PAGE_ASB_TARGET_KERN_VALUE into
three different response fields. The panic showed the full 64-bit value in
x8, x9, and x11, with masked versions in x10 and x12. These values
matched the per-boot Target Flag value recorded before the trigger.
The sanitized panic headers and register blocks are available in PANIC_REGISTERS.md.
Fix and hardening
My recommended defensive implementation was to parse the response into
temporary state, validate the count and remaining response length, allocate
and populate a temporary array, and only then publish the count and pointer to
smbmount. On any error, all three related fields—the count, pointer, and
allocation size—should be reset together. The consumer should also reject a
positive count paired with a NULL pointer.
The same pattern is worth checking anywhere SMBFS has a count/pointer or length/buffer pair, particularly the SID arrays and reconnect/error-retry paths. The code smell is a parser that writes one field into a long-lived object before validation completes, followed by cleanup that resets scalar state only when an allocation exists.
Apple's public advisories say the issue was addressed with improved bounds checking. They do not describe the implementation, and I have not independently verified the production patch, so the changes above are my recommendations rather than a description of Apple's fix.
Apple released the fix on September 14, 2026 in macOS Golden Gate 27, macOS Tahoe 26.7, and macOS Sequoia 15.8.
Reproduction
The PoC includes the loopback server used for the final evidence capture. On a vulnerable test Mac, it serves the crafted WHOAMI and UNIX_INFO2 responses and causes the panic during the mount/attribute sequence.
See poc/README.md for the commands. The PoC deliberately panics affected systems.
Disclosure Timeline
| Date | Event |
|---|---|
| May 3, 2026 | Submitted the report to Apple around 2:00 a.m. |
| May 14, 2026 | Apple notified me at 10:16 p.m. of a $20,000 bounty |
| June 15, 2026 | Received the bounty payment |
| September 14, 2026 | Apple released the fixes and published the security advisories |
Apple notified me of the $20,000 bounty just 11 days after I submitted the report. I honestly did not expect that turnaround; several of my other bounty decisions have taken months.
Comments