mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-28 06:13:31 +01:00
Merge branch 'feature8195_small_squashed'
This commit is contained in:
commit
aba39ea390
2
.gitignore
vendored
2
.gitignore
vendored
@ -172,6 +172,7 @@ cscope.*
|
||||
/src/test/test-child
|
||||
/src/test/test-memwipe
|
||||
/src/test/test-ntor-cl
|
||||
/src/test/test-switch-id
|
||||
/src/test/test_workqueue
|
||||
/src/test/test.exe
|
||||
/src/test/test-slow.exe
|
||||
@ -179,6 +180,7 @@ cscope.*
|
||||
/src/test/test-child.exe
|
||||
/src/test/test-ntor-cl.exe
|
||||
/src/test/test-memwipe.exe
|
||||
/src/test/test-switch-id.exe
|
||||
/src/test/test_workqueue.exe
|
||||
/src/test/test_zero_length_keys.sh
|
||||
/src/test/test_ntor.sh
|
||||
|
6
changes/feature8195
Normal file
6
changes/feature8195
Normal file
@ -0,0 +1,6 @@
|
||||
o Major features:
|
||||
- When Tor is started as root on Linux and told to switch user ID, it
|
||||
can now retain the capabilitity to bind to low ports. By default,
|
||||
Tor will do this only when it's switching user ID and some low
|
||||
ports have been configured. You can change this behavior with
|
||||
the new option KeepBindCapabilities. Closes ticket 8195.
|
16
configure.ac
16
configure.ac
@ -712,6 +712,19 @@ else
|
||||
fi
|
||||
AC_SUBST(TOR_ZLIB_LIBS)
|
||||
|
||||
dnl ----------------------------------------------------------------------
|
||||
dnl Check if libcap is available for capabilities.
|
||||
|
||||
tor_cap_pkg_debian="libcap2"
|
||||
tor_cap_pkg_redhat="libcap"
|
||||
tor_cap_devpkg_debian="libcap-dev"
|
||||
tor_cap_devpkg_redhat="libcap-devel"
|
||||
|
||||
AC_CHECK_LIB([cap], [cap_init], [],
|
||||
AC_MSG_NOTICE([Libcap was not found. Capabilities will not be usable.])
|
||||
)
|
||||
AC_CHECK_FUNCS(cap_set_proc)
|
||||
|
||||
dnl ---------------------------------------------------------------------
|
||||
dnl Now that we know about our major libraries, we can check for compiler
|
||||
dnl and linker hardening options. We need to do this with the libraries known,
|
||||
@ -719,7 +732,7 @@ dnl since sometimes the linker will like an option but not be willing to
|
||||
dnl use it with a build of a library.
|
||||
|
||||
all_ldflags_for_check="$TOR_LDFLAGS_zlib $TOR_LDFLAGS_openssl $TOR_LDFLAGS_libevent"
|
||||
all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI"
|
||||
all_libs_for_check="$TOR_ZLIB_LIBS $TOR_LIB_MATH $TOR_LIBEVENT_LIBS $TOR_OPENSSL_LIBS $TOR_SYSTEMD_LIBS $TOR_LIB_WS32 $TOR_LIB_GDI $TOR_CAP_LIBS"
|
||||
|
||||
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([], [
|
||||
#if !defined(__clang__)
|
||||
@ -912,6 +925,7 @@ AC_CHECK_HEADERS(
|
||||
fcntl.h \
|
||||
signal.h \
|
||||
string.h \
|
||||
sys/capability.h \
|
||||
sys/fcntl.h \
|
||||
sys/stat.h \
|
||||
sys/time.h \
|
||||
|
@ -619,6 +619,14 @@ GENERAL OPTIONS
|
||||
[[User]] **User** __UID__::
|
||||
On startup, setuid to this user and setgid to their primary group.
|
||||
|
||||
[[KeepBindCapabilities]] **KeepBindCapabilities** **0**|**1**|**auto**::
|
||||
On Linux, when we are started as root and we switch our identity using
|
||||
the **User** option, the **KeepBindCapabilities** option tells us whether to
|
||||
try to retain our ability to bind to low ports. If this value is 1, we
|
||||
try to keep the capability; if it is 0 we do not; and if it is **auto**,
|
||||
we keep the capability only if we are configured to listen on a low port.
|
||||
(Default: auto.)
|
||||
|
||||
[[HardwareAccel]] **HardwareAccel** **0**|**1**::
|
||||
If non-zero, try to use built-in (static) crypto hardware acceleration when
|
||||
available. (Default: 0)
|
||||
|
@ -71,6 +71,9 @@
|
||||
#ifdef HAVE_SYS_STATVFS_H
|
||||
#include <sys/statvfs.h>
|
||||
#endif
|
||||
#ifdef HAVE_SYS_CAPABILITY_H
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <conio.h>
|
||||
@ -1966,17 +1969,99 @@ tor_getpwuid(uid_t uid)
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Return true iff we were compiled with capability support, and capabilities
|
||||
* seem to work. **/
|
||||
int
|
||||
have_capability_support(void)
|
||||
{
|
||||
#ifdef HAVE_LINUX_CAPABILITIES
|
||||
cap_t caps = cap_get_proc();
|
||||
if (caps == NULL)
|
||||
return 0;
|
||||
cap_free(caps);
|
||||
return 1;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_LINUX_CAPABILITIES
|
||||
/** Helper. Drop all capabilities but a small set, and set PR_KEEPCAPS as
|
||||
* appropriate.
|
||||
*
|
||||
* If pre_setuid, retain only CAP_NET_BIND_SERVICE, CAP_SETUID, and
|
||||
* CAP_SETGID, and use PR_KEEPCAPS to ensure that capabilities persist across
|
||||
* setuid().
|
||||
*
|
||||
* If not pre_setuid, retain only CAP_NET_BIND_SERVICE, and disable
|
||||
* PR_KEEPCAPS.
|
||||
*
|
||||
* Return 0 on success, and -1 on failure.
|
||||
*/
|
||||
static int
|
||||
drop_capabilities(int pre_setuid)
|
||||
{
|
||||
/* We keep these three capabilities, and these only, as we setuid.
|
||||
* After we setuid, we drop all but the first. */
|
||||
const cap_value_t caplist[] = {
|
||||
CAP_NET_BIND_SERVICE, CAP_SETUID, CAP_SETGID
|
||||
};
|
||||
const char *where = pre_setuid ? "pre-setuid" : "post-setuid";
|
||||
const int n_effective = pre_setuid ? 3 : 1;
|
||||
const int n_permitted = pre_setuid ? 3 : 1;
|
||||
const int n_inheritable = 1;
|
||||
const int keepcaps = pre_setuid ? 1 : 0;
|
||||
|
||||
/* Sets whether we keep capabilities across a setuid. */
|
||||
if (prctl(PR_SET_KEEPCAPS, keepcaps) < 0) {
|
||||
log_warn(LD_CONFIG, "Unable to call prctl() %s: %s",
|
||||
where, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
cap_t caps = cap_get_proc();
|
||||
if (!caps) {
|
||||
log_warn(LD_CONFIG, "Unable to call cap_get_proc() %s: %s",
|
||||
where, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
cap_clear(caps);
|
||||
|
||||
cap_set_flag(caps, CAP_EFFECTIVE, n_effective, caplist, CAP_SET);
|
||||
cap_set_flag(caps, CAP_PERMITTED, n_permitted, caplist, CAP_SET);
|
||||
cap_set_flag(caps, CAP_INHERITABLE, n_inheritable, caplist, CAP_SET);
|
||||
|
||||
int r = cap_set_proc(caps);
|
||||
cap_free(caps);
|
||||
if (r < 0) {
|
||||
log_warn(LD_CONFIG, "No permission to set capabilities %s: %s",
|
||||
where, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Call setuid and setgid to run as <b>user</b> and switch to their
|
||||
* primary group. Return 0 on success. On failure, log and return -1.
|
||||
*
|
||||
* If SWITCH_ID_KEEP_BINDLOW is set in 'flags', try to use the capability
|
||||
* system to retain the abilitity to bind low ports.
|
||||
*
|
||||
* If SWITCH_ID_WARN_IF_NO_CAPS is set in flags, also warn if we have
|
||||
* don't have capability support.
|
||||
*/
|
||||
int
|
||||
switch_id(const char *user)
|
||||
switch_id(const char *user, const unsigned flags)
|
||||
{
|
||||
#ifndef _WIN32
|
||||
const struct passwd *pw = NULL;
|
||||
uid_t old_uid;
|
||||
gid_t old_gid;
|
||||
static int have_already_switched_id = 0;
|
||||
const int keep_bindlow = !!(flags & SWITCH_ID_KEEP_BINDLOW);
|
||||
const int warn_if_no_caps = !!(flags & SWITCH_ID_WARN_IF_NO_CAPS);
|
||||
|
||||
tor_assert(user);
|
||||
|
||||
@ -2000,6 +2085,20 @@ switch_id(const char *user)
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LINUX_CAPABILITIES
|
||||
(void) warn_if_no_caps;
|
||||
if (keep_bindlow) {
|
||||
if (drop_capabilities(1))
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
(void) keep_bindlow;
|
||||
if (warn_if_no_caps) {
|
||||
log_warn(LD_CONFIG, "KeepBindCapabilities set, but no capability support "
|
||||
"on this system.");
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Properly switch egid,gid,euid,uid here or bail out */
|
||||
if (setgroups(1, &pw->pw_gid)) {
|
||||
log_warn(LD_GENERAL, "Error setting groups to gid %d: \"%s\".",
|
||||
@ -2053,6 +2152,12 @@ switch_id(const char *user)
|
||||
|
||||
/* We've properly switched egid, gid, euid, uid, and supplementary groups if
|
||||
* we're here. */
|
||||
#ifdef HAVE_LINUX_CAPABILITIES
|
||||
if (keep_bindlow) {
|
||||
if (drop_capabilities(0))
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !defined(CYGWIN) && !defined(__CYGWIN__)
|
||||
/* If we tried to drop privilege to a group/user other than root, attempt to
|
||||
@ -2100,6 +2205,7 @@ switch_id(const char *user)
|
||||
|
||||
#else
|
||||
(void)user;
|
||||
(void)flags;
|
||||
|
||||
log_warn(LD_CONFIG,
|
||||
"User specified but switching users is unsupported on your OS.");
|
||||
|
@ -625,7 +625,18 @@ typedef unsigned long rlim_t;
|
||||
int get_max_sockets(void);
|
||||
int set_max_file_descriptors(rlim_t limit, int *max);
|
||||
int tor_disable_debugger_attach(void);
|
||||
int switch_id(const char *user);
|
||||
|
||||
#if defined(HAVE_SYS_CAPABILITY_H) && defined(HAVE_CAP_SET_PROC)
|
||||
#define HAVE_LINUX_CAPABILITIES
|
||||
#endif
|
||||
|
||||
int have_capability_support(void);
|
||||
|
||||
/** Flag for switch_id; see switch_id() for documentation */
|
||||
#define SWITCH_ID_KEEP_BINDLOW (1<<0)
|
||||
/** Flag for switch_id; see switch_id() for documentation */
|
||||
#define SWITCH_ID_WARN_IF_NO_CAPS (1<<1)
|
||||
int switch_id(const char *user, unsigned flags);
|
||||
#ifdef HAVE_PWD_H
|
||||
char *get_user_homedir(const char *username);
|
||||
#endif
|
||||
|
@ -310,6 +310,7 @@ static config_var_t option_vars_[] = {
|
||||
V(Socks5ProxyUsername, STRING, NULL),
|
||||
V(Socks5ProxyPassword, STRING, NULL),
|
||||
V(KeepalivePeriod, INTERVAL, "5 minutes"),
|
||||
V(KeepBindCapabilities, AUTOBOOL, "auto"),
|
||||
VAR("Log", LINELIST, Logs, NULL),
|
||||
V(LogMessageDomains, BOOL, "0"),
|
||||
V(LogTimeGranularity, MSEC_INTERVAL, "1 second"),
|
||||
@ -606,7 +607,8 @@ static int parse_ports(or_options_t *options, int validate_only,
|
||||
char **msg_out, int *n_ports_out,
|
||||
int *world_writable_control_socket);
|
||||
static int check_server_ports(const smartlist_t *ports,
|
||||
const or_options_t *options);
|
||||
const or_options_t *options,
|
||||
int *num_low_ports_out);
|
||||
|
||||
static int validate_data_directory(or_options_t *options);
|
||||
static int write_configuration_file(const char *fname,
|
||||
@ -1085,6 +1087,9 @@ consider_adding_dir_servers(const or_options_t *options,
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Helps determine flags to pass to switch_id. */
|
||||
static int have_low_ports = -1;
|
||||
|
||||
/** Fetch the active option list, and take actions based on it. All of the
|
||||
* things we do should survive being done repeatedly. If present,
|
||||
* <b>old_options</b> contains the previous value of the options.
|
||||
@ -1219,7 +1224,16 @@ options_act_reversible(const or_options_t *old_options, char **msg)
|
||||
|
||||
/* Setuid/setgid as appropriate */
|
||||
if (options->User) {
|
||||
if (switch_id(options->User) != 0) {
|
||||
tor_assert(have_low_ports != -1);
|
||||
unsigned switch_id_flags = 0;
|
||||
if (options->KeepBindCapabilities == 1) {
|
||||
switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
|
||||
switch_id_flags |= SWITCH_ID_WARN_IF_NO_CAPS;
|
||||
}
|
||||
if (options->KeepBindCapabilities == -1 && have_low_ports) {
|
||||
switch_id_flags |= SWITCH_ID_KEEP_BINDLOW;
|
||||
}
|
||||
if (switch_id(options->User, switch_id_flags) != 0) {
|
||||
/* No need to roll back, since you can't change the value. */
|
||||
*msg = tor_strdup("Problem with User value. See logs for details.");
|
||||
goto done;
|
||||
@ -4094,6 +4108,12 @@ options_transition_allowed(const or_options_t *old,
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (old->KeepBindCapabilities != new_val->KeepBindCapabilities) {
|
||||
*msg = tor_strdup("While Tor is running, changing KeepBindCapabilities is "
|
||||
"not allowed.");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!opt_streq(old->SyslogIdentityTag, new_val->SyslogIdentityTag)) {
|
||||
*msg = tor_strdup("While Tor is running, changing "
|
||||
"SyslogIdentityTag is not allowed.");
|
||||
@ -6632,10 +6652,13 @@ parse_ports(or_options_t *options, int validate_only,
|
||||
}
|
||||
}
|
||||
|
||||
if (check_server_ports(ports, options) < 0) {
|
||||
int n_low_ports = 0;
|
||||
if (check_server_ports(ports, options, &n_low_ports) < 0) {
|
||||
*msg = tor_strdup("Misconfigured server ports");
|
||||
goto err;
|
||||
}
|
||||
if (have_low_ports < 0)
|
||||
have_low_ports = (n_low_ports > 0);
|
||||
|
||||
*n_ports_out = smartlist_len(ports);
|
||||
|
||||
@ -6689,10 +6712,12 @@ parse_ports(or_options_t *options, int validate_only,
|
||||
}
|
||||
|
||||
/** Given a list of <b>port_cfg_t</b> in <b>ports</b>, check them for internal
|
||||
* consistency and warn as appropriate. */
|
||||
* consistency and warn as appropriate. Set *<b>n_low_ports_out</b> to the
|
||||
* number of sub-1024 ports we will be binding. */
|
||||
static int
|
||||
check_server_ports(const smartlist_t *ports,
|
||||
const or_options_t *options)
|
||||
const or_options_t *options,
|
||||
int *n_low_ports_out)
|
||||
{
|
||||
int n_orport_advertised = 0;
|
||||
int n_orport_advertised_ipv4 = 0;
|
||||
@ -6755,16 +6780,24 @@ check_server_ports(const smartlist_t *ports,
|
||||
r = -1;
|
||||
}
|
||||
|
||||
if (n_low_port && options->AccountingMax) {
|
||||
if (n_low_port && options->AccountingMax &&
|
||||
(!have_capability_support() || options->KeepBindCapabilities == 0)) {
|
||||
const char *extra = "";
|
||||
if (options->KeepBindCapabilities == 0 && have_capability_support())
|
||||
extra = ", and you have disabled KeepBindCapabilities.";
|
||||
log_warn(LD_CONFIG,
|
||||
"You have set AccountingMax to use hibernation. You have also "
|
||||
"chosen a low DirPort or OrPort. This combination can make Tor stop "
|
||||
"chosen a low DirPort or OrPort%s."
|
||||
"This combination can make Tor stop "
|
||||
"working when it tries to re-attach the port after a period of "
|
||||
"hibernation. Please choose a different port or turn off "
|
||||
"hibernation unless you know this combination will work on your "
|
||||
"platform.");
|
||||
"platform.", extra);
|
||||
}
|
||||
|
||||
if (n_low_ports_out)
|
||||
*n_low_ports_out = n_low_port;
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
|
@ -4432,6 +4432,9 @@ typedef struct {
|
||||
int keygen_passphrase_fd;
|
||||
int change_key_passphrase;
|
||||
char *master_key_fname;
|
||||
|
||||
/** Autobool: Do we try to retain capabilities if we can? */
|
||||
int KeepBindCapabilities;
|
||||
} or_options_t;
|
||||
|
||||
/** Persistent state for an onion router, as saved to disk. */
|
||||
|
@ -8,14 +8,16 @@ TESTS_ENVIRONMENT = \
|
||||
export builddir="$(builddir)"; \
|
||||
export TESTING_TOR_BINARY="$(TESTING_TOR_BINARY)";
|
||||
|
||||
TESTSCRIPTS = src/test/test_zero_length_keys.sh
|
||||
TESTSCRIPTS = src/test/test_zero_length_keys.sh \
|
||||
src/test/test_switch_id.sh
|
||||
|
||||
if USEPYTHON
|
||||
TESTSCRIPTS += src/test/test_ntor.sh src/test/test_bt.sh
|
||||
endif
|
||||
|
||||
TESTS += src/test/test src/test/test-slow src/test/test-memwipe \
|
||||
src/test/test_workqueue src/test/test_keygen.sh $(TESTSCRIPTS)
|
||||
src/test/test_workqueue src/test/test_keygen.sh \
|
||||
$(TESTSCRIPTS)
|
||||
|
||||
# These flavors are run using automake's test-driver and test-network.sh
|
||||
TEST_CHUTNEY_FLAVORS = basic-min bridges-min hs-min bridges+hs
|
||||
@ -37,7 +39,8 @@ noinst_PROGRAMS+= \
|
||||
src/test/test-slow \
|
||||
src/test/test-memwipe \
|
||||
src/test/test-child \
|
||||
src/test/test_workqueue
|
||||
src/test/test_workqueue \
|
||||
src/test/test-switch-id
|
||||
endif
|
||||
|
||||
src_test_AM_CPPFLAGS = -DSHARE_DATADIR="\"$(datadir)\"" \
|
||||
@ -136,6 +139,14 @@ src_test_test_workqueue_SOURCES = \
|
||||
src_test_test_workqueue_CPPFLAGS= $(src_test_AM_CPPFLAGS)
|
||||
src_test_test_workqueue_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
|
||||
|
||||
src_test_test_switch_id_SOURCES = \
|
||||
src/test/test_switch_id.c
|
||||
src_test_test_switch_id_CPPFLAGS= $(src_test_AM_CPPFLAGS)
|
||||
src_test_test_switch_id_CFLAGS = $(AM_CFLAGS) $(TEST_CFLAGS)
|
||||
src_test_test_switch_id_LDADD = \
|
||||
src/common/libor-testing.a \
|
||||
@TOR_ZLIB_LIBS@ @TOR_LIB_MATH@
|
||||
|
||||
src_test_test_LDFLAGS = @TOR_LDFLAGS_zlib@ @TOR_LDFLAGS_openssl@ \
|
||||
@TOR_LDFLAGS_libevent@
|
||||
src_test_test_LDADD = src/or/libtor-testing.a src/common/libor-testing.a \
|
||||
|
181
src/test/test_switch_id.c
Normal file
181
src/test/test_switch_id.c
Normal file
@ -0,0 +1,181 @@
|
||||
/* Copyright (c) 2015, The Tor Project, Inc. */
|
||||
/* See LICENSE for licensing information */
|
||||
|
||||
#include "or.h"
|
||||
|
||||
#ifdef HAVE_SYS_CAPABILITY_H
|
||||
#include <sys/capability.h>
|
||||
#endif
|
||||
|
||||
#define TEST_BUILT_WITH_CAPS 0
|
||||
#define TEST_HAVE_CAPS 1
|
||||
#define TEST_ROOT_CAN_BIND_LOW 2
|
||||
#define TEST_SETUID 3
|
||||
#define TEST_SETUID_KEEPCAPS 4
|
||||
#define TEST_SETUID_STRICT 5
|
||||
|
||||
static const char *username;
|
||||
|
||||
static const struct {
|
||||
const char *name;
|
||||
int test_id;
|
||||
} which_test[] = {
|
||||
{ "built-with-caps", TEST_BUILT_WITH_CAPS },
|
||||
{ "have-caps", TEST_HAVE_CAPS },
|
||||
{ "root-bind-low", TEST_ROOT_CAN_BIND_LOW },
|
||||
{ "setuid", TEST_SETUID },
|
||||
{ "setuid-keepcaps", TEST_SETUID_KEEPCAPS },
|
||||
{ "setuid-strict", TEST_SETUID_STRICT },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
/* 0 on no, 1 on yes, -1 on failure. */
|
||||
static int
|
||||
check_can_bind_low_ports(void)
|
||||
{
|
||||
int port;
|
||||
struct sockaddr_in sin;
|
||||
memset(&sin, 0, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
|
||||
for (port = 600; port < 1024; ++port) {
|
||||
sin.sin_port = htons(port);
|
||||
tor_socket_t fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
||||
if (! SOCKET_OK(fd)) {
|
||||
perror("socket");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int one = 1;
|
||||
if (setsockopt(fd, SOL_SOCKET,SO_REUSEADDR, &one, sizeof(one))) {
|
||||
perror("setsockopt");
|
||||
tor_close_socket_simple(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int res = bind(fd, (struct sockaddr *)&sin, sizeof(sin));
|
||||
tor_close_socket_simple(fd);
|
||||
|
||||
if (res == 0) {
|
||||
/* bind was successful */
|
||||
return 1;
|
||||
} else if (errno == EACCES || errno == EPERM) {
|
||||
/* Got a permission-denied error. */
|
||||
return 0;
|
||||
} else if (errno == EADDRINUSE) {
|
||||
/* Huh; somebody is using that port. */
|
||||
} else {
|
||||
perror("bind");
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
const char *testname;
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "I want 2 arguments: a username and a command.\n");
|
||||
return 1;
|
||||
}
|
||||
if (getuid() != 0) {
|
||||
fprintf(stderr, "This test only works when it's run as root.\n");
|
||||
return 1;
|
||||
}
|
||||
username = argv[1];
|
||||
testname = argv[2];
|
||||
int test_id = -1;
|
||||
int i;
|
||||
for (i = 0; which_test[i].name; ++i) {
|
||||
if (!strcmp(which_test[i].name, testname)) {
|
||||
test_id = which_test[i].test_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (test_id == -1) {
|
||||
fprintf(stderr, "Unrecognized test '%s'\n", testname);
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifdef HAVE_LINUX_CAPABILITIES
|
||||
const int have_cap_support = 1;
|
||||
#else
|
||||
const int have_cap_support = 0;
|
||||
#endif
|
||||
|
||||
int okay;
|
||||
|
||||
init_logging(1);
|
||||
log_severity_list_t sev;
|
||||
memset(&sev, 0, sizeof(sev));
|
||||
set_log_severity_config(LOG_WARN, LOG_ERR, &sev);
|
||||
add_stream_log(&sev, "", fileno(stderr));
|
||||
|
||||
switch (test_id)
|
||||
{
|
||||
case TEST_BUILT_WITH_CAPS:
|
||||
/* Succeed if we were built with capability support. */
|
||||
okay = have_cap_support;
|
||||
break;
|
||||
case TEST_HAVE_CAPS:
|
||||
/* Succeed if "capabilities work" == "we were built with capability
|
||||
* support." */
|
||||
okay = have_cap_support == have_capability_support();
|
||||
break;
|
||||
case TEST_ROOT_CAN_BIND_LOW:
|
||||
/* Succeed if root can bind low ports. */
|
||||
okay = check_can_bind_low_ports() == 1;
|
||||
break;
|
||||
case TEST_SETUID:
|
||||
/* Succeed if we can do a setuid with no capability retention, and doing
|
||||
* so makes us lose the ability to bind low ports */
|
||||
case TEST_SETUID_KEEPCAPS:
|
||||
/* Succeed if we can do a setuid with capability retention, and doing so
|
||||
* does not make us lose the ability to bind low ports */
|
||||
{
|
||||
int keepcaps = (test_id == TEST_SETUID_KEEPCAPS);
|
||||
okay = switch_id(username, keepcaps ? SWITCH_ID_KEEP_BINDLOW : 0) == 0;
|
||||
if (okay) {
|
||||
okay = check_can_bind_low_ports() == keepcaps;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case TEST_SETUID_STRICT:
|
||||
/* Succeed if, after a setuid, we cannot setuid back, and we cannot
|
||||
* re-grab any capabilities. */
|
||||
okay = switch_id(username, SWITCH_ID_KEEP_BINDLOW) == 0;
|
||||
if (okay) {
|
||||
/* We'd better not be able to setuid back! */
|
||||
if (setuid(0) == 0 || errno != EPERM) {
|
||||
okay = 0;
|
||||
}
|
||||
}
|
||||
#ifdef HAVE_LINUX_CAPABILITIES
|
||||
if (okay) {
|
||||
cap_t caps = cap_get_proc();
|
||||
const cap_value_t caplist[] = {
|
||||
CAP_SETUID,
|
||||
};
|
||||
cap_set_flag(caps, CAP_PERMITTED, 1, caplist, CAP_SET);
|
||||
if (cap_set_proc(caps) == 0 || errno != EPERM) {
|
||||
okay = 0;
|
||||
}
|
||||
cap_free(caps);
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unsupported test '%s'\n", testname);
|
||||
okay = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!okay) {
|
||||
fprintf(stderr, "Test %s failed!\n", testname);
|
||||
}
|
||||
|
||||
return (okay ? 0 : 1);
|
||||
}
|
||||
|
25
src/test/test_switch_id.sh
Executable file
25
src/test/test_switch_id.sh
Executable file
@ -0,0 +1,25 @@
|
||||
#!/bin/sh
|
||||
|
||||
if test "`id -u`" != '0'; then
|
||||
echo "This test only works when run as root. Skipping." >&2
|
||||
exit 77
|
||||
fi
|
||||
|
||||
if test "`id -u nobody`" = ""; then
|
||||
echo "This test requires that your system have a 'nobody' user. Sorry." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"${builddir:-.}/src/test/test-switch-id" nobody setuid || exit 1
|
||||
"${builddir:-.}/src/test/test-switch-id" nobody root-bind-low || exit 1
|
||||
"${builddir:-.}/src/test/test-switch-id" nobody setuid-strict || exit 1
|
||||
"${builddir:-.}/src/test/test-switch-id" nobody built-with-caps || exit 0
|
||||
# ... Go beyond this point only if we were built with capability support.
|
||||
|
||||
"${builddir:-.}/src/test/test-switch-id" nobody have-caps || exit 1
|
||||
"${builddir:-.}/src/test/test-switch-id" nobody setuid-keepcaps || exit 1
|
||||
|
||||
|
||||
echo "All okay"
|
||||
|
||||
exit 0
|
Loading…
Reference in New Issue
Block a user