Apple macOS High Sierra 10.13 - 'ctl_ctloutput-leak' Information Leak

EDB-ID: 44234
Author: Brandon Azad
Published: 2017-12-07
CVE: CVE-2017-13868
Type: Local
Platform: macOS
Aliases: ctl_ctloutput-leak.c
Advisory/Source: Link
Tags: N/A
Vulnerable App: N/A

  * ctl_ctloutput-leak.c 
* Brandon Azad
* CVE-2017-13868
* While looking through the source code of XNU version 4570.1.46, I noticed that the function
* ctl_ctloutput() in the file bsd/kern/kern_control.c does not check the return value of
* sooptcopyin(), which makes it possible to leak the uninitialized contents of a kernel heap
* allocation to user space. Triggering this information leak requires root privileges.
* The ctl_ctloutput() function is called when a userspace program calls getsockopt(2) on a kernel
* control socket. The relevant code does the following:
* (a) It allocates a kernel heap buffer for the data parameter to getsockopt(), without
* specifying the M_ZERO flag to zero out the allocated bytes.
* (b) It copies in the getsockopt() data from userspace using sooptcopyin(), filling the data
* buffer just allocated. This copyin is supposed to completely overwrite the allocated data,
* which is why the M_ZERO flag was not needed. However, the return value of sooptcopyin() is
* not checked, which means it is possible that the copyin has failed, leaving uninitialized
* data in the buffer. The copyin could fail if, for example, the program passed an unmapped
* address to getsockopt().
* (c) The code then calls the real getsockopt() implementation for this kernel control socket.
* This implementation should process the input buffer, possibly modifying it and shortening
* it, and return a result code. However, the implementation is free to assume that the
* supplied buffer has already been initialized (since theoretically it comes from user
* space), and hence several implementations don't modify the buffer at all. The NECP
* function necp_ctl_getopt(), for example, just returns 0 without processing the data buffer
* at all.
* (d) Finally, if the real getsockopt() implementation doesn't return an error, ctl_ctloutput()
* calls sooptcopyout() to copy the data buffer back to user space.
* Thus, by specifying an unmapped data address to getsockopt(2), we can cause a heap buffer of a
* controlled size to be allocated, prevent the contents of that buffer from being initialized, and
* then reach a call to sooptcopyout() that tries to write that buffer back to the unmapped
* address. All we need to do for the copyout to succeed is remap that address between the calls to
* sooptcopyin() and sooptcopyout(). If we can do that, then we will leak uninitialized kernel heap
* data to userspace.
* It turns out that this is a pretty easy race to win. While testing on my 2015 Macbook Pro, the
* mean number of attempts to win the race was never more than 600, and the median was never more
* than 5. (This testing was conducted with DEBUG off, since the printfs dramatically slow down the
* exploit.)
* This program exploits this vulnerability to leak data from a kernel heap buffer of a
* user-specified size. No attempt is made to seed the heap with interesting data. Tested on macOS
* High Sierra 10.13 (build 17A365).
* Download:
#if 0
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP, // (a) data is allocated
M_WAITOK); // without M_ZERO.
if (data == NULL)
return (ENOMEM);
* 4108337 - copy user data in case the
* kernel control needs it
error = sooptcopyin(sopt, data, // (b) sooptcopyin() is
sopt->sopt_valsize, sopt->sopt_valsize); // called to fill the
} // buffer; the return
len = sopt->sopt_valsize; // value is ignored.
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit, // (c) The getsockopt()
kcb->userdata, sopt->sopt_name, // implementation is
data, &len); // called to process
if (data != NULL && len > sopt->sopt_valsize) // the buffer.
panic_plain("ctl_ctloutput: ctl %s returned "
"len (%lu) > sopt_valsize (%lu)\n",
kcb->kctl->name, len,
socket_lock(so, 0);
if (error == 0) {
if (data != NULL)
error = sooptcopyout(sopt, data, len); // (d) If (c) succeeded,
else // then the data buffer
sopt->sopt_valsize = len; // is copied out to
} // userspace.
Related Posts