From ebf4e7ee9e561f05025b5990f7ca9f9dbefb0a2a Mon Sep 17 00:00:00 2001 From: Oleksandr Natalenko Date: Wed, 2 Nov 2022 19:14:09 +0100 Subject: [PATCH] check capabilities, not effective user ID Under Linux, a process may not be run under root, yet it may have a permission to do what a superuser may do given specific capabilities are granted. This commit makes iodine not depend on EUID being 0 in order to run properly. Instead, in presence of libcap-ng, the following capabilities are being checked: * `CAP_NET_BIND_SERVICES` for server to bind to a port, lower than `/proc/sys/net/ipv4/ip_unprivileged_port_start` * `CAP_NET_ADMIN` to operate on a TUN device * `CAP_SETUID` and `CAP_SETGID` in case server is configured to change the user it runs on behalf of This change is handy if iodine is being run under a non-root user, provided `AmbientCapabilities=` and `CapabilityBoundingSet=` of systemd are employed in the first place. Fixes: https://github.com/yarrick/iodine/issues/80 Signed-off-by: Oleksandr Natalenko --- src/common.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/common.h | 4 ++-- src/iodine.c | 2 +- src/iodined.c | 2 +- src/osflags | 2 ++ 5 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/common.c b/src/common.c index 0d62752..7739750 100644 --- a/src/common.c +++ b/src/common.c @@ -51,6 +51,11 @@ # include #endif +#ifdef HAVE_LIBCAPNG +#include +#include +#endif + #include "common.h" /* The raw header used when not using DNS protocol */ @@ -103,12 +108,60 @@ int setgroups(int count, int *groups) #ifndef WINDOWS32 void -check_privileges(void) +check_privileges(char *username, int port) { +#if defined HAVE_LIBCAPNG + bool capable = true; + + if (capng_get_caps_process() == -1) { + warnx("Unable to get capabilities"); + exit(-1); + } + + if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_NET_ADMIN)) { + warnx("capabilities: CAP_NET_ADMIN required"); + capable = false; + } + + if (port) { + unsigned short int ip_unprivileged_port_start = 1024; + + FILE *file = fopen("/proc/sys/net/ipv4/ip_unprivileged_port_start", "r"); + if (!file) { + warnx("sysctl: unable to get ip_unprivileged_port_start value"); + // do not bail out here in case systemd.service has ProcSubset=pid set + } else { + fscanf(file, "%hu", &ip_unprivileged_port_start); + fclose(file); + } + + if (port < ip_unprivileged_port_start && + !capng_have_capability(CAPNG_EFFECTIVE, CAP_NET_BIND_SERVICE)) { + warnx("capabilities: CAP_NET_BIND_SERVICE required"); + capable = false; + } + } + + if (username) { + if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_SETUID)) { + warnx("capabilities: CAP_SETUID required"); + capable = false; + } + if (!capng_have_capability(CAPNG_EFFECTIVE, CAP_SETGID)) { + warnx("capabilities: CAP_SETGID required"); + capable = false; + } + } + + if (!capable) { + exit(-1); + } +#else if (geteuid() != 0) { warnx("Run as root and you'll be happy."); exit(-1); } +#endif } #endif diff --git a/src/common.h b/src/common.h index 38222da..8b0a2a4 100644 --- a/src/common.h +++ b/src/common.h @@ -105,11 +105,11 @@ enum connection { }; #ifdef WINDOWS32 -static inline void check_privileges(void) +static inline void check_privileges(char *, int) { } #else -void check_privileges(void); +void check_privileges(char *, int); #endif char *format_addr(struct sockaddr_storage *sockaddr, int sockaddr_len); int get_addr(char *, int, int, int, struct sockaddr_storage *); diff --git a/src/iodine.c b/src/iodine.c index 3244dd9..88ebd21 100644 --- a/src/iodine.c +++ b/src/iodine.c @@ -279,7 +279,7 @@ int main(int argc, char **argv) } } - check_privileges(); + check_privileges(username, 0); argc -= optind; argv += optind; diff --git a/src/iodined.c b/src/iodined.c index 772f9ef..fccaffe 100644 --- a/src/iodined.c +++ b/src/iodined.c @@ -2519,7 +2519,7 @@ main(int argc, char **argv) argc -= optind; argv += optind; - check_privileges(); + check_privileges(username, port); if (argc != 2) usage(); diff --git a/src/osflags b/src/osflags index 9a437bd..9c3cc7e 100755 --- a/src/osflags +++ b/src/osflags @@ -23,6 +23,7 @@ link) [ -e /usr/include/selinux/selinux.h ] && FLAGS="$FLAGS -lselinux"; "$PKG_CONFIG" --exists libsystemd-daemon && FLAGS="$FLAGS $($PKG_CONFIG --libs libsystemd-daemon)"; "$PKG_CONFIG" --exists libsystemd && FLAGS="$FLAGS $($PKG_CONFIG --libs libsystemd)"; + "$PKG_CONFIG" --exists libcap-ng && FLAGS="$FLAGS $($PKG_CONFIG --libs libcap-ng)"; echo $FLAGS; ;; esac @@ -43,6 +44,7 @@ cflags) [ -e /usr/include/selinux/selinux.h ] && FLAGS="$FLAGS -DHAVE_SETCON"; "$PKG_CONFIG" --exists libsystemd-daemon && FLAGS="$FLAGS -DHAVE_SYSTEMD"; "$PKG_CONFIG" --exists libsystemd && FLAGS="$FLAGS -DHAVE_SYSTEMD"; + "$PKG_CONFIG" --exists libcap-ng && FLAGS="$FLAGS -DHAVE_LIBCAPNG"; echo $FLAGS; ;; GNU/kFreeBSD|GNU)