Line data Source code
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"
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"
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 1120 : MOCK_IMPL(int,
70 : check_private_dir,(const char *dirname, cpd_check_t check,
71 : const char *effective_user))
72 : {
73 1120 : int r;
74 1120 : struct stat st;
75 :
76 1120 : tor_assert(dirname);
77 :
78 : #ifndef _WIN32
79 1120 : int fd;
80 1120 : const struct passwd *pw = NULL;
81 1120 : uid_t running_uid;
82 1120 : 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 1120 : fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
98 :
99 : /* Was there an error? Maybe the directory does not exist? */
100 1120 : if (fd == -1) {
101 :
102 340 : if (errno != ENOENT) {
103 : /* Other directory error */
104 1 : log_warn(LD_FS, "Directory %s cannot be read: %s", dirname,
105 : strerror(errno));
106 1 : return -1;
107 : }
108 :
109 : /* Received ENOENT: Directory does not exist */
110 :
111 : /* Should we create the directory? */
112 339 : if (check & CPD_CREATE) {
113 96 : log_info(LD_GENERAL, "Creating directory %s", dirname);
114 96 : if (check & CPD_GROUP_READ) {
115 6 : r = mkdir(dirname, 0750);
116 : } else {
117 90 : r = mkdir(dirname, 0700);
118 : }
119 :
120 : /* check for mkdir() error */
121 96 : if (r) {
122 1 : log_warn(LD_FS, "Error creating directory %s: %s", dirname,
123 : strerror(errno));
124 1 : 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 95 : fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
130 :
131 95 : if (fd == -1) {
132 0 : log_warn(LD_FS, "Could not reopen recently created directory %s: %s",
133 : dirname,
134 : strerror(errno));
135 0 : return -1;
136 : } else {
137 95 : close(fd);
138 : }
139 :
140 243 : } else if (!(check & CPD_CHECK)) {
141 0 : log_warn(LD_FS, "Directory %s does not exist.", dirname);
142 0 : 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 338 : return 0;
148 : }
149 :
150 780 : tor_assert(fd >= 0);
151 :
152 : //f = tor_strdup(dirname);
153 : //clean_name_for_stat(f);
154 780 : log_debug(LD_FS, "stat()ing %s", dirname);
155 : //r = stat(sandbox_intern_string(f), &st);
156 780 : r = fstat(fd, &st);
157 780 : if (r == -1) {
158 0 : log_warn(LD_FS, "fstat() on directory %s failed.", dirname);
159 0 : close(fd);
160 0 : return -1;
161 : }
162 : //tor_free(f);
163 :
164 : /* check that dirname is a directory */
165 780 : if (!(st.st_mode & S_IFDIR)) {
166 3 : log_warn(LD_FS, "%s is not a directory", dirname);
167 3 : close(fd);
168 3 : return -1;
169 : }
170 :
171 777 : if (effective_user) {
172 : /* Look up the user and group information.
173 : * If we have a problem, bail out. */
174 0 : pw = tor_getpwnam(effective_user);
175 0 : if (pw == NULL) {
176 0 : log_warn(LD_CONFIG, "Error setting configured user: %s not found",
177 : effective_user);
178 0 : close(fd);
179 0 : return -1;
180 : }
181 0 : running_uid = pw->pw_uid;
182 0 : running_gid = pw->pw_gid;
183 : } else {
184 777 : running_uid = getuid();
185 777 : running_gid = getgid();
186 : }
187 777 : if (st.st_uid != running_uid) {
188 0 : char *process_ownername = NULL, *file_ownername = NULL;
189 :
190 : {
191 0 : const struct passwd *pw_running = tor_getpwuid(running_uid);
192 0 : process_ownername = pw_running ? tor_strdup(pw_running->pw_name) :
193 0 : tor_strdup("<unknown>");
194 : }
195 :
196 : {
197 0 : const struct passwd *pw_stat = tor_getpwuid(st.st_uid);
198 0 : file_ownername = pw_stat ? tor_strdup(pw_stat->pw_name) :
199 0 : tor_strdup("<unknown>");
200 : }
201 :
202 0 : 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 0 : tor_free(process_ownername);
208 0 : tor_free(file_ownername);
209 0 : close(fd);
210 0 : return -1;
211 : }
212 777 : if ( (check & (CPD_GROUP_OK|CPD_GROUP_READ))
213 6 : && (st.st_gid != running_gid) && (st.st_gid != 0)) {
214 0 : struct group *gr;
215 0 : char *process_groupname = NULL;
216 0 : gr = getgrgid(running_gid);
217 0 : process_groupname = gr ? tor_strdup(gr->gr_name) : tor_strdup("<unknown>");
218 0 : gr = getgrgid(st.st_gid);
219 :
220 0 : 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 0 : tor_free(process_groupname);
226 0 : close(fd);
227 0 : return -1;
228 : }
229 777 : unsigned unwanted_bits = 0;
230 777 : if (check & (CPD_GROUP_OK|CPD_GROUP_READ)) {
231 : unwanted_bits = 0027;
232 : } else {
233 771 : unwanted_bits = 0077;
234 : }
235 777 : unsigned check_bits_filter = ~0;
236 777 : if (check & CPD_RELAX_DIRMODE_CHECK) {
237 0 : check_bits_filter = 0022;
238 : }
239 777 : if ((st.st_mode & unwanted_bits & check_bits_filter) != 0) {
240 1 : unsigned new_mode;
241 1 : if (check & CPD_CHECK_MODE_ONLY) {
242 0 : log_warn(LD_FS, "Permissions on directory %s are too permissive.",
243 : dirname);
244 0 : close(fd);
245 0 : return -1;
246 : }
247 1 : log_warn(LD_FS, "Fixing permissions on directory %s", dirname);
248 1 : new_mode = st.st_mode;
249 1 : new_mode |= 0700; /* Owner should have rwx */
250 1 : if (check & CPD_GROUP_READ) {
251 0 : new_mode |= 0050; /* Group should have rx */
252 : }
253 1 : new_mode &= ~unwanted_bits; /* Clear the bits that we didn't want set...*/
254 1 : if (fchmod(fd, new_mode)) {
255 0 : log_warn(LD_FS, "Could not chmod directory %s: %s", dirname,
256 : strerror(errno));
257 0 : close(fd);
258 0 : return -1;
259 : } else {
260 1 : close(fd);
261 1 : return 0;
262 : }
263 : }
264 776 : 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);
270 : clean_fname_for_stat(f);
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 776 : 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 : */
306 223 : MOCK_IMPL(smartlist_t *,
307 : tor_listdir, (const char *dirname))
308 : {
309 223 : 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 223 : const char *prot_dname = sandbox_intern_string(dirname);
352 223 : DIR *d;
353 223 : struct dirent *de;
354 223 : if (!(d = opendir(prot_dname)))
355 : return NULL;
356 :
357 220 : result = smartlist_new();
358 1354 : while ((de = readdir(d))) {
359 1134 : if (!strcmp(de->d_name, ".") ||
360 914 : !strcmp(de->d_name, ".."))
361 440 : continue;
362 694 : smartlist_add_strdup(result, de->d_name);
363 : }
364 220 : closedir(d);
365 : #endif /* defined(_WIN32) */
366 220 : return result;
367 : }
|