summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-17 11:36:49 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-17 11:36:49 +0200
commit688a44577ac447362b8a7570764edc34b48b30a9 (patch)
tree015b0e479c4adc5c1e3d3b0e1c3ce08d3bcd67a7
parent059bb183777f54ef61c93a6a94140874a9619181 (diff)
Implement capability handling and user switching, disallow running as root
-rw-r--r--main.c97
-rw-r--r--main.h2
-rw-r--r--meson.build4
3 files changed, 95 insertions, 8 deletions
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 <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;
diff --git a/main.h b/main.h
index 0074bd9..282d827 100644
--- a/main.h
+++ b/main.h
@@ -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])