dir.c (10530B)
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 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); 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 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 MOCK_IMPL(smartlist_t *, 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 }