Bugtraq mailing list archives

Re: Publically writable directories


From: Michael.Dilger () Eng Sun COM (Michael Dilger)
Date: Mon, 17 Jun 1996 15:29:36 -0700


Is there a safe way of opening a temporary file in a publically writable
directory as a normal user, given a system with symbolic links?
I'm even willing to assume a sticky bit on the directory.

Main problem: How do I disallow a malicious

$ ln -s /tmp/some.file $MYHOME/.somedotfile

at the wrong times, without getting into race conditions?


I believe Casper Dik's solution for the binmail problem concerning
opening files in /var/mail (a publically writable, sticky directory)
is what you're looking for.  I've included it.

Michael B. Dilger
Michael.Dilger () Eng Sun COM



/*
From casper () fwi uva nl (Casper H.S. Dik)
Newsgroups: comp.security.unix
Subject: Re: fix for Sun /bin/mail problem?
Date: 18 May 1994 15:08:12 GMT

pjg () acsu buffalo edu (Paul Graham) writes:

barr () pop psu edu (David Barr) writes:
|Perhaps you could tell us _how_ it solves the problem.  From what I've
|seen here there's NO real solution without making /var/spool/mail
|not world-writeable.

actually there is an elegant little solution to this problem given a
public mail spool but i don't have room on this line to write it down.

I believe the following will code doesn't have any race conditions, will
not create files outside the spool directory and will not allow you
to append to other files.

If anyone finds a flaw in this program, I'd be happy to hear it.

Casper
*/

/*
 * Safe way to create/open a file in a sticky directory.
 * the file must belong to the executing user.
 *
 * Casper Dik (casper () fwi uva nl)
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/file.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>

#ifndef FILEMODE
#define FILEMODE 0600
#endif
#ifndef FILEGROUP
#define FILEGROUP -1
#endif
#define FIXMODE

FILE *
openmbox(uid_t, char *);

main(int argc, char **argv)
{
    struct passwd *pwd;
    FILE *mbox;
    int c;

    if (argc != 3) {
        fprintf(stderr,"Usage: %s file user\n", argv[0]);
        exit(1);
    }
    pwd = getpwnam(argv[2]);
    if (pwd == 0) {
        fprintf(stderr,"%s: %s: unknown user\n", argv[0], argv[1]);
        exit(1);
    }
    if ((mbox = openmbox(pwd->pw_uid, argv[1])) == 0) {
        fprintf(stderr,"Couldn't safely open %s\n", argv[1]);
        exit(1);
    }
    while((c = getchar()) != EOF)
        putc(c, mbox);
    exit(0);
}

/*
 * A procedure to safely open and create a mailbox in a worl-writable
 * sticky directory.
 * It should have no race conditions and should not allow
 * overwriting/creating random files for the user.
 * Essential parts are:
 *      - openening with open(2), not fopen(3S), for maximum control
 *      - splitting the open of an existing file and the create of a new
 *        mailbox.
 *      - combining the open with the validity checks
 */
FILE *
openmbox(uid_t uid, char *file)
{
    int fd;
    FILE *f;

    /* First we'll try a normal open, no O_CREAT */
    fd = open(file, O_WRONLY|O_APPEND);
    if (fd == -1) {

        if (errno != ENOENT) {
            /* If there's another error, we can't handle it */
            perror(file);
            return 0;
        }

        /* Create the file.  It must not exist.  If it does exist, fail. */
        fd = open(file, O_WRONLY|O_APPEND|O_CREAT|O_EXCL, FILEMODE);
        if (fd < 0) {
            fprintf(stderr,"Can't create %s\n", file);
            return 0;
        }

        /*
         * At this point we have the file and an open filedescriptor
         * We can be sure we aren't overwriting another file because we've
         * created this file in the spool directory.
         * Only on systems where O_CREAT|O_EXCL creates files at the end
         * of symlinks we have to worry */
    } else {
        struct stat fdbuf, filebuf;

        /* the next two stats should never fail */
        if (fstat(fd, &fdbuf) == -1) {
            perror("fstat");
            return 0;
        }
        if (lstat(file, &filebuf) == -1) {
            perror(file);
            return 0;
        }

        /* Now check that: file and fd reference the same file,
           file only has one link, file is plain file */
        if (fdbuf.st_dev != filebuf.st_dev ||
            fdbuf.st_ino != filebuf.st_ino ||
            fdbuf.st_nlink != 1 ||
            filebuf.st_nlink != 1 ||
            (fdbuf.st_mode & S_IFMT) != S_IFREG) {
            fprintf(stderr,"%s must be a plain file with one link\n", file);
            return 0;
        }
        /* we have a filedescriptor pointing to a file in the spool directory
         * how can we be sure it wasn't pointing at another file when we
         * opened it?
         * At the time of the lstat, the file in the spool directory was
         * a hardlink to the file we previously opened.
         * (st_dev and st_ino uniquely identify a file)
         * It was the only hardlink.
         * Could it have been a different file previously?
         * Yes, but only if the file existed in a writable directory and
         * no longer exists there now.
         */
    }
    /* Now we have an fd pointing to the file in the spool directory.
     * Now we're going to fix ownership and mode */
#ifdef FIXMODE
    (void) fchmod(fd, FILEMODE);
    (void) fchown(fd, uid, FILEGROUP);
#endif
    f = fdopen(fd, "a");
    if (f == 0)
        close(fd);
    return f;
}



Current thread: