1 /* Copyright (c) 2003, Roger Dingledine
2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3 * Copyright (c) 2007-2020, The Tor Project, Inc. */
4 /* See LICENSE for licensing information */
9 * \brief Read directories, and create directories with restrictive
13 #include "lib/fs/dir.h"
14 #include "lib/fs/path.h"
15 #include "lib/fs/userdb.h"
17 #include "lib/log/log.h"
18 #include "lib/log/util_bug.h"
19 #include "lib/log/win32err.h"
20 #include "lib/container/smartlist.h"
21 #include "lib/sandbox/sandbox.h"
22 #include "lib/malloc/malloc.h"
23 #include "lib/string/printf.h"
24 #include "lib/string/compat_string.h"
26 #ifdef HAVE_SYS_TYPES_H
27 #include <sys/types.h>
29 #ifdef HAVE_SYS_STAT_H
43 #else /* !(defined(_WIN32)) */
47 #endif /* defined(_WIN32) */
52 /** Check whether <b>dirname</b> exists and is private. If yes return 0.
53 * If <b>dirname</b> does not exist:
54 * - if <b>check</b>&CPD_CREATE, try to create it and return 0 on success.
55 * - if <b>check</b>&CPD_CHECK, and we think we can create it, return 0.
56 * - if <b>check</b>&CPD_CHECK is false, and the directory exists, return 0.
57 * - otherwise, return -1.
58 * If CPD_GROUP_OK is set, then it's okay if the directory
59 * is group-readable, but in all cases we create the directory mode 0700.
60 * If CPD_GROUP_READ is set, existing directory behaves as CPD_GROUP_OK and
61 * if the directory is created it will use mode 0750 with group read
62 * permission. Group read privileges also assume execute permission
63 * as norm for directories. If CPD_CHECK_MODE_ONLY is set, then we don't
64 * alter the directory permissions if they are too permissive:
66 * When effective_user is not NULL, check permissions against the given user
67 * and its primary group.
70 check_private_dir
,(const char *dirname
, cpd_check_t check
,
71 const char *effective_user
))
80 const struct passwd
*pw
= NULL
;
85 * Goal is to harden the implementation by removing any
86 * potential for race between stat() and chmod().
87 * chmod() accepts filename as argument. If an attacker can move
88 * the file between stat() and chmod(), a potential race exists.
90 * Several suggestions taken from:
91 * https://developer.apple.com/library/mac/documentation/
92 * Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html
96 * O_NOFOLLOW to ensure that it does not follow symbolic links */
97 fd
= open(sandbox_intern_string(dirname
), O_NOFOLLOW
);
99 /* Was there an error? Maybe the directory does not exist? */
102 if (errno
!= ENOENT
) {
103 /* Other directory error */
104 log_warn(LD_FS
, "Directory %s cannot be read: %s", dirname
,
109 /* Received ENOENT: Directory does not exist */
111 /* Should we create the directory? */
112 if (check
& CPD_CREATE
) {
113 log_info(LD_GENERAL
, "Creating directory %s", dirname
);
114 if (check
& CPD_GROUP_READ
) {
115 r
= mkdir(dirname
, 0750);
117 r
= mkdir(dirname
, 0700);
120 /* check for mkdir() error */
122 log_warn(LD_FS
, "Error creating directory %s: %s", dirname
,
127 /* we just created the directory. try to open it again.
128 * permissions on the directory will be checked again below.*/
129 fd
= open(sandbox_intern_string(dirname
), O_NOFOLLOW
);
132 log_warn(LD_FS
, "Could not reopen recently created directory %s: %s",
140 } else if (!(check
& CPD_CHECK
)) {
141 log_warn(LD_FS
, "Directory %s does not exist.", dirname
);
145 /* XXXX In the case where check==CPD_CHECK, we should look at the
146 * parent directory a little harder. */
152 //f = tor_strdup(dirname);
153 //clean_name_for_stat(f);
154 log_debug(LD_FS
, "stat()ing %s", dirname
);
155 //r = stat(sandbox_intern_string(f), &st);
158 log_warn(LD_FS
, "fstat() on directory %s failed.", dirname
);
164 /* check that dirname is a directory */
165 if (!(st
.st_mode
& S_IFDIR
)) {
166 log_warn(LD_FS
, "%s is not a directory", dirname
);
171 if (effective_user
) {
172 /* Look up the user and group information.
173 * If we have a problem, bail out. */
174 pw
= tor_getpwnam(effective_user
);
176 log_warn(LD_CONFIG
, "Error setting configured user: %s not found",
181 running_uid
= pw
->pw_uid
;
182 running_gid
= pw
->pw_gid
;
184 running_uid
= getuid();
185 running_gid
= getgid();
187 if (st
.st_uid
!= running_uid
) {
188 char *process_ownername
= NULL
, *file_ownername
= NULL
;
191 const struct passwd
*pw_running
= tor_getpwuid(running_uid
);
192 process_ownername
= pw_running
? tor_strdup(pw_running
->pw_name
) :
193 tor_strdup("<unknown>");
197 const struct passwd
*pw_stat
= tor_getpwuid(st
.st_uid
);
198 file_ownername
= pw_stat
? tor_strdup(pw_stat
->pw_name
) :
199 tor_strdup("<unknown>");
202 log_warn(LD_FS
, "%s is not owned by this user (%s, %d) but by "
203 "%s (%d). Perhaps you are running Tor as the wrong user?",
204 dirname
, process_ownername
, (int)running_uid
,
205 file_ownername
, (int)st
.st_uid
);
207 tor_free(process_ownername
);
208 tor_free(file_ownername
);
212 if ( (check
& (CPD_GROUP_OK
|CPD_GROUP_READ
))
213 && (st
.st_gid
!= running_gid
) && (st
.st_gid
!= 0)) {
215 char *process_groupname
= NULL
;
216 gr
= getgrgid(running_gid
);
217 process_groupname
= gr
? tor_strdup(gr
->gr_name
) : tor_strdup("<unknown>");
218 gr
= getgrgid(st
.st_gid
);
220 log_warn(LD_FS
, "%s is not owned by this group (%s, %d) but by group "
221 "%s (%d). Are you running Tor as the wrong user?",
222 dirname
, process_groupname
, (int)running_gid
,
223 gr
? gr
->gr_name
: "<unknown>", (int)st
.st_gid
);
225 tor_free(process_groupname
);
229 unsigned unwanted_bits
= 0;
230 if (check
& (CPD_GROUP_OK
|CPD_GROUP_READ
)) {
231 unwanted_bits
= 0027;
233 unwanted_bits
= 0077;
235 unsigned check_bits_filter
= ~0;
236 if (check
& CPD_RELAX_DIRMODE_CHECK
) {
237 check_bits_filter
= 0022;
239 if ((st
.st_mode
& unwanted_bits
& check_bits_filter
) != 0) {
241 if (check
& CPD_CHECK_MODE_ONLY
) {
242 log_warn(LD_FS
, "Permissions on directory %s are too permissive.",
247 log_warn(LD_FS
, "Fixing permissions on directory %s", dirname
);
248 new_mode
= st
.st_mode
;
249 new_mode
|= 0700; /* Owner should have rwx */
250 if (check
& CPD_GROUP_READ
) {
251 new_mode
|= 0050; /* Group should have rx */
253 new_mode
&= ~unwanted_bits
; /* Clear the bits that we didn't want set...*/
254 if (fchmod(fd
, new_mode
)) {
255 log_warn(LD_FS
, "Could not chmod directory %s: %s", dirname
,
265 #else /* defined(_WIN32) */
266 /* Win32 case: we can't open() a directory. */
267 (void)effective_user
;
269 char *f
= tor_strdup(dirname
);
270 clean_fname_for_stat(f
);
271 log_debug(LD_FS
, "stat()ing %s", f
);
272 r
= stat(sandbox_intern_string(f
), &st
);
275 if (errno
!= ENOENT
) {
276 log_warn(LD_FS
, "Directory %s cannot be read: %s", dirname
,
280 if (check
& CPD_CREATE
) {
281 log_info(LD_GENERAL
, "Creating directory %s", dirname
);
284 log_warn(LD_FS
, "Error creating directory %s: %s", dirname
,
288 } else if (!(check
& CPD_CHECK
)) {
289 log_warn(LD_FS
, "Directory %s does not exist.", dirname
);
294 if (!(st
.st_mode
& S_IFDIR
)) {
295 log_warn(LD_FS
, "%s is not a directory", dirname
);
299 #endif /* !defined(_WIN32) */
303 /** Return a new list containing the filenames in the directory <b>dirname</b>.
304 * Return NULL on error or if <b>dirname</b> is not a directory.
306 MOCK_IMPL(smartlist_t
*,
307 tor_listdir
, (const char *dirname
))
312 TCHAR tpattern
[MAX_PATH
] = {0};
313 char name
[MAX_PATH
*2+1] = {0};
315 WIN32_FIND_DATA findData
;
316 tor_asprintf(&pattern
, "%s\\*", dirname
);
318 mbstowcs(tpattern
,pattern
,MAX_PATH
);
320 strlcpy(tpattern
, pattern
, MAX_PATH
);
322 if (INVALID_HANDLE_VALUE
== (handle
= FindFirstFile(tpattern
, &findData
))) {
326 result
= smartlist_new();
329 wcstombs(name
,findData
.cFileName
,MAX_PATH
);
330 name
[sizeof(name
)-1] = '\0';
332 strlcpy(name
,findData
.cFileName
,sizeof(name
));
333 #endif /* defined(UNICODE) */
334 if (strcmp(name
, ".") &&
335 strcmp(name
, "..")) {
336 smartlist_add_strdup(result
, name
);
338 if (!FindNextFile(handle
, &findData
)) {
340 if ((err
= GetLastError()) != ERROR_NO_MORE_FILES
) {
341 char *errstr
= format_win32_error(err
);
342 log_warn(LD_FS
, "Error reading directory '%s': %s", dirname
, errstr
);
350 #else /* !defined(_WIN32) */
351 const char *prot_dname
= sandbox_intern_string(dirname
);
354 if (!(d
= opendir(prot_dname
)))
357 result
= smartlist_new();
358 while ((de
= readdir(d
))) {
359 if (!strcmp(de
->d_name
, ".") ||
360 !strcmp(de
->d_name
, ".."))
362 smartlist_add_strdup(result
, de
->d_name
);
365 #endif /* defined(_WIN32) */