Bugtraq mailing list archives
Re: SUMMARY Security Info (root broken)
From: casper () fwi uva nl (Casper Dik)
Date: Tue, 04 Oct 1994 10:24:51 +0100
I think that Pat has highlighted here the problem with a lot of packages that use a setuid root process to create a file in a restricted directory (e.g, a 775 root.mail /var/spool/mail.) I've looked at the 4.4BSD-lite (NetBSD uses this) mail.local.c and at first, thought there was a potential race condition in the code where it does an lstat check then an open, thinking there was a race condition. Checking the man page for open() however, revealed the following tidbits: If path is a symbolic link and O_CREAT and O_EXCL are set, the link is not followed. (From Solaris 2.3, and the NetBSD-current man page says something similar.)
These semntics were introduced at the same time that symlinks were introduced. However, some vendors did pick up symlinks but w/o the don't follow on O_CREAT|O_EXCL semantics. (IRIX 4.0.x, I think)
So, it seems that a standard (POSIX?) has explicitly given us a method to atomically create a file if it doesn't exist, whilst at the same time not getting fooled by a dangling symlink (which is a common way to exploit setuid race conditions, correct?)
POSIX doesn't specify symlinks (yet).
Now, I don't know if this helps people on systems where this behaviour doesn't exist (I'm not sure if Sunos 4 supports this, for example.)
SunOS 4 and all BSD/SVR4 derived systems should. SVR3 and earlier with symlinks need not.
It's the creating of the new file by a priviliged process that is the critical region that so often gets spoofed by a race condition. I have some (simple - thus easy to follow and assure is correct - I hope :) code at home that I was working on which should work without a race condition (using the atomic link()), so I'll post it tomorrow to get disected by those with more experience than I. If it does work the way I expect it to, I feel that a simpler, more effective, mail.local could be implemented that didn't rely upon the O_CREAT | O_EXCL feature of newer systems I described above...
Here's some code I wrote earlier that does much the same thing. (it dates from May somewhere) Casper #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of shell archive." # Contents: safecreate.c # Wrapped by casper () fwi uva nl on Tue May 17 16:34:58 1994 PATH=/bin:/usr/bin:/usr/ucb ; export PATH if test -f 'safecreate.c' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'safecreate.c'\" else echo shar: Extracting \"'safecreate.c'\" \(3836 characters\) sed "s/^X//" >'safecreate.c' <<'END_OF_FILE' X/* X * Safe way to create/open a file in a sticky directory. X * the file must belong to the executing user. X * X * Casper Dik (casper () fwi uva nl) X */ X#include <stdio.h> X#include <stdlib.h> X#include <unistd.h> X#include <sys/stat.h> X#include <sys/types.h> X#include <sys/file.h> X#include <string.h> X#include <errno.h> X#include <pwd.h> X X#ifndef FILEMODE X#define FILEMODE 0600 X#endif X#ifndef FILEGROUP X#define FILEGROUP -1 X#endif X#define FIXMODE X XFILE * Xopenmbox(uid_t, char *); X Xmain(int argc, char **argv) X{ X struct passwd *pwd; X FILE *mbox; X int c; X X if (argc != 3) { X fprintf(stderr,"Usage: %s file user\n", argv[0]); X exit(1); X } X pwd = getpwnam(argv[2]); X if (pwd == 0) { X fprintf(stderr,"%s: %s: unknown user\n", argv[0], argv[1]); X exit(1); X } X if ((mbox = openmbox(pwd->pw_uid, argv[1])) == 0) { X fprintf(stderr,"Couldn't safely open %s\n", argv[1]); X exit(1); X } X while((c = getchar()) != EOF) X putc(c, mbox); X exit(0); X} X XFILE * Xopenmbox(uid_t uid, char *file) X{ X char *dir, *p; X int fd; X FILE *f; X X dir = strdup(file); X p = strrchr(dir,'/'); X if (!p) X dir = "."; X else X *p = '\0'; X X /* First we'll try a normal open, no O_CREAT */ X fd = open(file, O_WRONLY|O_APPEND); X if (fd == -1) { X char *tmpf; X X /* If there's another error, we can't handle it */ X if (errno != ENOENT) { X perror(file); X return 0; X } X X /* Now we must create the file; */ X tmpf = tempnam(dir, "psrz"); X if (tmpf == 0) { X fprintf(stderr,"Can't generate unique filename\n"); X return 0; X } X fd = open(tmpf, O_WRONLY|O_APPEND|O_CREAT|O_EXCL, FILEMODE); X if (fd < 0) { X fprintf(stderr,"Can't create temporary file\n"); X (void) unlink(tmpf); X free(tmpf); X return 0; X } X /* Now we have a file in the spool directory, we're going to X * rename it the old fashioned way because we want it to X * fail if someone created the mailbox in the meantime */ X if (link(tmpf, file) == -1) { X if (errno == EEXIST) X fprintf(stderr,"%s created behind our back\n", file); X else X perror("link"); X (void) unlink(tmpf); X free(tmpf); X return 0; X } X (void) unlink(tmpf); X free(tmpf); X /* X * At this point we have the file and an open filedescriptor X * We can be sure we aren't overwriting another file because we've X * created this file in the spool directory. X * Only on systems where O_CREAT|O_EXCL creates files at the end X * of symlinks we have to worry */ X } else { X struct stat fdbuf, filebuf; X X /* the next two things should never happen */ X if (fstat(fd, &fdbuf) == -1) { X perror("fstat"); X return 0; X } X if (lstat(file, &filebuf) == -1) { X perror(file); X return 0; X } X X /* Now check that: file and fd reference the same file, X file only has one link, file is plain file */ X if (fdbuf.st_dev != filebuf.st_dev || X fdbuf.st_ino != filebuf.st_ino || X fdbuf.st_nlink != 1 || X filebuf.st_nlink != 1 || X (fdbuf.st_mode & S_IFMT) != S_IFREG) { X fprintf(stderr,"%s must be a plain file with one link\n", file); X return 0; X } X /* we have a filedescriptor pointing to a file in the spool directory X * how can we be sure it wasn't pointing at another file when we X * opened it? X * At the time of the lstat, the file in the spool directory was X * a hardlink to the file we previously opened. X * (st_dev and st_ino uniquely identify a file) X * It was the only hardlink. X * Could it have been a different file previously? X * Yes, but only if the file existed in a writable directory and X * no longer exists there now. X */ X } X /* Now we have an fd pointing to the file in the spool directory. X * Now we're going to fix ownership and mode */ X#ifdef FIXMODE X (void) fchmod(fd, FILEMODE); X (void) fchown(fd, uid, FILEGROUP); X#endif X f = fdopen(fd, "a"); X if (f == 0) X close(fd); X return f; X} END_OF_FILE if test 3836 -ne `wc -c <'safecreate.c'`; then echo shar: \"'safecreate.c'\" unpacked with wrong size! fi # end of 'safecreate.c' fi echo shar: End of shell archive. exit 0
Current thread:
- [Tim Newsham: IRIX Race Conditions] Tim Newsham (Oct 02)
- <Possible follow-ups>
- [Tim Newsham: IRIX Race Conditions] Tim Newsham (Oct 02)
- Re: your mail Joseph W. Stroup (Oct 02)
- SUMMARY Security Info (root broken) Pat Myrto (Oct 03)
- Re: SUMMARY Security Info (root broken) Luke Mewburn (Oct 03)
- Re: SUMMARY Security Info (root broken) Pat Myrto (Oct 03)
- Re: SUMMARY Security Info (root broken) Casper Dik (Oct 04)
- [Tim Newsham: IRIX Race Conditions] Tim Newsham (Oct 02)
- [Tim Newsham: IRIX Race Conditions] Tim Newsham (Oct 02)
- [Tim Newsham: IRIX Race Conditions] Brent Chapman (Oct 02)