mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-12-06 19:41:15 +01:00
368 lines
10 KiB
C
368 lines
10 KiB
C
/* Copyright (c) 2003, Roger Dingledine
|
|
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
|
|
* Copyright (c) 2007-2019, 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;
|
|
}
|