Two months ago, we demonstrated the first public bypass of Apple MIE on macOS 26.4.1. We withheld the technical details until Apple shipped fixes, which are now available in macOS 26.6. We'd like to thank Apple for their collaboration throughout this disclosure process. Since March, we've reported 38 vulnerabilities to Apple and counting.

In this blog, we'll share the details of the two vulnerabilities behind our exploit. We'll present the full exploit at Black Hat USA on August 5 and publish the complete technical report afterward.

Before then, we'd like to turn these bugs into a challenge. Apple MIE combines defenses across hardware, the hypervisor, the kernel, and userspace. Together, they form a mesh of overlapping filters that block many classes of vulnerabilities. Apple also continues to strengthen these mitigations over time. Given their complexity, rapid evolution, and the limited public discussion of exploitation techniques, we believe modern XNU kernel exploitation is one of the hardest and most interesting benchmarks for AI-assisted exploit development. We'd love to see how the community solves this challenge before we reveal our own approach at Black Hat. Humans, AI, and human-AI teams are all welcome.

We're looking forward to seeing different solutions and learning new techniques. It took us about five days to go from bug discovery to a polished exploit for this chain.

The bugs we used were in WebDAV and SMBClient, both are open-source Apple components; you can find their source code here and here. The source code references here are from SMBClient-538.100.12 and webdavfs-403.0.0.0.1.

Both SMB and WebDAV on macOS are split designs. There is an in-kernel filesystem (smbfs and webdav_fs) that implements vnode operations, and a userspace helper that manages the session or the HTTP transport. When a program does something like reading a file or resolving a path on the mounted volume, the kernel side emits a request to the server and parses the reply.

Both bugs happen when a client connects to a malicious SMB/WebDAV server. In the case of WebDAV, a malicious server can induce the client to read back uninitialized kernel memory; this is our infoleak. In the case of SMB, a malicious server can induce the client to mistreat a special response as a different type than intended; this is our type confusion. It is possible to combine these two bugs in a way such that we will end up with kernel read/write primitives and gain code execution.

We will now go into the details for each bug.

SMB2 CREATE requests can carry create contexts which are tagged, optional blobs that request extra behaviour such as a lease or a durable handle. Apple's client adds its own AAPL contexts, including a "Resolve ID" context that asks the server to map an inode number to a path. Each context has a four-byte name, and the reply echoes contexts back with their own names.

When the VFS layer resolves a vnode by inode on an smbfs mount and the mounted server has advertised the right capability flags, smbfs_vget issues an AAPL Resolve ID compound request. The fsgetpath(2) syscall is a convenient trigger: it hands the kernel both an fsid that selects the mounted volume and an objid that VFS passes straight down as the inode. Note that the branch that emits the request is controlled by the session state the server advertised at mount time:

SMBClient/kernel/smbfs/smbfs_vfsops.c smbfs_vget

The inode number flows, unmodified, into the request. smb2fs_smb_cmpd_resolve_id builds a small stack object, a struct smb2_create_ctx_resolve_id whose first field is that file ID, and stashes a pointer to it in the generic void *create_contextp slot on the request:

SMBClient/kernel/smbfs/smbfs_smb_2.c smb2fs_smb_cmpd_resolve_id

So far so good: the client knows it sent a Resolve ID context, and it knows what type lives behind that pointer.

When the reply comes back, smb2_smb_parse_create_contexts does not consult the request to decide what create_contextp points at. It switches on the four-byte context name in the server's reply and casts create_contextp to whatever type matches that name. If the reply names RqLs (a lease), it enters the lease case and immediately begins treating the object as a lease wrapper:

SMBClient/kernel/netsmb/smb_smb_2.c smb2_smb_parse_create_contexts

The client sent a Resolve ID context. A malicious server can reply with a lease context (RqLs) instead. The parser dutifully enters the lease case and reinterprets the Resolve ID object as a struct smb2_dur_hndl_and_lease. The two structures do not agree on their layout so we have a type confusion bug:

SMBClient/kernel/netsmb/smb_rq_2.h and SMBClient/kernel/netsmb/smb_2.h

The first 8-byte field of the Resolve ID object is the file ID, a value that came straight from the caller-supplied inode. Under the lease interpretation, that same 8 bytes is leasep, a pointer to a lease object.

We summarize the bug in the following diagram:

The second bug is an info disclosure and it lives in the WebDAV read path.

WebDAV is split like SMB: the webdav_fs kext backs vnode reads, and a signed userspace agent (webdavfs_agent) performs the actual HTTP requests. The attacker controls the HTTP peer so he/she controls the content and length of every HTTP response.

The trigger is a file whose download is deliberately stalled. When a program reads a region of the file that hasn't been downloaded yet, and that region is far enough ahead of the cached prefix that waiting is pointless, webdav_rdwr takes an "out-of-band" shortcut: it asks the agent to fetch just those bytes directly, via webdav_read_bytes:

webdavfs/webdav_fs.kextproj/webdav_fs.kmodproj/webdav_vnops.c webdav_rdwr

webdav_read_bytes does three things that are individually reasonable and collectively a leak:

webdavfs/webdav_fs.kextproj/webdav_fs.kmodproj/webdav_vnops.c webdav_read_bytes

  1. It allocates a buffer size to the requested read (up to a 16 MB MAX_READ) with MALLOC(..., M_TEMP, M_WAITOK). Note the absence of M_ZERO: the buffer is not zeroed.

  2. It sends the request to the agent and waits for the reply.

  3. After the reply "succeeds," it copies request_read.count bytes, the number it asked for, back to the user with uiomove, regardless of how many bytes actually arrived.

The attacker's job is to make step 2 succeed while delivering nothing. The userspace agent treats any 2xx HTTP status as success, so an empty 206 Partial Content is a "successful" reply carrying a zero-length body:

webdavfs/mount.tproj/webdav_network.c translate_status_to_error / network_read

The agent serialises that zero length back to the kext. The missing check is on the kext side. webdav_sendmsg sets up receive iovecs for the status word plus the reply buffer, asks the socket layer for MSG_WAITALL, but then tests only the error return; it never verifies that the number of bytes received (iolen) actually covers the reply buffer it promised the caller:

webdavfs/webdav_fs.kextproj/webdav_fs.kmodproj/webdav_vnops.c webdav_sendmsg

A status word arrives, the buffer stays untouched, and step 3 copies its full requested length out anyway.

The kext trusts the length it requested rather than the length it received, and copies out a buffer it never filled. Whatever was previously in those pages is disclosed to userspace. The bug can also be illustrated as follows:

Apple MIE's mesh of overlapping mitigations sits between these two bugs and root. We think this pair is especially, though not uniquely, powerful for exploiting a modern XNU kernel. We invite you to develop an exploit that gains root from an unprivileged user on a modern macOS device with full MIE enabled.

This is not necessarily the easiest path to an LPE on macOS. The challenge is deliberately artificial: use these two bugs to overcome Apple's kernel memory-corruption mitigations and reach root. What techniques work? Which primitives do you build along the way? Can any of them be reused elsewhere?

Build your exploit, don't stop at arbitrary kernel read/write, document your process, and tag us on X (@calif_io). We're looking forward to seeing how humans, AI, and human-AI teams approach the challenge. We'll share our own approach after our Black Hat talk.

Discussion about this post

Ready for more?