Tor  0.4.7.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  */
69 MOCK_IMPL(int,
70 check_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  */
307 tor_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:2434
smartlist_t * tor_listdir(const char *dirname)
Definition: dir.c:307
int check_private_dir(const char *dirname, cpd_check_t check, const char *effective_user)
Definition: dir.c:71
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:52
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.
smartlist_t * smartlist_new(void)
void smartlist_add_strdup(struct smartlist_t *sl, const char *string)
#define MOCK_IMPL(rv, funcname, arglist)
Definition: testsupport.h:133
const struct passwd * tor_getpwuid(uid_t uid)
Definition: userdb.c:106
const struct passwd * tor_getpwnam(const char *username)
Definition: userdb.c:70
Header for userdb.c.
Macros to manage assertions, fatal and non-fatal.
#define tor_assert(expr)
Definition: util_bug.h:102
Header for win32err.c.