LCOV - code coverage report
Current view: top level - lib/fs - dir.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 72 115 62.6 %
Date: 2021-11-24 03:28:48 Functions: 2 2 100.0 %

          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             : }

Generated by: LCOV version 1.14