From 688a44577ac447362b8a7570764edc34b48b30a9 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Wed, 17 Jun 2020 11:36:49 +0200 Subject: Implement capability handling and user switching, disallow running as root --- main.c | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 90 insertions(+), 7 deletions(-) (limited to 'main.c') diff --git a/main.c b/main.c index 1147709..598b9b2 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,4 @@ +#define _GNU_SOURCE #include #include #include @@ -13,6 +14,9 @@ #include #include #include +#include +#include +#include #include "main.h" #include "uring.h" @@ -226,6 +230,8 @@ cfg_init(int argc, char **argv) if (!cfg) perrordie("malloc"); + cfg->uid = geteuid(); + cfg->gid = getegid(); uring_task_init(&cfg->task, "cfg", NULL, cfg_free); list_init(&cfg->servers); @@ -235,10 +241,11 @@ cfg_init(int argc, char **argv) { "homedir", required_argument, 0, 'h' }, { "debug", required_argument, 0, 'd' }, { "verbose", no_argument, 0, 'v' }, + { "user", required_argument, 0, 'v' }, { 0, 0, 0, 0 } }; - c = getopt_long(argc, argv, ":h:d:v", + c = getopt_long(argc, argv, ":h:d:vu:", long_options, &option_index); if (c == -1) break; @@ -250,6 +257,28 @@ cfg_init(int argc, char **argv) case 'v': debug_mask |= DBG_VERBOSE; break; + case 'u': { + struct passwd *pwd; + + errno = 0; + pwd = getpwnam(optarg); + if (!pwd) { + if (errno == 0) + errno = ESRCH; + if (errno == ESRCH) + die("failed to find user %s", optarg); + else + die("failed to find user %s (%m)", optarg); + } + + debug(DBG_CFG, "asked to execute with uid %ji gid %ji\n", + (intmax_t)pwd->pw_uid, + (intmax_t)pwd->pw_gid); + cfg->uid = pwd->pw_uid; + cfg->gid = pwd->pw_gid; + break; + } + case 'd': if (!strcasecmp(optarg, "all")) { debug_mask = ~0; @@ -284,11 +313,50 @@ cfg_init(int argc, char **argv) if (!cfg->homedir) cfg->homedir = DEFAULT_HOMEDIR_PATH; - verbose("Homedir is %s\n", cfg->homedir); + return cfg; +} + +static void +cfg_apply(struct cfg *cfg) +{ + if (cfg->uid == 0 || cfg->gid == 0) + /* This catches both -u root and running as root without -u */ + die("Execution as root is not supported (use -u )"); + + capng_clear(CAPNG_SELECT_BOTH); + if (capng_updatev(CAPNG_ADD, + CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_NET_RAW, CAP_NET_BIND_SERVICE, -1)) + die("capng_updatev failed"); + + if (geteuid() != cfg->uid) { + if (capng_change_id(cfg->uid, + cfg->gid, + CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)) + die("capng_change_id failed"); + } else { + if (capng_apply(CAPNG_SELECT_BOTH)) { + capng_clear(CAPNG_SELECT_BOTH); + if (capng_apply(CAPNG_SELECT_BOTH)) + die("capng_apply failed"); + } + setgroups(0, NULL); + } + + /* + * Do this after caps have been dropped to make sure we're not + * accessing a directory we should have permissions to. + */ if (chdir(cfg->homedir)) - perrordie("chdir"); + perrordie("chdir(%s)", cfg->homedir); - return cfg; + if (debug_enabled(DBG_VERBOSE)) { + char *wd; + + wd = get_current_dir_name(); + verbose("Homedir: %s\n", wd ? wd : ""); + free(wd); + } } struct signalfd_ev { @@ -467,13 +535,30 @@ main(int argc, char **argv) unsigned server_count; cfg = cfg_init(argc, argv); - cfghack = cfg; + cfg_apply(cfg); + cfg_read(cfg); uring_init(cfg); + igmp_init(cfg); + + /* Drop CAP_NET_RAW (if we have it), only used for igmp */ + capng_clear(CAPNG_SELECT_BOTH); + if (capng_update(CAPNG_ADD, + CAPNG_EFFECTIVE | CAPNG_PERMITTED, + CAP_NET_BIND_SERVICE)) + die("capng_update failed"); + + if (capng_apply(CAPNG_SELECT_BOTH)) { + /* Try clearing all caps, shouldn't fail */ + capng_clear(CAPNG_SELECT_BOTH); + if (capng_apply(CAPNG_SELECT_BOTH)) + die("capng_apply failed"); + } + signalfd_init(cfg); cfgdir_init(cfg); @@ -482,8 +567,6 @@ main(int argc, char **argv) announce_start(cfg->aev); - igmp_init(cfg); - uring_task_put(cfg, &cfg->task); server_count = 0; -- cgit v1.2.3