diff options
author | David Härdeman <david@hardeman.nu> | 2020-06-17 11:36:49 +0200 |
---|---|---|
committer | David Härdeman <david@hardeman.nu> | 2020-06-17 11:36:49 +0200 |
commit | 688a44577ac447362b8a7570764edc34b48b30a9 (patch) | |
tree | 015b0e479c4adc5c1e3d3b0e1c3ce08d3bcd67a7 | |
parent | 059bb183777f54ef61c93a6a94140874a9619181 (diff) |
Implement capability handling and user switching, disallow running as root
-rw-r--r-- | main.c | 97 | ||||
-rw-r--r-- | main.h | 2 | ||||
-rw-r--r-- | meson.build | 4 |
3 files changed, 95 insertions, 8 deletions
@@ -1,3 +1,4 @@ +#define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <stdarg.h> @@ -13,6 +14,9 @@ #include <sys/eventfd.h> #include <signal.h> #include <systemd/sd-daemon.h> +#include <cap-ng.h> +#include <pwd.h> +#include <grp.h> #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 <someuser>)"); + + 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 : "<unknown>"); + 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; @@ -88,6 +88,8 @@ struct uring_task { }; struct cfg { + uid_t uid; + gid_t gid; const char *homedir; char *cfg_path; bool do_igmp; diff --git a/meson.build b/meson.build index 69fd4e5..2425c45 100644 --- a/meson.build +++ b/meson.build @@ -2,6 +2,7 @@ project('mcproxy', 'c', default_options : ['c_std=gnu18']) liburing = dependency('liburing') libsystemd = dependency('libsystemd') +libcapng = dependency('libcap-ng') mcproxy_sources = [ 'main.c', @@ -22,5 +23,6 @@ executable('stest', 'stest.c') executable('mcproxy', mcproxy_sources, dependencies : [liburing, - libsystemd]) + libsystemd, + libcapng]) |