summaryrefslogtreecommitdiff
path: root/lib/chdir-safer.c
blob: e97b2fce6e03e8bd5653cec4944cb2210b243404 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/* like chdir(2), but safer, if possible
   Copyright (C) 2005 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   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, write to the Free Software Foundation,
   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */

/* written by Jim Meyering */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#include "chdir-safer.h"

#include <stdbool.h>
#include <stdio.h>
#include <assert.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "fcntl--.h" /* for the open->open_safer mapping */

/* Assuming we can use open-with-O_NOFOLLOW, open DIR and fchdir into it --
   but fail (setting errno to EACCES) if anyone replaces it with a symlink,
   or otherwise changes its type.  Return zero upon success.  */
static int
fchdir_new (char const *dir)
{
  int fail = 1;
  struct stat sb;
  int saved_errno = 0;
  int fd = open (dir, O_NOFOLLOW | O_RDONLY | O_NDELAY);

  assert (O_NOFOLLOW);

  if (0 <= fd
      && fstat (fd, &sb) == 0
      /* Given the entry we've just created, if its type has changed, then
	 someone may be trying to do something nasty.  However, the risk of
	 such an attack is so low that it isn't worth a special diagnostic.
	 Simply skip the fchdir and set errno, so that the caller can
	 report the failure.  */
      && (S_ISDIR (sb.st_mode) || ((errno = EACCES), 0))
      && fchdir (fd) == 0)
    {
      fail = 0;
    }
  else
    {
      saved_errno = errno;
    }

  if (0 < fd && close (fd) != 0 && saved_errno == 0)
    saved_errno = errno;

  errno = saved_errno;
  return fail;
}

/* Just like chmod, but don't follow symlinks.
   This can avoid a minor race condition between when a directory is created
   and when we chdir into it.  If the open syscall honors the O_NOFOLLOW flag,
   then use open,fchdir,close.  Otherwise, just call chdir.  */
int
chdir_no_follow (char const *file)
{
  /* Using open and fchmod is reliable only if open honors the O_NOFOLLOW
     flag.  Otherwise, an attacker could simply replace the just-created
     entry with a symlink, and open would follow it blindly.  */
  return (O_NOFOLLOW
	  ? fchdir_new (file)
	  : chdir      (file));
}