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: