/*
* safopen.c - hopefully secure wrappper for fopen()
* $Id: safopen.c,v 1.5 2005/05/10 13:49:17 rdenisc Exp $
*/
/***********************************************************************
* Copyright (C) 2002-2004 Remi Denis-Courmont. *
* This program is free software; you can redistribute and/or modify *
* it under the terms of the GNU General Public License as published *
* by the Free Software Foundation; version 2 of the license. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. *
* See the GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, you can get it from: *
* http://www.gnu.org/copyleft/gpl.html *
***********************************************************************/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <string.h> /* memset(), memcmp() */
#include <fcntl.h> /* open() */
#include <sys/stat.h> /* lstat(), fstat() */
#include <unistd.h> /* close(), lseek() */
#include <errno.h>
#include "secstdio.h"
#ifdef WINSOCK
FILE *
secure_fopen (const char *path, const char *mode)
{
return fopen (path, mode);
}
#else
# ifndef O_NOFOLLOW
# define O_NOFOLLOW 0 /* only *BSD & Linux and maybe few others have that */
# endif
# ifndef O_LARGEFILE
# define O_LARGEFILE 0
# endif
#if (defined (HAVE_STAT) && !defined (HAVE_LSTAT))
# define lstat( filename, structure ) stat (filename, structure)
/* lstat() is not defined by POSIX. We use stat() instead. */
# define HAVE_LSTAT 1
#endif
/*
* As secure as possible replacement fopen(path, "a").
* This function is used to open all log files (excepted stdout).
*
* DO submit patches against this function if you think it is still
* insecure, and have other ideas.
*
* I currently assume that the situation is as follow:
*
* # Read-only open mode is secure.
*
* # Append open mode (whether read is enabled or not) is secure as long as
* the file is regular or does not exists. We need lstat() and fstat().
* To prevent a race condition, we check the file after it has been opened.
*
* # Write open mode, which causes the file to be erased automatically, is
* never absolutely secure. The file has to be regular or not to exists.
* To minimize race condition risks, we need O_NOFOLLOW (but this does not
* protect from non link, non regular files that we may open, for example,
* FIFOs).
*
* It is up to the caller to set adequate eUID, eGID and umask before calling
* this function.
*/
FILE *
secure_fopen (const char *path, const char *mode)
{
int fd, flags;
const char *modep;
struct
{
unsigned readf:1;
unsigned writf:1;
unsigned creaf:1;
unsigned trunf:1;
unsigned appef:1;
} opts = { 0, 0, 0, 0, 0 };
FILE *stream;
#ifdef HAVE_LSTAT
struct stat st1, st2;
#endif
/* Digests open mode */
for (modep = mode; *modep; modep++)
switch (*modep)
{
case 'r':
opts.readf = 1;
break;
case 'w':
opts.writf = opts.creaf = opts.trunf = 1;
break;
case 'a':
opts.writf = opts.creaf = opts.appef = 1;
break;
case '+':
opts.readf = opts.writf = 1;
break;
}
if (opts.readf)
flags = (opts.writf) ? O_RDWR : O_RDONLY;
else
{
if (opts.writf)
flags = O_WRONLY;
else
{
errno = EACCES;
return NULL; /* don't read, nor write! */
}
}
if (opts.trunf)
flags |= O_TRUNC;
#ifdef HAVE_LSTAT
/* Gets current file state */
memset (&st1, 0, sizeof (st1));
if (lstat (path, &st1))
{
if ((errno != ENOENT) || !opts.creaf)
return NULL;
flags |= O_CREAT|O_EXCL;
/*
* The file did NOT exists at the time of lstat().
* O_CREAT will create it, while O_EXCL will prevent a race
* condition if it is created between lstat() and open() by
* another concurrent process.
*/
}
else /* The file already exists. We don't want to create it. */
{
opts.creaf = 0;
if (opts.writf)
/*
* If the file is to be opened in write mode, we have some
* safety checks:
*
* Refuses to open a non-regular file. This includes:
* # links: might be used to modify files holding important
* data
* # FIFO, some devices: may block output to log file and I
* know no legitimate use them anyway.
* # sockets, directories, other devices: usually cannot be
* opened or used properly anyway.
*
* Refuses to open hard-linked file for the same reason as
* links.
*/
{
if ((st1.st_nlink != 1) || !S_ISREG (st1.st_mode))
{
errno = EPERM;
return NULL;
}
flags |= O_NOFOLLOW;
}
}
#else
if (opts.creaf)
flags |= O_CREAT;
#endif
fd = open (path, flags | O_LARGEFILE, 0666);
if (fd == -1)
return NULL;
#ifdef HAVE_LSTAT
if (!opts.creaf)
{
/*
* Now, we have to make sure the file was not changed
* between lstat() and open().
*/
memset (&st2, 0, sizeof (st2));
if (fstat (fd, &st2)
|| memcmp (&st1, &st2, sizeof (struct stat)))
{
close (fd);
errno = EPERM;
return NULL;
}
}
#endif
/* Goes to end of file if needed */
if (opts.appef && (lseek (fd, 0, SEEK_END) == -1))
{
close (fd);
return NULL;
}
stream = fdopen (fd, mode);
if (stream == NULL)
close (fd);
return stream;
}
#endif /* WINSOCK */
syntax highlighted by Code2HTML, v. 0.9.1