From 1bc2695744c729804af32d48ce68854cba4de8f7 Mon Sep 17 00:00:00 2001 From: Ondrej Zajicek Date: Tue, 10 May 2011 02:42:17 +0200 Subject: [PATCH] Allows run with restricted privileges. Adds option -u and -g to specify user and group. When different user (than root) is specified, linux capabilities CAP_NET_* are kept. --- sysdep/cf/README | 2 + sysdep/cf/linux-22.h | 2 + sysdep/cf/linux-v6.h | 2 + sysdep/linux/Modules | 1 + sysdep/linux/syspriv.h | 62 +++++++++++++++++++++++++++++++ sysdep/unix/main.c | 83 +++++++++++++++++++++++++++++++++++++++++- 6 files changed, 150 insertions(+), 2 deletions(-) create mode 100644 sysdep/linux/syspriv.h diff --git a/sysdep/cf/README b/sysdep/cf/README index 15a45a65..3b5bcd4f 100644 --- a/sysdep/cf/README +++ b/sysdep/cf/README @@ -6,6 +6,7 @@ CONFIG_SELF_CONSCIOUS We're able to recognize whether route was installed by us CONFIG_MULTIPLE_TABLES The kernel supports multiple routing tables CONFIG_ALL_TABLES_AT_ONCE Kernel scanner wants to process all tables at once CONFIG_MC_PROPER_SRC Multicast packets have source address according to socket saddr field +CONFIG_RESTRICTED_PRIVILEGES Implements restricted privileges using drop_uid() CONFIG_UNIX_IFACE Use Unix interface scanner CONFIG_UNIX_SET Use Unix route setting @@ -19,3 +20,4 @@ CONFIG_UNNUM_MULTICAST krt-iface: We support multicasts on unnumbered PtP device CONFIG_LINUX_MC_MREQN Linux: Use struct mreqn for multicasting CONFIG_LINUX_MC_MREQ Linux: Use struct mreq CONFIG_LINUX_MC_MREQ_BIND Linux: Use struct mreq and SO_BINDTODEVICE + diff --git a/sysdep/cf/linux-22.h b/sysdep/cf/linux-22.h index 9ccab648..51b339d1 100644 --- a/sysdep/cf/linux-22.h +++ b/sysdep/cf/linux-22.h @@ -17,6 +17,8 @@ #define CONFIG_LINUX_MC_MREQN #define CONFIG_UNIX_DONTROUTE +#define CONFIG_RESTRICTED_PRIVILEGES + /* Link: sysdep/linux/netlink Link: sysdep/linux diff --git a/sysdep/cf/linux-v6.h b/sysdep/cf/linux-v6.h index ef52ee46..467d7728 100644 --- a/sysdep/cf/linux-v6.h +++ b/sysdep/cf/linux-v6.h @@ -19,6 +19,8 @@ #define CONFIG_MULTIPLE_TABLES #define CONFIG_ALL_TABLES_AT_ONCE +#define CONFIG_RESTRICTED_PRIVILEGES + /* Link: sysdep/linux/netlink Link: sysdep/linux diff --git a/sysdep/linux/Modules b/sysdep/linux/Modules index 1b867d81..09f4a470 100644 --- a/sysdep/linux/Modules +++ b/sysdep/linux/Modules @@ -3,3 +3,4 @@ krt-scan.c krt-scan.h #endif sysio.h +syspriv.h diff --git a/sysdep/linux/syspriv.h b/sysdep/linux/syspriv.h new file mode 100644 index 00000000..bfe19ac3 --- /dev/null +++ b/sysdep/linux/syspriv.h @@ -0,0 +1,62 @@ + +#include +#include + +#ifndef _LINUX_CAPABILITY_VERSION_3 +#define _LINUX_CAPABILITY_VERSION_3 0x20080522 +#define _LINUX_CAPABILITY_U32S_3 2 +#endif + +/* capset() prototype is missing ... */ +int capset(cap_user_header_t hdrp, const cap_user_data_t datap); + +static inline int +set_capabilities(u32 caps) +{ + struct __user_cap_header_struct cap_hdr; + struct __user_cap_data_struct cap_dat[_LINUX_CAPABILITY_U32S_3]; + int err; + + cap_hdr.version = _LINUX_CAPABILITY_VERSION_3; + cap_hdr.pid = 0; + + memset(cap_dat, 0, sizeof(cap_dat)); + cap_dat[0].effective = cap_dat[0].permitted = caps; + + err = capset(&cap_hdr, cap_dat); + if (!err) + return 0; + + /* Kernel may support do not support our version of capability interface. + The last call returned supported version so we just retry it. */ + if (errno == EINVAL) + { + err = capset(&cap_hdr, cap_dat); + if (!err) + return 0; + } + + return -1; +} + +static void +drop_uid(uid_t uid) +{ + u32 caps = + CAP_TO_MASK(CAP_NET_BIND_SERVICE) | + CAP_TO_MASK(CAP_NET_BROADCAST) | + CAP_TO_MASK(CAP_NET_ADMIN) | + CAP_TO_MASK(CAP_NET_RAW); + + if (seteuid(uid) < 0) + die("seteuid: %m"); + + if (set_capabilities(caps) < 0) + die("capset: %m"); + + if (prctl(PR_SET_KEEPCAPS, 1) < 0) + die("prctl: %m"); + + if (setresuid(uid, uid, uid) < 0) + die("setresuid: %m"); +} diff --git a/sysdep/unix/main.c b/sysdep/unix/main.c index fd921ace..744062b4 100644 --- a/sysdep/unix/main.c +++ b/sysdep/unix/main.c @@ -8,11 +8,15 @@ #undef LOCAL_DEBUG +#define _GNU_SOURCE 1 + #include #include #include #include #include +#include +#include #include "nest/bird.h" #include "lib/lists.h" @@ -58,6 +62,29 @@ async_dump(void) debug("\n"); } +/* + * Dropping privileges + */ + +#ifdef CONFIG_RESTRICTED_PRIVILEGES +#include "lib/syspriv.h" +#else + +static inline void +drop_uid(uid_t uid) +{ + die("Cannot change user on this platform"); +} + +#endif + +static inline void +drop_gid(gid_t gid) +{ + if (setgid(gid) < 0) + die("setgid: %m"); +} + /* * Reading the Configuration */ @@ -444,14 +471,16 @@ signal_init(void) * Parsing of command-line arguments */ -static char *opt_list = "c:dD:ps:"; +static char *opt_list = "c:dD:ps:u:g:"; static int parse_and_exit; char *bird_name; +static char *use_user; +static char *use_group; static void usage(void) { - fprintf(stderr, "Usage: %s [-c ] [-d] [-D ] [-p] [-s ]\n", bird_name); + fprintf(stderr, "Usage: %s [-c ] [-d] [-D ] [-p] [-s ] [-u ] [-g ]\n", bird_name); exit(1); } @@ -469,6 +498,44 @@ get_bird_name(char *s, char *def) return t+1; } +static inline uid_t +get_uid(const char *s) +{ + struct passwd *pw; + char *endptr; + + errno = 0; + long int rv = strtol(s, &endptr, 10); + + if (!errno && !*endptr) + return rv; + + pw = getpwnam(s); + if (!pw) + die("Cannot find user '%s'", s); + + return pw->pw_uid; +} + +static inline gid_t +get_gid(const char *s) +{ + struct group *gr; + char *endptr; + + errno = 0; + long int rv = strtol(s, &endptr, 10); + + if (!errno && !*endptr) + return rv; + + gr = getgrnam(s); + if (!gr) + die("Cannot find group '%s'", s); + + return gr->gr_gid; +} + static void parse_args(int argc, char **argv) { @@ -504,6 +571,12 @@ parse_args(int argc, char **argv) case 's': path_control_socket = optarg; break; + case 'u': + use_user = optarg; + break; + case 'g': + use_group = optarg; + break; default: usage(); } @@ -528,6 +601,12 @@ main(int argc, char **argv) log_init_debug(""); log_switch(debug_flag, NULL, NULL); + if (use_group) + drop_gid(get_gid(use_group)); + + if (use_user) + drop_uid(get_uid(use_user)); + if (!parse_and_exit) test_old_bird(path_control_socket);