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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
|
/* like chmod(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 "chmod-safer.h"
#include <stdbool.h>
#include <stdio.h>
#include <assert.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include "fcntl--.h" /* for the open->open_safer mapping */
#ifndef HAVE_FCHMOD
# define HAVE_FCHMOD false
# define fchmod(fd, mode) (-1)
#endif
/* Return true if the file corresponding to stat buffer pointer ST is of type,
TYPE, and if its major and minor device numbers match (if appropriate:
when the type is S_IFIFO or S_IFBLK). */
static inline bool
same_file_type (struct stat const *st, dev_t device, mode_t type)
{
/* The types must always match. */
if ( ! (st->st_mode & type))
return false;
/* For character and block devices, the major and minor device
numbers must match, too. */
if (type & (S_IFCHR | S_IFBLK))
return st->st_rdev == device;
return true;
}
/* Assuming we can use open-with-O_NOFOLLOW and fchmod, set the permissions
of FILE to MODE, using fchmod -- but fail (setting errno to EACCES) if
anyone hard-links to FILE, replaces it with a symlink, or otherwise
changes its type. Return zero upon success. */
static int
fchmod_new (char const *file, mode_t mode, dev_t device, mode_t file_type)
{
int fail = 1;
struct stat sb;
int saved_errno = 0;
int fd = open (file, 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 link count is
not 1 or its type/device 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 fchmod and set errno, so that the
code below reports the failure to set permissions.
Note that we don't check the link count if the expected
type is `directory'. */
&& (((sb.st_nlink == 1 || file_type == S_IFDIR)
&& same_file_type (&sb, device, file_type))
|| ((errno = EACCES), 0))
&& fchmod (fd, mode) == 0)
{
fail = 0;
}
else
{
saved_errno = errno;
}
if (0 <= fd && close (fd) != 0 && saved_errno == 0)
saved_errno = errno;
errno = saved_errno;
return fail;
}
/* Use a safer variant of chmod, if the underlying system facilities permit.
This can avoid a minor race condition between when a file/device/directory
is created and when we set its permissions. If the system provides an
fchmod function and if its open syscall honors the O_NOFOLLOW flag, then
use open,fchmod,close. Otherwise, just call chmod. FILE and MODE are
just as for chmod. DEVICE is the combination (a la stat.st_rdev) of major
and minor device numbers. FILE_TYPE is one of the S_IF* values, e.g.,
S_IFBLK, S_IFCHR, etc. */
int
chmod_safer (char const *file, mode_t mode, dev_t device, mode_t file_type)
{
/* 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 (HAVE_FCHMOD && O_NOFOLLOW
? fchmod_new (file, mode, device, file_type)
: chmod (file, mode));
}
|