tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

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 }