diff options
author | Nick Mathewson <nickm@torproject.org> | 2018-06-27 09:40:21 -0400 |
---|---|---|
committer | Nick Mathewson <nickm@torproject.org> | 2018-06-27 12:30:11 -0400 |
commit | 1e2e0f7e461b4518b123c4050c3dbc786ac422c4 (patch) | |
tree | 65fd14c74582fe2edccba0cc865c347d8b79712b /src/lib/fs/dir.c | |
parent | 3246c114a20da44064f7fae8eb6b6712e708a700 (diff) | |
download | tor-1e2e0f7e461b4518b123c4050c3dbc786ac422c4.tar.gz tor-1e2e0f7e461b4518b123c4050c3dbc786ac422c4.zip |
Extract functions from compat.c and util.h into a new fs library
Diffstat (limited to 'src/lib/fs/dir.c')
-rw-r--r-- | src/lib/fs/dir.c | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/src/lib/fs/dir.c b/src/lib/fs/dir.c new file mode 100644 index 0000000000..9f09327d84 --- /dev/null +++ b/src/lib/fs/dir.c @@ -0,0 +1,360 @@ +/* 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 */ + +#include "lib/fs/dir.h" +#include "lib/fs/path.h" +#include "lib/fs/userdb.h" + +#include "lib/log/torlog.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/util_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; +} |