diff options
author | anonymous <anon@fakeanonemail.org> | 2014-08-28 18:10:21 +0000 |
---|---|---|
committer | meejah <meejah@meejah.ca> | 2014-08-30 15:23:05 -0600 |
commit | c13db1f6143cf99830dc73dd527898e711e6b704 (patch) | |
tree | c09814e3dc2a5da84969f9d04da58287d4f3737f /src | |
parent | f113a263deda8e1619f46cd913fc24ac28da832d (diff) | |
download | tor-c13db1f6143cf99830dc73dd527898e711e6b704.tar.gz tor-c13db1f6143cf99830dc73dd527898e711e6b704.zip |
Ticket #11291: patch from "anon":
test-11291-group-redable-hsdirs-wtests-may8.patch
Diffstat (limited to 'src')
-rw-r--r-- | src/common/util.c | 15 | ||||
-rw-r--r-- | src/common/util.h | 27 | ||||
-rw-r--r-- | src/or/config.c | 1 | ||||
-rw-r--r-- | src/or/or.h | 4 | ||||
-rw-r--r-- | src/or/rendservice.c | 36 | ||||
-rw-r--r-- | src/test/Makefile.nmake | 4 | ||||
-rw-r--r-- | src/test/include.am | 1 | ||||
-rw-r--r-- | src/test/test.c | 2 | ||||
-rw-r--r-- | src/test/test_checkdir.c | 130 |
9 files changed, 209 insertions, 11 deletions
diff --git a/src/common/util.c b/src/common/util.c index 16ff8e3a80..6971e4d627 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -1872,6 +1872,9 @@ file_status(const char *fname) * <b>check</b>&CPD_CHECK, and we think we can create it, return 0. Else * 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 @@ -1910,7 +1913,12 @@ check_private_dir(const char *dirname, cpd_check_t check, #if defined (_WIN32) r = mkdir(dirname); #else - r = mkdir(dirname, 0700); + if (check & CPD_GROUP_READ) { + r = mkdir(dirname, STAT_RWXU|STAT_RGRP|STAT_XGRP); + } + else { + r = mkdir(dirname, STAT_RWXU); + } #endif if (r) { log_warn(LD_FS, "Error creating directory %s: %s", dirname, @@ -1963,7 +1971,8 @@ check_private_dir(const char *dirname, cpd_check_t check, tor_free(process_ownername); return -1; } - if ((check & CPD_GROUP_OK) && st.st_gid != running_gid) { + if ( (check & (CPD_GROUP_OK|CPD_GROUP_READ)) + && (st.st_gid != running_gid) ) { struct group *gr; char *process_groupname = NULL; gr = getgrgid(running_gid); @@ -1978,7 +1987,7 @@ check_private_dir(const char *dirname, cpd_check_t check, tor_free(process_groupname); return -1; } - if (check & CPD_GROUP_OK) { + if (check & (CPD_GROUP_OK|CPD_GROUP_READ)) { mask = 0027; } else { mask = 0077; diff --git a/src/common/util.h b/src/common/util.h index 61bb05f016..c98a32c19c 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -344,9 +344,34 @@ typedef unsigned int cpd_check_t; #define CPD_CREATE 1 #define CPD_CHECK 2 #define CPD_GROUP_OK 4 -#define CPD_CHECK_MODE_ONLY 8 +#define CPD_GROUP_READ 8 +#define CPD_CHECK_MODE_ONLY 16 int check_private_dir(const char *dirname, cpd_check_t check, const char *effective_user); + +#if !defined(_WIN32) && !defined (WINCE) +/** Unix like (non win32) group and world permission constants. + * These constants are directly compatible with *nix ABI, e.g. S_IRWXU,* + * NOTE: these are distinct from the private directory flags CPD_* + * which are a portable way for storing private data cross platform. + */ +#define STAT_RWXU 00700 +#define STAT_RUSR 00400 +#define STAT_WUSR 00200 +#define STAT_XUSR 00100 + +#define STAT_RWXG 00070 +#define STAT_RGRP 00040 +#define STAT_WGRP 00020 +#define STAT_XGRP 00010 + +#define STAT_RWXO 00007 +#define STAT_ROTH 00004 +#define STAT_WOTH 00002 +#define STAT_XOTH 00001 +#endif + +/** File open and create options */ #define OPEN_FLAGS_REPLACE (O_WRONLY|O_CREAT|O_TRUNC) #define OPEN_FLAGS_APPEND (O_WRONLY|O_CREAT|O_APPEND) #define OPEN_FLAGS_DONT_REPLACE (O_CREAT|O_EXCL|O_APPEND|O_WRONLY) diff --git a/src/or/config.c b/src/or/config.c index 7800ec1908..1753722f95 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -271,6 +271,7 @@ static config_var_t option_vars_[] = { V(AccelDir, FILENAME, NULL), V(HashedControlPassword, LINELIST, NULL), V(HidServDirectoryV2, BOOL, "1"), + V(HiddenServiceGroupReadable, BOOL, "0"), VAR("HiddenServiceDir", LINELIST_S, RendConfigLines, NULL), OBSOLETE("HiddenServiceExcludeNodes"), OBSOLETE("HiddenServiceNodes"), diff --git a/src/or/or.h b/src/or/or.h index 3683607741..9207dbaa91 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4223,6 +4223,10 @@ typedef struct { /** Should we send the timestamps that pre-023 hidden services want? */ int Support022HiddenServices; + + /** Create the Hidden Service directories and hostname files group readable. */ + int HiddenServiceGroupReadable; + } or_options_t; /** Persistent state for an onion router, as saved to disk. */ diff --git a/src/or/rendservice.c b/src/or/rendservice.c index 749d6fa880..83e6a3b82c 100644 --- a/src/or/rendservice.c +++ b/src/or/rendservice.c @@ -368,10 +368,12 @@ rend_config_services(const or_options_t *options, int validate_only) for (line = options->RendConfigLines; line; line = line->next) { if (!strcasecmp(line->key, "HiddenServiceDir")) { if (service) { /* register the one we just finished parsing */ - if (validate_only) + if (validate_only) { rend_service_free(service); - else + } + else { rend_add_service(service); + } } service = tor_malloc_zero(sizeof(rend_service_t)); service->directory = tor_strdup(line->value); @@ -513,10 +515,12 @@ rend_config_services(const or_options_t *options, int validate_only) } } if (service) { - if (validate_only) + if (validate_only) { rend_service_free(service); - else + } + else { rend_add_service(service); + } } /* If this is a reload and there were hidden services configured before, @@ -693,10 +697,23 @@ rend_service_load_keys(rend_service_t *s) { char fname[512]; char buf[128]; + cpd_check_t check_opts = CPD_CREATE; + if (get_options()->HiddenServiceGroupReadable) { + check_opts |= CPD_GROUP_READ; + } /* Check/create directory */ - if (check_private_dir(s->directory, CPD_CREATE, get_options()->User) < 0) + if (check_private_dir(s->directory, check_opts, get_options()->User) < 0) { return -1; + } +#ifndef _WIN32 + if (get_options()->HiddenServiceGroupReadable) { + /** Only new dirs created get new opts, also enforce group read. */ + if (chmod(s->directory, STAT_RWXU|STAT_RGRP|STAT_XGRP)) { + log_warn(LD_FS,"Unable to make %s group-readable.", s->directory); + } + } +#endif /* Load key */ if (strlcpy(fname,s->directory,sizeof(fname)) >= sizeof(fname) || @@ -733,6 +750,15 @@ rend_service_load_keys(rend_service_t *s) memwipe(buf, 0, sizeof(buf)); return -1; } +#ifndef _WIN32 + if (get_options()->HiddenServiceGroupReadable) { + /** Also verify hostname file created with group read. */ + if (chmod(fname, STAT_RUSR|STAT_WUSR|STAT_RGRP)) { + log_warn(LD_FS,"Unable to make hidden hostname file %s group-readable.", fname); + } + } +#endif + memwipe(buf, 0, sizeof(buf)); /* If client authorization is configured, load or generate keys. */ diff --git a/src/test/Makefile.nmake b/src/test/Makefile.nmake index 822431f3b8..f6ee7f3f53 100644 --- a/src/test/Makefile.nmake +++ b/src/test/Makefile.nmake @@ -12,8 +12,8 @@ LIBS = ..\..\..\build-alpha\lib\libevent.lib \ crypt32.lib gdi32.lib user32.lib TEST_OBJECTS = test.obj test_addr.obj test_containers.obj \ - test_controller_events.ogj test_crypto.obj test_data.obj test_dir.obj \ - test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ + test_controller_events.obj test_crypto.obj test_data.obj test_dir.obj \ + test_checkdir.obj test_microdesc.obj test_pt.obj test_util.obj test_config.obj \ test_cell_formats.obj test_replay.obj test_introduce.obj tinytest.obj \ test_hs.obj diff --git a/src/test/include.am b/src/test/include.am index 77c92f12f8..36d86c74ea 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -28,6 +28,7 @@ src_test_test_SOURCES = \ src/test/test_cell_queue.c \ src/test/test_data.c \ src/test/test_dir.c \ + src/test/test_checkdir.c \ src/test/test_entrynodes.c \ src/test/test_extorport.c \ src/test/test_introduce.c \ diff --git a/src/test/test.c b/src/test/test.c index e836160bf4..6d7c6fa64b 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1302,6 +1302,7 @@ extern struct testcase_t crypto_tests[]; extern struct testcase_t container_tests[]; extern struct testcase_t util_tests[]; extern struct testcase_t dir_tests[]; +extern struct testcase_t checkdir_tests[]; extern struct testcase_t microdesc_tests[]; extern struct testcase_t pt_tests[]; extern struct testcase_t config_tests[]; @@ -1338,6 +1339,7 @@ static struct testgroup_t testgroups[] = { { "cellfmt/", cell_format_tests }, { "cellqueue/", cell_queue_tests }, { "dir/", dir_tests }, + { "checkdir/", checkdir_tests }, { "dir/md/", microdesc_tests }, { "pt/", pt_tests }, { "config/", config_tests }, diff --git a/src/test/test_checkdir.c b/src/test/test_checkdir.c new file mode 100644 index 0000000000..877c789bcc --- /dev/null +++ b/src/test/test_checkdir.c @@ -0,0 +1,130 @@ +/* Copyright (c) 2014, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" +#include "or.h" +#include <dirent.h> +#include "config.h" +#include "test.h" +#include "util.h" + +/** Run unit tests for private dir permission enforcement logic. */ +static void +test_checkdir_perms(void *testdata) +{ + or_options_t *options = get_options_mutable(); + const char *subdir = "test_checkdir"; + char *testdir; + cpd_check_t cpd_chkopts; + cpd_check_t unix_create_opts; + cpd_check_t unix_verify_optsmask; + struct stat st; + + /** setup data directory before tests. */ + tor_free(options->DataDirectory); + options->DataDirectory = tor_strdup(get_fname(subdir)); + tt_int_op(mkdir(options->DataDirectory, STAT_RWXU), ==, 0); + + /** test: create new dir, no flags. */ + testdir = get_datadir_fname("checkdir_new_none"); + cpd_chkopts = CPD_CREATE; + unix_verify_optsmask = STAT_RWXO|STAT_RWXG; /* 0077 */ + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + /** test: create new dir, CPD_GROUP_OK option set. */ + testdir = get_datadir_fname("checkdir_new_groupok"); + cpd_chkopts = CPD_CREATE|CPD_GROUP_OK; + unix_verify_optsmask = STAT_RWXO|STAT_RWXG; /* 0077 */ + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + + /** test: create new dir, CPD_GROUP_READ option set. */ + testdir = get_datadir_fname("checkdir_new_groupread"); + cpd_chkopts = CPD_CREATE|CPD_GROUP_READ; + unix_verify_optsmask = STAT_RWXO|STAT_WGRP; /* 0027 */ + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + + /** test: check existing dir created with defaults, + and verify with CPD_CREATE only. */ + testdir = get_datadir_fname("checkdir_exists_none"); + cpd_chkopts = CPD_CREATE; + unix_create_opts = STAT_RWXU; /* 0700 */ + unix_verify_optsmask = STAT_RWXO|STAT_RWXG; /* 0077 */ + tt_int_op(0, ==, mkdir(testdir, unix_create_opts)); + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + + /** test: check existing dir created with defaults, + and verify with CPD_GROUP_OK option set. */ + testdir = get_datadir_fname("checkdir_exists_groupok"); + cpd_chkopts = CPD_CREATE; + unix_verify_optsmask = STAT_RWXO|STAT_RWXG; /* 0077 */ + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + cpd_chkopts = CPD_GROUP_OK; + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + + /** test: check existing dir created with defaults, + and verify with CPD_GROUP_READ option set. */ + testdir = get_datadir_fname("checkdir_exists_groupread"); + cpd_chkopts = CPD_CREATE; + unix_verify_optsmask = STAT_RWXO|STAT_WGRP; /* 0027 */ + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + cpd_chkopts = CPD_GROUP_READ; + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + + /** test: check existing dir created with CPD_GROUP_READ, + and verify with CPD_GROUP_OK option set. */ + testdir = get_datadir_fname("checkdir_existsread_groupok"); + cpd_chkopts = CPD_CREATE|CPD_GROUP_READ; + unix_verify_optsmask = STAT_RWXO|STAT_WGRP; /* 0027 */ + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + cpd_chkopts = CPD_GROUP_OK; + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + + /** test: check existing dir created with CPD_GROUP_READ, + and verify with CPD_GROUP_READ option set. */ + testdir = get_datadir_fname("checkdir_existsread_groupread"); + cpd_chkopts = CPD_CREATE|CPD_GROUP_READ; + unix_verify_optsmask = STAT_RWXO|STAT_WGRP; /* 0027 */ + tt_int_op(0, ==, check_private_dir(testdir, cpd_chkopts, NULL)); + tt_int_op(0, ==, stat(testdir, &st)); + tt_int_op(0, ==, (st.st_mode & unix_verify_optsmask)); + tor_free(testdir); + + done: + ; +} + +#define CHECKDIR(name,flags) \ + { #name, test_checkdir_##name, (flags), NULL, NULL } + +struct testcase_t checkdir_tests[] = { + CHECKDIR(perms, 0), + END_OF_TESTCASES +}; + |