Logo Search packages:      
Sourcecode: tcpreen version File versions  Download package

safopen.c

/*
 * safopen.c - hopefully secure wrappper for fopen()
 * $Id: safopen.c 171 2005-05-10 13:49:17Z rdenisc $
 */

/***********************************************************************
 *  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 */

Generated by  Doxygen 1.6.0   Back to index