123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367 |
- /* Copyright (c) 2003, Roger Dingledine
- * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2018, The Tor Project, Inc. */
- /* See LICENSE for licensing information */
- /**
- * \file dir.c
- *
- * \brief Read directories, and create directories with restrictive
- * permissions.
- **/
- #include "lib/fs/dir.h"
- #include "lib/fs/path.h"
- #include "lib/fs/userdb.h"
- #include "lib/log/log.h"
- #include "lib/log/util_bug.h"
- #include "lib/log/win32err.h"
- #include "lib/container/smartlist.h"
- #include "lib/sandbox/sandbox.h"
- #include "lib/malloc/malloc.h"
- #include "lib/string/printf.h"
- #include "lib/string/compat_string.h"
- #ifdef HAVE_SYS_TYPES_H
- #include <sys/types.h>
- #endif
- #ifdef HAVE_SYS_STAT_H
- #include <sys/stat.h>
- #endif
- #ifdef HAVE_UNISTD_H
- #include <unistd.h>
- #endif
- #ifdef HAVE_FCNTL_H
- #include <fcntl.h>
- #endif
- #ifdef _WIN32
- #include <io.h>
- #include <direct.h>
- #include <windows.h>
- #else /* !(defined(_WIN32)) */
- #include <dirent.h>
- #include <pwd.h>
- #include <grp.h>
- #endif /* defined(_WIN32) */
- #include <errno.h>
- #include <string.h>
- /** Check whether <b>dirname</b> exists and is private. If yes return 0.
- * If <b>dirname</b> does not exist:
- * - if <b>check</b>&CPD_CREATE, try to create it and return 0 on success.
- * - if <b>check</b>&CPD_CHECK, and we think we can create it, return 0.
- * - if <b>check</b>&CPD_CHECK is false, and the directory exists, return 0.
- * - otherwise, return -1.
- * If CPD_GROUP_OK is set, then it's okay if the directory
- * is group-readable, but in all cases we create the directory mode 0700.
- * If CPD_GROUP_READ is set, existing directory behaves as CPD_GROUP_OK and
- * if the directory is created it will use mode 0750 with group read
- * permission. Group read privileges also assume execute permission
- * as norm for directories. If CPD_CHECK_MODE_ONLY is set, then we don't
- * alter the directory permissions if they are too permissive:
- * we just return -1.
- * When effective_user is not NULL, check permissions against the given user
- * and its primary group.
- */
- MOCK_IMPL(int,
- check_private_dir,(const char *dirname, cpd_check_t check,
- const char *effective_user))
- {
- int r;
- struct stat st;
- tor_assert(dirname);
- #ifndef _WIN32
- int fd;
- const struct passwd *pw = NULL;
- uid_t running_uid;
- gid_t running_gid;
- /*
- * Goal is to harden the implementation by removing any
- * potential for race between stat() and chmod().
- * chmod() accepts filename as argument. If an attacker can move
- * the file between stat() and chmod(), a potential race exists.
- *
- * Several suggestions taken from:
- * https://developer.apple.com/library/mac/documentation/
- * Security/Conceptual/SecureCodingGuide/Articles/RaceConditions.html
- */
- /* Open directory.
- * O_NOFOLLOW to ensure that it does not follow symbolic links */
- fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
- /* Was there an error? Maybe the directory does not exist? */
- if (fd == -1) {
- if (errno != ENOENT) {
- /* Other directory error */
- log_warn(LD_FS, "Directory %s cannot be read: %s", dirname,
- strerror(errno));
- return -1;
- }
- /* Received ENOENT: Directory does not exist */
- /* Should we create the directory? */
- if (check & CPD_CREATE) {
- log_info(LD_GENERAL, "Creating directory %s", dirname);
- if (check & CPD_GROUP_READ) {
- r = mkdir(dirname, 0750);
- } else {
- r = mkdir(dirname, 0700);
- }
- /* check for mkdir() error */
- if (r) {
- log_warn(LD_FS, "Error creating directory %s: %s", dirname,
- strerror(errno));
- return -1;
- }
- /* we just created the directory. try to open it again.
- * permissions on the directory will be checked again below.*/
- fd = open(sandbox_intern_string(dirname), O_NOFOLLOW);
- if (fd == -1) {
- log_warn(LD_FS, "Could not reopen recently created directory %s: %s",
- dirname,
- strerror(errno));
- return -1;
- } else {
- close(fd);
- }
- } else if (!(check & CPD_CHECK)) {
- log_warn(LD_FS, "Directory %s does not exist.", dirname);
- return -1;
- }
- /* XXXX In the case where check==CPD_CHECK, we should look at the
- * parent directory a little harder. */
- return 0;
- }
- tor_assert(fd >= 0);
- //f = tor_strdup(dirname);
- //clean_name_for_stat(f);
- log_debug(LD_FS, "stat()ing %s", dirname);
- //r = stat(sandbox_intern_string(f), &st);
- r = fstat(fd, &st);
- if (r == -1) {
- log_warn(LD_FS, "fstat() on directory %s failed.", dirname);
- close(fd);
- return -1;
- }
- //tor_free(f);
- /* check that dirname is a directory */
- if (!(st.st_mode & S_IFDIR)) {
- log_warn(LD_FS, "%s is not a directory", dirname);
- close(fd);
- return -1;
- }
- if (effective_user) {
- /* Look up the user and group information.
- * If we have a problem, bail out. */
- pw = tor_getpwnam(effective_user);
- if (pw == NULL) {
- log_warn(LD_CONFIG, "Error setting configured user: %s not found",
- effective_user);
- close(fd);
- return -1;
- }
- running_uid = pw->pw_uid;
- running_gid = pw->pw_gid;
- } else {
- running_uid = getuid();
- running_gid = getgid();
- }
- if (st.st_uid != running_uid) {
- char *process_ownername = NULL, *file_ownername = NULL;
- {
- const struct passwd *pw_running = tor_getpwuid(running_uid);
- process_ownername = pw_running ? tor_strdup(pw_running->pw_name) :
- tor_strdup("<unknown>");
- }
- {
- const struct passwd *pw_stat = tor_getpwuid(st.st_uid);
- file_ownername = pw_stat ? tor_strdup(pw_stat->pw_name) :
- tor_strdup("<unknown>");
- }
- log_warn(LD_FS, "%s is not owned by this user (%s, %d) but by "
- "%s (%d). Perhaps you are running Tor as the wrong user?",
- dirname, process_ownername, (int)running_uid,
- file_ownername, (int)st.st_uid);
- tor_free(process_ownername);
- tor_free(file_ownername);
- close(fd);
- return -1;
- }
- if ( (check & (CPD_GROUP_OK|CPD_GROUP_READ))
- && (st.st_gid != running_gid) && (st.st_gid != 0)) {
- struct group *gr;
- char *process_groupname = NULL;
- gr = getgrgid(running_gid);
- process_groupname = gr ? tor_strdup(gr->gr_name) : tor_strdup("<unknown>");
- gr = getgrgid(st.st_gid);
- log_warn(LD_FS, "%s is not owned by this group (%s, %d) but by group "
- "%s (%d). Are you running Tor as the wrong user?",
- dirname, process_groupname, (int)running_gid,
- gr ? gr->gr_name : "<unknown>", (int)st.st_gid);
- tor_free(process_groupname);
- close(fd);
- return -1;
- }
- unsigned unwanted_bits = 0;
- if (check & (CPD_GROUP_OK|CPD_GROUP_READ)) {
- unwanted_bits = 0027;
- } else {
- unwanted_bits = 0077;
- }
- unsigned check_bits_filter = ~0;
- if (check & CPD_RELAX_DIRMODE_CHECK) {
- check_bits_filter = 0022;
- }
- if ((st.st_mode & unwanted_bits & check_bits_filter) != 0) {
- unsigned new_mode;
- if (check & CPD_CHECK_MODE_ONLY) {
- log_warn(LD_FS, "Permissions on directory %s are too permissive.",
- dirname);
- close(fd);
- return -1;
- }
- log_warn(LD_FS, "Fixing permissions on directory %s", dirname);
- new_mode = st.st_mode;
- new_mode |= 0700; /* Owner should have rwx */
- if (check & CPD_GROUP_READ) {
- new_mode |= 0050; /* Group should have rx */
- }
- new_mode &= ~unwanted_bits; /* Clear the bits that we didn't want set...*/
- if (fchmod(fd, new_mode)) {
- log_warn(LD_FS, "Could not chmod directory %s: %s", dirname,
- strerror(errno));
- close(fd);
- return -1;
- } else {
- close(fd);
- return 0;
- }
- }
- close(fd);
- #else /* !(!defined(_WIN32)) */
- /* Win32 case: we can't open() a directory. */
- (void)effective_user;
- char *f = tor_strdup(dirname);
- clean_fname_for_stat(f);
- log_debug(LD_FS, "stat()ing %s", f);
- r = stat(sandbox_intern_string(f), &st);
- tor_free(f);
- if (r) {
- if (errno != ENOENT) {
- log_warn(LD_FS, "Directory %s cannot be read: %s", dirname,
- strerror(errno));
- return -1;
- }
- if (check & CPD_CREATE) {
- log_info(LD_GENERAL, "Creating directory %s", dirname);
- r = mkdir(dirname);
- if (r) {
- log_warn(LD_FS, "Error creating directory %s: %s", dirname,
- strerror(errno));
- return -1;
- }
- } else if (!(check & CPD_CHECK)) {
- log_warn(LD_FS, "Directory %s does not exist.", dirname);
- return -1;
- }
- return 0;
- }
- if (!(st.st_mode & S_IFDIR)) {
- log_warn(LD_FS, "%s is not a directory", dirname);
- return -1;
- }
- #endif /* !defined(_WIN32) */
- return 0;
- }
- /** Return a new list containing the filenames in the directory <b>dirname</b>.
- * Return NULL on error or if <b>dirname</b> is not a directory.
- */
- MOCK_IMPL(smartlist_t *,
- tor_listdir, (const char *dirname))
- {
- smartlist_t *result;
- #ifdef _WIN32
- char *pattern=NULL;
- TCHAR tpattern[MAX_PATH] = {0};
- char name[MAX_PATH*2+1] = {0};
- HANDLE handle;
- WIN32_FIND_DATA findData;
- tor_asprintf(&pattern, "%s\\*", dirname);
- #ifdef UNICODE
- mbstowcs(tpattern,pattern,MAX_PATH);
- #else
- strlcpy(tpattern, pattern, MAX_PATH);
- #endif
- if (INVALID_HANDLE_VALUE == (handle = FindFirstFile(tpattern, &findData))) {
- tor_free(pattern);
- return NULL;
- }
- result = smartlist_new();
- while (1) {
- #ifdef UNICODE
- wcstombs(name,findData.cFileName,MAX_PATH);
- name[sizeof(name)-1] = '\0';
- #else
- strlcpy(name,findData.cFileName,sizeof(name));
- #endif /* defined(UNICODE) */
- if (strcmp(name, ".") &&
- strcmp(name, "..")) {
- smartlist_add_strdup(result, name);
- }
- if (!FindNextFile(handle, &findData)) {
- DWORD err;
- if ((err = GetLastError()) != ERROR_NO_MORE_FILES) {
- char *errstr = format_win32_error(err);
- log_warn(LD_FS, "Error reading directory '%s': %s", dirname, errstr);
- tor_free(errstr);
- }
- break;
- }
- }
- FindClose(handle);
- tor_free(pattern);
- #else /* !(defined(_WIN32)) */
- const char *prot_dname = sandbox_intern_string(dirname);
- DIR *d;
- struct dirent *de;
- if (!(d = opendir(prot_dname)))
- return NULL;
- result = smartlist_new();
- while ((de = readdir(d))) {
- if (!strcmp(de->d_name, ".") ||
- !strcmp(de->d_name, ".."))
- continue;
- smartlist_add_strdup(result, de->d_name);
- }
- closedir(d);
- #endif /* defined(_WIN32) */
- return result;
- }
|