From 78440eab81959ec7a95effd579fd87b7c56dbe3d Mon Sep 17 00:00:00 2001 From: Matthias Schiffer Date: Mon, 24 Dec 2012 23:52:18 +0100 Subject: Add user switching and capability support --- src/CMakeLists.txt | 1 + src/capabilities.c | 114 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/config.c | 70 +++++++++++++++++++++++++++++++- src/config.l | 5 +++ src/config.y | 42 ++++++++++++++++++++ src/fastd.c | 68 ++++++++++++++++++++++++++++++-- src/fastd.h | 16 +++++++- src/types.h | 6 +++ 8 files changed, 317 insertions(+), 5 deletions(-) create mode 100644 src/capabilities.c (limited to 'src') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0fb0ebd..ccee0d9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,6 +16,7 @@ BISON_TARGET(fastd_config_parse config.y ${CMAKE_CURRENT_BINARY_DIR}/config.yy.c add_executable(fastd fastd.c + capabilities.c config.c crypto.c crypto_linux.c diff --git a/src/capabilities.c b/src/capabilities.c new file mode 100644 index 0000000..415cce6 --- /dev/null +++ b/src/capabilities.c @@ -0,0 +1,114 @@ +/* + Copyright (c) 2012, Matthias Schiffer + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +#include "fastd.h" + +#ifdef WITH_CAPABILITIES + +#include + +#include +#include + + +static void try_cap(fastd_context_t *ctx, cap_value_t cap) { + char *name = cap_to_name(cap); + + if (!name) + return; + + pr_debug(ctx, "Trying to acquire %s", name); + + cap_t caps = cap_get_proc(); + + if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &cap, CAP_SET) < 0) { + pr_debug_errno(ctx, "cap_set_flags"); + goto end_free; + } + + if (cap_set_proc(caps) < 0) { + pr_debug_errno(ctx, "cap_set_proc"); + goto end_free; + } + + pr_verbose(ctx, "Acquired capability %s.", name); + + end_free: + cap_free(caps); + cap_free(name); +} + +void fastd_cap_init(fastd_context_t *ctx) { + /* interface creation */ + try_cap(ctx, CAP_NET_ADMIN); + + /* privileged binds */ + try_cap(ctx, CAP_NET_BIND_SERVICE); + + /* for device binds */ + try_cap(ctx, CAP_NET_RAW); +} + +void fastd_cap_lock(fastd_context_t *ctx) { + if (prctl(PR_SET_SECUREBITS, + SECBIT_KEEP_CAPS_LOCKED | + SECBIT_NO_SETUID_FIXUP | + SECBIT_NO_SETUID_FIXUP_LOCKED | + SECBIT_NOROOT | + SECBIT_NOROOT_LOCKED) < 0) { + pr_debug_errno(ctx, "prctl"); + } +} + +void fastd_cap_drop(fastd_context_t *ctx) { + cap_t caps = cap_init(); + + if (cap_set_proc(caps) < 0) { + pr_debug_errno(ctx, "cap_set_proc"); + } + else { + pr_verbose(ctx, "Dropped capabilities."); + } + + cap_free(caps); + +} + + +#else /* WITH_CAPABILITIES */ + +void fastd_cap_init(fastd_context_t *ctx) { +} + +void fastd_cap_lock(fastd_context_t *ctx) { +} + +void fastd_cap_drop(fastd_context_t *ctx) { +} + +#endif /* WITH_CAPABILITIES */ + + diff --git a/src/config.c b/src/config.c index 57059e5..2d387f3 100644 --- a/src/config.c +++ b/src/config.c @@ -31,11 +31,15 @@ #include #include -#include #include +#include #include +#include #include #include + +#include + #include #include @@ -104,6 +108,8 @@ static void default_config(fastd_config_t *conf) { conf->mtu = 1500; conf->mode = MODE_TAP; + conf->drop_caps = DROP_CAPS_ON; + conf->protocol = &fastd_protocol_ec25519_fhmqvc; conf->method_default = &fastd_method_null; conf->key_valid = 3600; /* 60 minutes */ @@ -522,6 +528,8 @@ static void count_peers(fastd_context_t *ctx, fastd_config_t *conf) { OPTION(usage, "--help" OR "-h", "Shows this help text") \ OPTION(version, "--version" OR "-v", "Shows the fastd version") \ OPTION(option_daemon, "--daemon" OR "-d", "Runs fastd in the background") \ + OPTION_ARG(option_user, "--user", "", "Sets the user to run fastd as") \ + OPTION_ARG(option_group, "--group", "", "Sets the group to run fastd as") \ OPTION_ARG(option_pid_file, "--pid-file", "", "Writes fastd's PID to the specified file") \ OPTION_ARG(option_log_level, "--log-level", "error|warn|info|verbose|debug", "Sets the stderr log level; default is info, if no alternative log destination ist configured") \ OPTION_ARG(option_syslog_level, "--syslog-level", "error|warn|info|verbose|debug", "Sets the log level for syslog output; default is not to use syslog") \ @@ -600,6 +608,16 @@ static int parse_log_level(fastd_context_t *ctx, const char *arg) { +static void option_user(fastd_context_t *ctx, fastd_config_t *conf, const char *arg) { + free(conf->user); + conf->user = strdup(arg); +} + +static void option_group(fastd_context_t *ctx, fastd_config_t *conf, const char *arg) { + free(conf->group); + conf->group = strdup(arg); +} + static void option_log_level(fastd_context_t *ctx, fastd_config_t *conf, const char *arg) { conf->log_stderr_level = parse_log_level(ctx, arg); } @@ -786,6 +804,52 @@ static void option_machine_readable(fastd_context_t *ctx, fastd_config_t *conf) } +static void configure_user(fastd_context_t *ctx, fastd_config_t *conf) { + conf->uid = getuid(); + conf->gid = getgid(); + + if (conf->user) { + struct passwd pwd, *pwdr; + size_t bufspace = 1024; + int error; + + do { + char buf[bufspace]; + error = getpwnam_r(conf->user, &pwd, buf, bufspace, &pwdr); + bufspace *= 2; + } while(error == ERANGE); + + if (error) + exit_errno(ctx, "getpwnam_r"); + + if (!pwdr) + exit_error(ctx, "Unable to find user `%s'.", conf->user); + + conf->uid = pwdr->pw_uid; + conf->gid = pwdr->pw_gid; + } + + if (conf->group) { + struct group grp, *grpr; + size_t bufspace = 1024; + int error; + + do { + char buf[bufspace]; + error = getgrnam_r(conf->group, &grp, buf, bufspace, &grpr); + bufspace *= 2; + } while(error == ERANGE); + + if (error) + exit_errno(ctx, "getgrnam_r"); + + if (!grpr) + exit_error(ctx, "Unable to find group `%s'.", conf->group); + + conf->gid = grpr->gr_gid; + } +} + void fastd_configure(fastd_context_t *ctx, fastd_config_t *conf, int argc, char *const argv[]) { #define OR , #define OPTION(func, options, message) \ @@ -831,6 +895,8 @@ void fastd_configure(fastd_context_t *ctx, fastd_config_t *conf, int argc, char if (!conf->peers && !has_peer_group_peer_dirs(conf->peer_group)) exit_error(ctx, "config error: neither fixed peers nor peer dirs have been configured"); + configure_user(ctx, conf); + count_peers(ctx, conf); #undef OR @@ -968,6 +1034,8 @@ void fastd_config_release(fastd_context_t *ctx, fastd_config_t *conf) { free_peer_group(conf->peer_group); + free(conf->user); + free(conf->group); free(conf->ifname); free(conf->secret); free(conf->on_up); diff --git a/src/config.l b/src/config.l index afe37dd..1881575 100644 --- a/src/config.l +++ b/src/config.l @@ -100,7 +100,12 @@ float { TOKEN(TOK_FLOAT); } crypto { TOKEN(TOK_CRYPTO); } use { TOKEN(TOK_USE); } default { TOKEN(TOK_DEFAULT); } +user { TOKEN(TOK_USER); } group { TOKEN(TOK_GROUP); } +drop { TOKEN(TOK_DROP); } +capabilities { TOKEN(TOK_CAPABILITIES); } +early { TOKEN(TOK_EARLY); } +lock { TOKEN(TOK_LOCK); } limit { TOKEN(TOK_LIMIT); } [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} { diff --git a/src/config.y b/src/config.y index d91dd61..5794124 100644 --- a/src/config.y +++ b/src/config.y @@ -100,7 +100,12 @@ %token TOK_CRYPTO %token TOK_USE %token TOK_DEFAULT +%token TOK_USER %token TOK_GROUP +%token TOK_DROP +%token TOK_CAPABILITIES +%token TOK_EARLY +%token TOK_LOCK %token TOK_LIMIT %token TOK_ADDR4 @@ -129,6 +134,8 @@ %type maybe_bind_interface %type maybe_bind_default %type bind_default +%type drop_capabilities_enabled +%type drop_capabilities_lock %% start: START_CONFIG config @@ -146,6 +153,9 @@ peer_group_config: ; statement: peer_group_statement + | TOK_USER user ';' + | TOK_GROUP group ';' + | TOK_DROP TOK_CAPABILITIES drop_capabilities ';' | TOK_LOG log ';' | TOK_INTERFACE interface ';' | TOK_BIND bind ';' @@ -169,6 +179,38 @@ peer_group_statement: | TOK_INCLUDE include ';' ; +user: TOK_STRING { + free(conf->user); + conf->user = strdup($1->str); + } + +group: TOK_STRING { + free(conf->group); + conf->group = strdup($1->str); + } + +drop_capabilities: + drop_capabilities_enabled drop_capabilities_lock { + conf->drop_caps = $1; + conf->lock_caps = $2; + } + +drop_capabilities_enabled: + TOK_EARLY { + $$ = DROP_CAPS_EARLY; + } + | boolean { + $$ = $1 ? DROP_CAPS_ON : DROP_CAPS_OFF; + } + +drop_capabilities_lock: + TOK_LOCK { + $$ = true; + } + | { + $$ = false; + } + log: TOK_LEVEL log_level { conf->log_stderr_level = $2; } diff --git a/src/fastd.c b/src/fastd.c index 18a0c91..b7a7a83 100644 --- a/src/fastd.c +++ b/src/fastd.c @@ -33,6 +33,7 @@ #include "task.h" #include +#include #include #include #include @@ -94,6 +95,15 @@ static void init_pipes(fastd_context_t *ctx) { } static void init_log(fastd_context_t *ctx) { + uid_t uid = geteuid(); + gid_t gid = getegid(); + + if (ctx->conf->user || ctx->conf->group) { + /* We don't care about errors here */ + setegid(ctx->conf->gid); + seteuid(ctx->conf->uid); + } + if (ctx->conf->log_syslog_level >= 0) openlog(ctx->conf->log_syslog_ident, LOG_PID, LOG_DAEMON); @@ -107,6 +117,9 @@ static void init_log(fastd_context_t *ctx) { file->next = ctx->log_files; ctx->log_files = file; } + + seteuid(uid); + setegid(gid); } static void close_log(fastd_context_t *ctx) { @@ -993,10 +1006,19 @@ static void write_pid(fastd_context_t *ctx, pid_t pid) { if (!ctx->conf->pid_file) return; + uid_t uid = geteuid(); + gid_t gid = getegid(); + + if (ctx->conf->user || ctx->conf->group) { + /* We don't care about errors here */ + setegid(ctx->conf->gid); + seteuid(ctx->conf->uid); + } + int fd = open(ctx->conf->pid_file, O_WRONLY|O_CREAT|O_TRUNC, 0666); if (fd < 0) { pr_error_errno(ctx, "can't write PID file: open"); - return; + goto end; } if (dprintf(fd, "%i", pid) < 0) @@ -1004,6 +1026,36 @@ static void write_pid(fastd_context_t *ctx, pid_t pid) { if (close(fd) < 0) pr_warn_errno(ctx, "close"); + + end: + seteuid(uid); + setegid(gid); +} + +static void set_user(fastd_context_t *ctx) { + if (ctx->conf->user || ctx->conf->group) { + if (setgid(ctx->conf->gid) < 0) + exit_errno(ctx, "setgid"); + + if (setgroups(1, &ctx->conf->gid) < 0) { + if (errno != EPERM) + pr_debug_errno(ctx, "setgroups"); + } + + if (setuid(ctx->conf->uid) < 0) + exit_errno(ctx, "setuid"); + + pr_info(ctx, "Changed to UID %i, GID %i.", ctx->conf->uid, ctx->conf->gid); + } +} + +static void drop_caps(fastd_context_t *ctx) { + if (ctx->conf->lock_caps) + fastd_cap_lock(ctx); + + set_user(ctx); + + fastd_cap_drop(ctx); } int main(int argc, char *argv[]) { @@ -1023,8 +1075,6 @@ int main(int argc, char *argv[]) { init_log(&ctx); - crypto_init(&ctx); - if (conf.generate_key) { conf.protocol->generate_key(&ctx); exit(0); @@ -1041,6 +1091,10 @@ int main(int argc, char *argv[]) { pr_info(&ctx, "fastd " FASTD_VERSION " starting"); + fastd_cap_init(&ctx); + + crypto_init(&ctx); + init_sockets(&ctx); bind_sockets(&ctx); @@ -1066,8 +1120,16 @@ int main(int argc, char *argv[]) { write_pid(&ctx, getpid()); } + if (conf.drop_caps == DROP_CAPS_EARLY) + drop_caps(&ctx); + on_up(&ctx); + if (conf.drop_caps == DROP_CAPS_ON) + drop_caps(&ctx); + else if (conf.drop_caps == DROP_CAPS_OFF) + set_user(&ctx); + while (!terminate) { handle_tasks(&ctx); diff --git a/src/fastd.h b/src/fastd.h index fd2a668..dc24579 100644 --- a/src/fastd.h +++ b/src/fastd.h @@ -195,6 +195,14 @@ struct fastd_config { bool forward; + fastd_drop_caps_t drop_caps; + bool lock_caps; + + char *user; + char *group; + uid_t uid; + gid_t gid; + const fastd_protocol_t *protocol; const fastd_method_t *methods[MAX_METHODS]; const fastd_method_t *method_default; @@ -316,6 +324,10 @@ void fastd_config_release(fastd_context_t *ctx, fastd_config_t *conf); void fastd_configure(fastd_context_t *ctx, fastd_config_t *conf, int argc, char *const argv[]); void fastd_reconfigure(fastd_context_t *ctx, fastd_config_t *conf); +void fastd_cap_init(fastd_context_t *ctx); +void fastd_cap_lock(fastd_context_t *ctx); +void fastd_cap_drop(fastd_context_t *ctx); + void fastd_random_bytes(fastd_context_t *ctx, void *buffer, size_t len, bool secure); static inline int fastd_rand(fastd_context_t *ctx, int min, int max) { @@ -334,8 +346,10 @@ static inline int fastd_rand(fastd_context_t *ctx, int min, int max) { #define pr_verbose(ctx, args...) fastd_logf(ctx, LOG_INFO, args) #define pr_debug(ctx, args...) fastd_logf(ctx, LOG_DEBUG, args) +#define pr_error_errno(ctx, message) pr_error(ctx, "%s: %s", message, strerror(errno)) #define pr_warn_errno(ctx, message) pr_warn(ctx, "%s: %s", message, strerror(errno)) -#define pr_error_errno(ctx, message) pr_warn(ctx, "%s: %s", message, strerror(errno)) +#define pr_debug_errno(ctx, message) pr_debug(ctx, "%s: %s", message, strerror(errno)) + #define exit_fatal(ctx, args...) do { pr_fatal(ctx, args); abort(); } while(0) #define exit_bug(ctx, message) exit_fatal(ctx, "BUG: %s", message) #define exit_error(ctx, args...) do { pr_error(ctx, args); exit(1); } while(0) diff --git a/src/types.h b/src/types.h index cda16ec..a4da2b8 100644 --- a/src/types.h +++ b/src/types.h @@ -41,6 +41,12 @@ typedef enum fastd_mode { MODE_TUN, } fastd_mode_t; +typedef enum fastd_drop_caps { + DROP_CAPS_OFF, + DROP_CAPS_ON, + DROP_CAPS_EARLY, +} fastd_drop_caps_t; + typedef struct fastd_buffer fastd_buffer_t; typedef union fastd_peer_address fastd_peer_address_t; -- cgit v1.2.3