Tor 0.4.9.0-alpha-dev
dir.c
Go to the documentation of this file.
1/* Copyright (c) 2003, Roger Dingledine
2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
3 * Copyright (c) 2007-2021, The Tor Project, Inc. */
4/* See LICENSE for licensing information */
5
6/**
7 * \file dir.c
8 *
9 * \brief Read directories, and create directories with restrictive
10 * permissions.
11 **/
12
13#include "lib/fs/dir.h"
14#include "lib/fs/path.h"
15#include "lib/fs/userdb.h"
16
17#include "lib/log/log.h"
18#include "lib/log/util_bug.h"
19#include "lib/log/win32err.h"
21#include "lib/sandbox/sandbox.h"
22#include "lib/malloc/malloc.h"
23#include "lib/string/printf.h"
25
26#ifdef HAVE_SYS_TYPES_H
27#include <sys/types.h>
28#endif
29#ifdef HAVE_SYS_STAT_H
30#include <sys/stat.h>
31#endif
32#ifdef HAVE_UNISTD_H
33#include <unistd.h>
34#endif
35#ifdef HAVE_FCNTL_H
36#include <fcntl.h>
37#endif
38
39#ifdef _WIN32
40#include <io.h>
41#include <direct.h>
42#include <windows.h>
43#else /* !(defined(_WIN32)) */
44#include <dirent.h>
45#include <pwd.h>
46#include <grp.h>
47#endif /* defined(_WIN32) */
48
49#include <errno.h>
50#include <string.h>
51
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:
65 * we just return -1.
66 * When effective_user is not NULL, check permissions against the given user
67 * and its primary group.
68 */
69MOCK_IMPL(int,
70check_private_dir,(const char *dirname, cpd_check_t check,
71 const char *effective_user))
72{
73 int r;
74 struct stat st;
75
76 tor_assert(dirname);
77
78#ifndef _WIN32
79 int fd;
80 const struct passwd *pw = NULL;
81 uid_t running_uid;
82 gid_t running_gid;
83
84 /*
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.
89 *
90 * Several suggestions taken from:
91 * https://developer.apple.com/library/mac/documentation/
92 * Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html
93 */
94
95 /* Open directory.
96 * O_NOFOLLOW to ensure that it does not follow symbolic links */
97 fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
98
99 /* Was there an error? Maybe the directory does not exist? */
100 if (fd == -1) {
101
102 if (errno != ENOENT) {
103 /* Other directory error */
104 log_warn(LD_FS, "Directory %s cannot be read: %s", dirname,
105 strerror(errno));
106 return -1;
107 }
108
109 /* Received ENOENT: Directory does not exist */
110
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);
116 } else {
117 r = mkdir(dirname, 0700);
118 }
119
120 /* check for mkdir() error */
121 if (r) {
122 log_warn(LD_FS, "Error creating directory %s: %s", dirname,
123 strerror(errno));
124 return -1;
125 }
126
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);
130
131 if (fd == -1) {
132 log_warn(LD_FS, "Could not reopen recently created directory %s: %s",
133 dirname,
134 strerror(errno));
135 return -1;
136 } else {
137 close(fd);
138 }
139
140 } else if (!(check & CPD_CHECK)) {
141 log_warn(LD_FS, "Directory %s does not exist.", dirname);
142 return -1;
143 }
144
145 /* XXXX In the case where check==CPD_CHECK, we should look at the
146 * parent directory a little harder. */
147 return 0;
148 }
149
150 tor_assert(fd >= 0);
151
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);
156 r = fstat(fd, &st);
157 if (r == -1) {
158 log_warn(LD_FS, "fstat() on directory %s failed.", dirname);
159 close(fd);
160 return -1;
161 }
162 //tor_free(f);
163
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);
167 close(fd);
168 return -1;
169 }
170
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);
175 if (pw == NULL) {
176 log_warn(LD_CONFIG, "Error setting configured user: %s not found",
177 effective_user);
178 close(fd);
179 return -1;
180 }
181 running_uid = pw->pw_uid;
182 running_gid = pw->pw_gid;
183 } else {
184 running_uid = getuid();
185 running_gid = getgid();
186 }
187 if (st.st_uid != running_uid) {
188 char *process_ownername = NULL, *file_ownername = NULL;
189
190 {
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>");
194 }
195
196 {
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>");
200 }
201
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);
206
207 tor_free(process_ownername);
208 tor_free(file_ownername);
209 close(fd);
210 return -1;
211 }
212 if ( (check & (CPD_GROUP_OK|CPD_GROUP_READ))
213 && (st.st_gid != running_gid) && (st.st_gid != 0)) {
214 struct group *gr;
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);
219
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);
224
225 tor_free(process_groupname);
226 close(fd);
227 return -1;
228 }
229 unsigned unwanted_bits = 0;
230 if (check & (CPD_GROUP_OK|CPD_GROUP_READ)) {
231 unwanted_bits = 0027;
232 } else {
233 unwanted_bits = 0077;
234 }
235 unsigned check_bits_filter = ~0;
236 if (check & CPD_RELAX_DIRMODE_CHECK) {
237 check_bits_filter = 0022;
238 }
239 if ((st.st_mode & unwanted_bits & check_bits_filter) != 0) {
240 unsigned new_mode;
241 if (check & CPD_CHECK_MODE_ONLY) {
242 log_warn(LD_FS, "Permissions on directory %s are too permissive.",
243 dirname);
244 close(fd);
245 return -1;
246 }
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 */
252 }
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,
256 strerror(errno));
257 close(fd);
258 return -1;
259 } else {
260 close(fd);
261 return 0;
262 }
263 }
264 close(fd);
265#else /* defined(_WIN32) */
266 /* Win32 case: we can't open() a directory. */
267 (void)effective_user;
268
269 char *f = tor_strdup(dirname);
271 log_debug(LD_FS, "stat()ing %s", f);
272 r = stat(sandbox_intern_string(f), &st);
273 tor_free(f);
274 if (r) {
275 if (errno != ENOENT) {
276 log_warn(LD_FS, "Directory %s cannot be read: %s", dirname,
277 strerror(errno));
278 return -1;
279 }
280 if (check & CPD_CREATE) {
281 log_info(LD_GENERAL, "Creating directory %s", dirname);
282 r = mkdir(dirname);
283 if (r) {
284 log_warn(LD_FS, "Error creating directory %s: %s", dirname,
285 strerror(errno));
286 return -1;
287 }
288 } else if (!(check & CPD_CHECK)) {
289 log_warn(LD_FS, "Directory %s does not exist.", dirname);
290 return -1;
291 }
292 return 0;
293 }
294 if (!(st.st_mode & S_IFDIR)) {
295 log_warn(LD_FS, "%s is not a directory", dirname);
296 return -1;
297 }
298
299#endif /* !defined(_WIN32) */
300 return 0;
301}
302
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.
305 */
307tor_listdir, (const char *dirname))
308{
309 smartlist_t *result;
310#ifdef _WIN32
311 char *pattern=NULL;
312 TCHAR tpattern[MAX_PATH] = {0};
313 char name[MAX_PATH*2+1] = {0};
314 HANDLE handle;
315 WIN32_FIND_DATA findData;
316 tor_asprintf(&pattern, "%s\\*", dirname);
317#ifdef UNICODE
318 mbstowcs(tpattern,pattern,MAX_PATH);
319#else
320 strlcpy(tpattern, pattern, MAX_PATH);
321#endif
322 if (INVALID_HANDLE_VALUE == (handle = FindFirstFile(tpattern, &findData))) {
323 tor_free(pattern);
324 return NULL;
325 }
326 result = smartlist_new();
327 while (1) {
328#ifdef UNICODE
329 wcstombs(name,findData.cFileName,MAX_PATH);
330 name[sizeof(name)-1] = '\0';
331#else
332 strlcpy(name,findData.cFileName,sizeof(name));
333#endif /* defined(UNICODE) */
334 if (strcmp(name, ".") &&
335 strcmp(name, "..")) {
336 smartlist_add_strdup(result, name);
337 }
338 if (!FindNextFile(handle, &findData)) {
339 DWORD err;
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);
343 tor_free(errstr);
344 }
345 break;
346 }
347 }
348 FindClose(handle);
349 tor_free(pattern);
350#else /* !defined(_WIN32) */
351 const char *prot_dname = sandbox_intern_string(dirname);
352 DIR *d;
353 struct dirent *de;
354 if (!(d = opendir(prot_dname)))
355 return NULL;
356
357 result = smartlist_new();
358 while ((de = readdir(d))) {
359 if (!strcmp(de->d_name, ".") ||
360 !strcmp(de->d_name, ".."))
361 continue;
362 smartlist_add_strdup(result, de->d_name);
363 }
364 closedir(d);
365#endif /* defined(_WIN32) */
366 return result;
367}
Header for compat_string.c.
const char * name
Definition: config.c:2462
int check_private_dir(const char *dirname, cpd_check_t check, const char *effective_user)
Definition: dir.c:71
smartlist_t * tor_listdir(const char *dirname)
Definition: dir.c:307
Header for dir.c.
unsigned int cpd_check_t
Definition: dir.h:20
Headers for log.c.
#define LD_FS
Definition: log.h:70
#define LD_GENERAL
Definition: log.h:62
#define LD_CONFIG
Definition: log.h:68
Headers for util_malloc.c.
#define tor_free(p)
Definition: malloc.h:56
void clean_fname_for_stat(char *name)
Definition: path.c:164
Header for path.c.
int tor_asprintf(char **strp, const char *fmt,...)
Definition: printf.c:75
Header for printf.c.
Header file for sandbox.c.
#define sandbox_intern_string(s)
Definition: sandbox.h:110
Header for smartlist.c.
void smartlist_add_strdup(struct smartlist_t *sl, const char *string)
smartlist_t * smartlist_new(void)
#define MOCK_IMPL(rv, funcname, arglist)
Definition: testsupport.h:133
const struct passwd * tor_getpwnam(const char *username)
Definition: userdb.c:70
const struct passwd * tor_getpwuid(uid_t uid)
Definition: userdb.c:106
Header for userdb.c.
Macros to manage assertions, fatal and non-fatal.
#define tor_assert(expr)
Definition: util_bug.h:103
Header for win32err.c.