Current File : //root/postfix-3.2.0/src/util/safe_open.c |
/*++
/* NAME
/* safe_open 3
/* SUMMARY
/* safely open or create regular file
/* SYNOPSIS
/* #include <safe_open.h>
/*
/* VSTREAM *safe_open(path, flags, mode, st, user, group, why)
/* const char *path;
/* int flags;
/* mode_t mode;
/* struct stat *st;
/* uid_t user;
/* gid_t group;
/* VSTRING *why;
/* DESCRIPTION
/* safe_open() carefully opens or creates a file in a directory
/* that may be writable by untrusted users. If a file is created
/* it is given the specified ownership and permission attributes.
/* If an existing file is opened it must not be a symbolic link,
/* it must not be a directory, and it must have only one hard link.
/*
/* Arguments:
/* .IP "path, flags, mode"
/* These arguments are the same as with open(2). The O_EXCL flag
/* must appear either in combination with O_CREAT, or not at all.
/* .sp
/* No change is made to the permissions of an existing file.
/* .IP st
/* Null pointer, or pointer to storage for the attributes of the
/* opened file.
/* .IP "user, group"
/* File ownership for a file created by safe_open(). Specify -1
/* in order to disable user and/or group ownership change.
/* .sp
/* No change is made to the ownership of an existing file.
/* .IP why
/* A VSTRING pointer for diagnostics.
/* DIAGNOSTICS
/* Panic: interface violations.
/*
/* A null result means there was a problem. The nature of the
/* problem is returned via the \fIwhy\fR buffer; when an error
/* cannot be reported via \fIerrno\fR, the generic value EPERM
/* (operation not permitted) is used instead.
/* HISTORY
/* .fi
/* .ad
/* A safe open routine was discussed by Casper Dik in article
/* <2rdb0s$568@mail.fwi.uva.nl>, posted to comp.security.unix
/* (May 18, 1994).
/*
/* Olaf Kirch discusses how the lstat()/open()+fstat() test can
/* be fooled by delaying the open() until the inode found with
/* lstat() has been re-used for a sensitive file (article
/* <20000103212443.A5807@monad.swb.de> posted to bugtraq on
/* Jan 3, 2000). This can be a concern for a set-ugid process
/* that runs under the control of a user and that can be
/* manipulated with start/stop signals.
/* LICENSE
/* .ad
/* .fi
/* The Secure Mailer license must be distributed with this software.
/* AUTHOR(S)
/* Wietse Venema
/* IBM T.J. Watson Research
/* P.O. Box 704
/* Yorktown Heights, NY 10598, USA
/*--*/
/* System library. */
#include <sys_defs.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
/* Utility library. */
#include <msg.h>
#include <vstream.h>
#include <vstring.h>
#include <stringops.h>
#include <safe_open.h>
#include <warn_stat.h>
/* safe_open_exist - open existing file */
static VSTREAM *safe_open_exist(const char *path, int flags,
struct stat * fstat_st, VSTRING *why)
{
struct stat local_statbuf;
struct stat lstat_st;
int saved_errno;
VSTREAM *fp;
/*
* Open an existing file.
*/
if ((fp = vstream_fopen(path, flags & ~(O_CREAT | O_EXCL), 0)) == 0) {
saved_errno = errno;
vstring_sprintf(why, "cannot open file: %m");
errno = saved_errno;
return (0);
}
/*
* Examine the modes from the open file: it must have exactly one hard
* link (so that someone can't lure us into clobbering a sensitive file
* by making a hard link to it), and it must be a non-symlink file.
*/
if (fstat_st == 0)
fstat_st = &local_statbuf;
if (fstat(vstream_fileno(fp), fstat_st) < 0) {
msg_fatal("%s: bad open file status: %m", path);
} else if (fstat_st->st_nlink != 1) {
vstring_sprintf(why, "file has %d hard links",
(int) fstat_st->st_nlink);
errno = EPERM;
} else if (S_ISDIR(fstat_st->st_mode)) {
vstring_sprintf(why, "file is a directory");
errno = EISDIR;
}
/*
* Look up the file again, this time using lstat(). Compare the fstat()
* (open file) modes with the lstat() modes. If there is any difference,
* either we followed a symlink while opening an existing file, someone
* quickly changed the number of hard links, or someone replaced the file
* after the open() call. The link and mode tests aren't really necessary
* in daemon processes. Set-uid programs, on the other hand, can be
* slowed down by arbitrary amounts, and there it would make sense to
* compare even more file attributes, such as the inode generation number
* on systems that have one.
*
* Grr. Solaris /dev/whatever is a symlink. We'll have to make an exception
* for symlinks owned by root. NEVER, NEVER, make exceptions for symlinks
* owned by a non-root user. This would open a security hole when
* delivering mail to a world-writable mailbox directory.
*
* Sebastian Krahmer of SuSE brought to my attention that some systems have
* changed their semantics of link(symlink, newpath), such that the
* result is a hardlink to the symlink. For this reason, we now also
* require that the symlink's parent directory is writable only by root.
*/
else if (lstat(path, &lstat_st) < 0) {
vstring_sprintf(why, "file status changed unexpectedly: %m");
errno = EPERM;
} else if (S_ISLNK(lstat_st.st_mode)) {
if (lstat_st.st_uid == 0) {
VSTRING *parent_buf = vstring_alloc(100);
const char *parent_path = sane_dirname(parent_buf, path);
struct stat parent_st;
int parent_ok;
parent_ok = (stat(parent_path, &parent_st) == 0 /* not lstat */
&& parent_st.st_uid == 0
&& (parent_st.st_mode & (S_IWGRP | S_IWOTH)) == 0);
vstring_free(parent_buf);
if (parent_ok)
return (fp);
}
vstring_sprintf(why, "file is a symbolic link");
errno = EPERM;
} else if (fstat_st->st_dev != lstat_st.st_dev
|| fstat_st->st_ino != lstat_st.st_ino
#ifdef HAS_ST_GEN
|| fstat_st->st_gen != lstat_st.st_gen
#endif
|| fstat_st->st_nlink != lstat_st.st_nlink
|| fstat_st->st_mode != lstat_st.st_mode) {
vstring_sprintf(why, "file status changed unexpectedly");
errno = EPERM;
}
/*
* We are almost there...
*/
else {
return (fp);
}
/*
* End up here in case of fstat()/lstat() problems or inconsistencies.
*/
vstream_fclose(fp);
return (0);
}
/* safe_open_create - create new file */
static VSTREAM *safe_open_create(const char *path, int flags, mode_t mode,
struct stat * st, uid_t user, gid_t group, VSTRING *why)
{
VSTREAM *fp;
/*
* Create a non-existing file. This relies on O_CREAT | O_EXCL to not
* follow symbolic links.
*/
if ((fp = vstream_fopen(path, flags | (O_CREAT | O_EXCL), mode)) == 0) {
vstring_sprintf(why, "cannot create file exclusively: %m");
return (0);
}
/*
* Optionally look up the file attributes.
*/
if (st != 0 && fstat(vstream_fileno(fp), st) < 0)
msg_fatal("%s: bad open file status: %m", path);
/*
* Optionally change ownership after creating a new file. If there is a
* problem we should not attempt to delete the file. Something else may
* have opened the file in the mean time.
*/
#define CHANGE_OWNER(user, group) (user != (uid_t) -1 || group != (gid_t) -1)
if (CHANGE_OWNER(user, group)
&& fchown(vstream_fileno(fp), user, group) < 0) {
msg_warn("%s: cannot change file ownership: %m", path);
}
/*
* We are almost there...
*/
else {
return (fp);
}
/*
* End up here in case of trouble.
*/
vstream_fclose(fp);
return (0);
}
/* safe_open - safely open or create file */
VSTREAM *safe_open(const char *path, int flags, mode_t mode,
struct stat * st, uid_t user, gid_t group, VSTRING *why)
{
VSTREAM *fp;
switch (flags & (O_CREAT | O_EXCL)) {
/*
* Open an existing file, carefully.
*/
case 0:
return (safe_open_exist(path, flags, st, why));
/*
* Create a new file, carefully.
*/
case O_CREAT | O_EXCL:
return (safe_open_create(path, flags, mode, st, user, group, why));
/*
* Open an existing file or create a new one, carefully. When opening
* an existing file, we are prepared to deal with "no file" errors
* only. When creating a file, we are prepared for "file exists"
* errors only. Any other error means we better give up trying.
*/
case O_CREAT:
fp = safe_open_exist(path, flags, st, why);
if (fp == 0 && errno == ENOENT) {
fp = safe_open_create(path, flags, mode, st, user, group, why);
if (fp == 0 && errno == EEXIST)
fp = safe_open_exist(path, flags, st, why);
}
return (fp);
/*
* Interface violation. Sorry, but we must be strict.
*/
default:
msg_panic("safe_open: O_EXCL flag without O_CREAT flag");
}
}