diff --git a/.travis.yml b/.travis.yml index b4dfdc47..4a0bfb1b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ env: - secure: fiVVKcMM8Cz8WAj6PB6eD/b+Y77klXOe9jbpehf6QwjFwf6paEHoMsrZ0aFXogm2Uej47GlTdRb3UkBqonbK4ANbu0ewsWCW0RGClZz5ghaSnfwdxEhuXsrFIax7DvJCStk2V84Keb+tSVemx4opxqZAlZ/Nen28S91KSDoJeRA= matrix: - BUILD_TYPE=normal - - CYASSL="3.3.2" BUILD_TYPE=cyassl + - LIBATTR="2.4.47" LIBCAP="2.24" CYASSL="3.3.2" BUILD_TYPE="full" cache: directories: - dependencies-src diff --git a/.travis_configure_wrapper.sh b/.travis_configure_wrapper.sh index 241dab90..96d07370 100755 --- a/.travis_configure_wrapper.sh +++ b/.travis_configure_wrapper.sh @@ -1,23 +1,52 @@ #!/usr/bin/env bash -if [[ -z "$BUILD_TYPE" ]]; then - echo "BUILD_TYPE not set. Bye." - exit 1 -fi -if [[ "$BUILD_TYPE" == "normal" ]]; then +function main { + if [[ -z "$BUILD_TYPE" ]]; then + echo "BUILD_TYPE not set. Bye." + exit 1 + fi - echo "Running Wifidog configure" - ./configure $@ + if [[ "$BUILD_TYPE" == "normal" ]]; then -elif [[ "$BUILD_TYPE" == "cyassl" ]]; then - if [[ -z "$CYASSL" ]]; then - echo "CYASSL not set." + echo "Running Wifidog configure" + ./configure $@ + + elif [[ "$BUILD_TYPE" == "full" ]]; then + if [[ -z "$CYASSL" || -z "$LIBCAP" || -z "$LIBATTR" ]]; then + echo "Make sure that CYASSL, LIBCAP and LIBATTR are set." + exit 1 + fi + mkdir -p dependencies-src || true + mkdir -p dependencies-installed || true + # reset CFLAGS because some dependencies generate warnings + OLD_CFLAGS="${CFLAGS}" + OLD_CXXFLAGS="${CXXFLAGS}" + OLD_LDFLAGS="${LDFLAGS}" + CUR=`pwd` + export CFLAGS="-I${CUR}/dependencies-installed/include/" + export CXXFLAGS="-I${CUR}/dependencies-installed/include/" + export LDFLAGS="-L${CUR}/dependencies-installed/lib/" + build_cyassl + build_libattr + build_libcap + echo "Running Wifidog configure" + export CFLAGS="${OLD_CFLAGS} ${CFLAGS}" + export CXXFLAGS="${OLD_CXXFLAGS} ${LDFLAGS}" + export LDFLAGS="${OLD_LDFLAGS} ${LDFLAGS}" + ./configure --enable-cyassl --enable-libcap $@ + else + echo "Unknow BUILD_TYPE $BUILD_TYPE" exit 1 fi + +} + +function build_cyassl { + # TODO: changing $CYASSL version number will not invalidate this check + # Need to remove full cache in travis interface if we want to upgrade + # CyaSSL CUR=`pwd` - mkdir -p dependencies-src || true - mkdir -p dependencies-installed || true if [[ ! -f dependencies-installed/include/cyassl/ssl.h ]]; then echo "Cached CyaSSL install not found. Installing." cd dependencies-src @@ -47,11 +76,57 @@ elif [[ "$BUILD_TYPE" == "cyassl" ]]; then else echo "Cached CyaSSL install found." fi - echo "Running Wifidog configure" - export CFLAGS="-I${CUR}/dependencies-installed/include/" - export LDFLAGS="-L${CUR}/dependencies-installed/lib/" - ./configure --enable-cyassl $@ -else - echo "Unknow BUILD_TYPE $BUILD_TYPE" - exit 1 -fi +} + +function build_libcap { + CUR=`pwd` + if [[ ! -f dependencies-installed/usr/include/sys/capability.h ]]; then + echo "Cached libcap not found. Installing." + cd dependencies-src + if [[ -f libcap-${LIBCAP}/Makefile ]]; then + echo "Found cached libcap package" + else + echo "No cache, downloading libcap" + wget https://www.kernel.org/pub/linux/libs/security/linux-privs/libcap2/libcap-${LIBCAP}.tar.gz \ + -O libcap-${LIBCAP}.tar.gz + tar -xvzf libcap-${LIBCAP}.tar.gz + fi + cd libcap-${LIBCAP} + echo "Content of libcap-${LIBCAP}" + ls + echo "Running libcap make install" + make install DESTDIR="$CUR"/dependencies-installed/ IPATH="${CFLAGS} -fPIC -I\$(topdir)/libcap/include/uapi -I\$(topdir)/libcap/include" LDFLAGS=${LDFLAGS} RAISE_SETFCAP=no lib=lib prefix=/ + cd "$CUR" + else + echo "Cached libcap install found." + fi + +} + +function build_libattr { + CUR=`pwd` + if [[ ! -f dependencies-installed/include/attr/libattr.h ]]; then + echo "Cached libattr not found. Installing." + cd dependencies-src + if [[ -f libattr-${LIBATTR}/configure ]]; then + echo "Found cached libattr package" + else + echo "No cache, downloading libattr" + wget http://download.savannah.gnu.org/releases/attr/attr-${LIBATTR}.src.tar.gz \ + -O attr-${LIBATTR}.tar.gz + tar -xvzf attr-${LIBATTR}.tar.gz + fi + cd attr-${LIBATTR} + echo "Content of attr-${LIBATTR}" + ls + echo "Running attr configure" + ./configure --prefix="$CUR"/dependencies-installed/ + echo "Running attr make install" + make install install-dev install-lib + cd $CUR + else + echo "Cached attr install found." + fi +} + +main diff --git a/configure.in b/configure.in index 33c585d1..e8efcae9 100644 --- a/configure.in +++ b/configure.in @@ -85,10 +85,25 @@ AC_SUBST(enable_latex_docs) # Acutally perform the doxygen check BB_ENABLE_DOXYGEN +# Enable capabilities? +AC_DEFUN([BB_LIBCAP], +[ +AC_ARG_ENABLE(libcap, [ --enable-libcap enable privilege dropping with capabilities (no)], [], [enable_libcap=no]) +if test "x$enable_libcap" = xyes; then + AC_CHECK_HEADERS(sys/capability.h) + AC_SEARCH_LIBS([cap_get_proc], [cap], [], [ + AC_MSG_ERROR([unable to find the cap_get_proc function.]) + ]) + AC_DEFINE(USE_LIBCAP,, "Compile with libcap support") +fi +]) +# Actually perform the libcap check +BB_LIBCAP + # Enable cyassl? AC_DEFUN([BB_CYASSL], [ -AC_ARG_ENABLE(cyassl, [ --enable-cyassl enable TLS support for auth server communication (no)], [], [enable_cyassl=no]) +AC_ARG_ENABLE(cyassl, [ --enable-cyassl enable TLS support for auth server communication (no)], [], [enable_cyassl=no]) if test "x$enable_cyassl" = xyes; then AC_CHECK_HEADERS(cyassl/ssl.h) AC_SEARCH_LIBS([CyaSSLv23_client_method], [cyassl wolfssl], [], [ diff --git a/contrib/load-tester/wifidog-mock.conf b/contrib/load-tester/wifidog-mock.conf index 0a488f80..86cf2849 100644 --- a/contrib/load-tester/wifidog-mock.conf +++ b/contrib/load-tester/wifidog-mock.conf @@ -1,3 +1,6 @@ +User nobody +Group nobody + ExternalInterface eth0 GatewayInterface internal0 diff --git a/src/Makefile.am b/src/Makefile.am index fba84cf0..5a9999fd 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -34,6 +34,7 @@ libgateway_a_SOURCES = commandline.c \ httpd_thread.c \ simple_http.c \ pstring.c \ + capabilities.c \ wd_util.c noinst_HEADERS = commandline.h \ @@ -55,6 +56,7 @@ noinst_HEADERS = commandline.h \ httpd_thread.h \ simple_http.h \ pstring.h \ + capabilities.h \ wd_util.h wdctl_LDADD = libgateway.a diff --git a/src/capabilities.c b/src/capabilities.c new file mode 100644 index 00000000..e4a6a0f4 --- /dev/null +++ b/src/capabilities.c @@ -0,0 +1,275 @@ +/* vim: set et sw=4 ts=4 sts=4 : */ +/********************************************************************\ + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * +\********************************************************************/ + +/** @file capabilities.c + @author Copyright (C) 2015 Michael Haas +*/ + +#include "../config.h" + +#ifdef USE_LIBCAP + +#include + +#include +#include +#include +#include +/* FILE and popen */ +#include +/* For strerror */ +#include +/* For exit */ +#include +/* For getpwnam */ +#include +/* For getgrnam */ +#include + +#include "capabilities.h" +#include "debug.h" +#include "safe.h" + +/** + * Switches to non-privileged user and drops unneeded capabilities. + * + * Wifidog does not need to run as root. The only capabilities required + * are: + * - CAP_NET_RAW: get IP addresses from sockets, ICMP ping + * - CAP_NET_ADMIN: modify firewall rules + * + * This function drops all other capabilities. As only the effective + * user id is set, it is theoretically possible for an attacker to + * regain root privileges. + * Any processes started with execve will + * have UID0. This is a convenient side effect to allow for proper + * operation of iptables. + * + * Any error is considered fatal and exit() is called. + * + * @param user Non-privileged user + * @param group Non-privileged group + */ +void +drop_privileges(const char *user, const char *group) +{ + const int num_caps = 2; + /* The capabilities we want. + * CAP_NET_RAW is used for our socket handling. + * CAP_NET_ADMIN is not used directly by iptables which + * is called by Wifidog + */ + cap_value_t cap_values[] = { CAP_NET_RAW, CAP_NET_ADMIN }; + cap_t caps; + int ret = 0; + + debug(LOG_DEBUG, "Entered drop_privileges"); + /* + * We are about to drop our effective UID to a non-privileged user. + * This clears the EFFECTIVE capabilities set, so we later re-enable + * these. We can do that because they are not cleared from + * the PERMITTED set. + * Note: if we used setuid() instead of seteuid(), we would have lost the + * PERMITTED set as well. In this case, we would need to call prctl + * with PR_SET_KEEPCAPS. + */ + set_user_group(user, group); + caps = cap_get_proc(); + if (NULL == caps) { + debug(LOG_ERR, "cap_get_proc failed, exiting!"); + exit(1); + } + debug(LOG_DEBUG, "Current capabilities: %s", cap_to_text(caps, NULL)); + /* Clear all caps and then set the caps we desire */ + cap_clear(caps); + cap_set_flag(caps, CAP_PERMITTED, num_caps, cap_values, CAP_SET); + ret = cap_set_proc(caps); + if (ret == -1) { + debug(LOG_ERR, "Could not set capabilities!"); + exit(1); + } + cap_free(caps); + caps = cap_get_proc(); + if (NULL == caps) { + debug(LOG_ERR, "cap_get_proc failed, exiting!"); + exit(1); + } + debug(LOG_DEBUG, "Dropped caps, now: %s", cap_to_text(caps, NULL)); + cap_free(caps); + caps = cap_get_proc(); + debug(LOG_DEBUG, "Current capabilities: %s", cap_to_text(caps, NULL)); + if (NULL == caps) { + debug(LOG_ERR, "cap_get_proc failed, exiting!"); + exit(1); + } + debug(LOG_DEBUG, "Regaining capabilities."); + /* Re-gain privileges */ + cap_set_flag(caps, CAP_EFFECTIVE, num_caps, cap_values, CAP_SET); + cap_set_flag(caps, CAP_INHERITABLE, num_caps, cap_values, CAP_SET); + ret = cap_set_proc(caps); + if (ret == -1) { + debug(LOG_ERR, "Could not set capabilities!"); + exit(1); + } + cap_free(caps); + caps = cap_get_proc(); + if (NULL == caps) { + debug(LOG_ERR, "cap_get_proc failed, exiting!"); + exit(1); + } + debug(LOG_INFO, "Final capabilities: %s", cap_to_text(caps, NULL)); + cap_free(caps); +} + +/** + * Switches the effective user ID to 0 (root). + * + * If the underlying seteuid call fails, an error message is logged. + * No other error handling is performed. + * + */ +void +switch_to_root() +{ + int ret = 0; + ret = seteuid(0); + /* Not being able to raise privileges is not fatal. */ + if (ret != 0) { + debug(LOG_ERR, "execute: Could not seteuid(0): %s", strerror(errno)); + } + ret = setegid(0); + if (ret != 0) { + debug(LOG_ERR, "execute: Could not setegid(0): %s", strerror(errno)); + } + debug(LOG_DEBUG, "execute: Switched to UID 0!");; +} + +/** + * Switches user and group, typically to a non-privileged user. + * + * If either user or group switching fails, this is considered fatal + * and exit() is called. + * + * @param user name of the user + * @param group name of the group + * + */ +void +set_user_group(const char *user, const char *group) +{ + debug(LOG_DEBUG, "Switching to group %s", group); + struct passwd *pwd = NULL; + struct passwd *pwdresult = NULL; + struct group *grp = NULL; + struct group *grpresult = NULL; + char *buf; + ssize_t bufsize; + int s; + + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + + if (bufsize == -1) { + /* Suggested by man getgrnam_r */ + bufsize = 16384; + } + buf = safe_malloc(bufsize); + + s = getgrnam_r(group, grp, buf, bufsize, &grpresult); + + if (grpresult == NULL) { + if (s == 0) { + debug(LOG_ERR, "GID for group %s not found!", group); + } else { + debug(LOG_ERR, "Failed to look up GID for group %s: %s", group, strerror(errno)); + } + exit(1); + } + + s = getpwnam_r(user, pwd, buf, bufsize, &pwdresult); + if (pwdresult == NULL) { + if (s == 0) { + debug(LOG_ERR, "UID for user %s not found!", user); + } else { + debug(LOG_ERR, "Failed to look up UID for user %s: %s", user, strerror(errno)); + } + exit(1); + } + + set_uid_gid(pwd->pw_uid, grp->gr_gid); + free(pwd); + free(pwdresult); + free(grp); + free(grpresult); + free(buf); + +} + +/** + * Switches user ID and group ID, typically to a non-privileged user. + * + * If either user or group switching fails, this is considered fatal + * and exit() is called. + * + * @param uid the ID of the user + * @param gid the ID of the group + * + */ +void +set_uid_gid(uid_t uid, gid_t gid) +{ + int ret; + ret = setegid(gid); + if (ret != 0) { + debug(LOG_ERR, "Failed to setegid() %s", strerror(errno)); + exit(1); + } + ret = seteuid(uid); + if (ret != 0) { + debug(LOG_ERR, "Failed to seteuid(): %s", strerror(errno)); + exit(1); + } +} + +/** + * Calls popen with root privileges. + * + * This method is a wrapper around popen(). The effective + * user and group IDs of the current process are temporarily set + * to 0 (root) and then reset to the original, typically non-privileged, + * values before returning. + * + * @param command First popen parameter + * @param type Second popen parameter + * @returns File handle pointer returned by popen + */ +FILE * +popen_as_root(const char *command, const char *type) +{ + FILE *p = NULL; + uid_t uid = getuid(); + gid_t gid = getgid(); + switch_to_root(); + p = popen(command, type); + set_uid_gid(uid, gid); + return p; +} + +#endif /* USE_LIBCAP */ diff --git a/src/capabilities.h b/src/capabilities.h new file mode 100644 index 00000000..abc27644 --- /dev/null +++ b/src/capabilities.h @@ -0,0 +1,45 @@ +/* vim: set et sw=4 ts=4 sts=4 : */ +/********************************************************************\ + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License as * + * published by the Free Software Foundation; either version 2 of * + * the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License* + * along with this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + * * +\********************************************************************/ + +/** @file capabilities.h + @author Copyright (C) 2015 Michael Haas +*/ + +#include "../config.h" + +#ifdef USE_LIBCAP + +#ifndef _CAPABILITIES_H_ +#define _CAPABILITIES_H_ + +void drop_privileges(const char *, const char *); + +void switch_to_root(); + +FILE *popen_as_root(const char *, const char *); + +void set_user_group(const char *, const char *); + +void set_uid_gid(uid_t, gid_t); + +#endif /* _CAPABILITIES_H_ */ + +#endif /* USE_LIBCAP */ diff --git a/src/conf.c b/src/conf.c index 4bf862b5..52362622 100644 --- a/src/conf.c +++ b/src/conf.c @@ -103,6 +103,8 @@ typedef enum { oSSLPeerVerification, oSSLCertPath, oSSLAllowedCipherList, + oUser, + oGroup, } OpCodes; /** @internal @@ -149,6 +151,8 @@ static const struct { "sslpeerverification", oSSLPeerVerification}, { "sslcertpath", oSSLCertPath}, { "sslallowedcipherlist", oSSLAllowedCipherList}, { + "user", oUser}, { + "group", oGroup}, { NULL, oBadOption},}; static void config_notnull(const void *, const char *); @@ -204,7 +208,9 @@ config_init(void) config.deltatraffic = DEFAULT_DELTATRAFFIC; config.ssl_cipher_list = NULL; config.arp_table_path = safe_strdup(DEFAULT_ARPTABLE); - + config.user = safe_strdup(DEFAULT_USER); + config.group = safe_strdup(DEFAULT_GROUP); + debugconf.log_stderr = 1; debugconf.debuglevel = DEFAULT_DEBUGLEVEL; debugconf.syslog_facility = DEFAULT_SYSLOG_FACILITY; @@ -791,6 +797,22 @@ config_read(const char *filename) debug(LOG_WARNING, "SSLAllowedCipherList is set but no SSL compiled in. Ignoring!"); #endif break; + case oUser: +#ifndef USE_LIBCAP + debug(LOG_WARNING, "Non-privileged user is set but not compiled with libcap. Bailing out!"); + exit(1); +#endif + free(config.user); + config.user = safe_strdup(p1); + break; + case oGroup: +#ifndef USE_LIBCAP + debug(LOG_WARNING, "Non-privileged group is set but not compiled with libcap. Bailing out!"); + exit(1); +#endif + free(config.group); + config.group = safe_strdup(p1); + break; case oBadOption: /* FALL THROUGH */ default: diff --git a/src/conf.h b/src/conf.h index d145ae34..2b7f3afb 100644 --- a/src/conf.h +++ b/src/conf.h @@ -67,6 +67,8 @@ #define DEFAULT_AUTHSERVSSLPEERVER 1 /* 0 means: Enable peer verification */ #define DEFAULT_DELTATRAFFIC 0 /* 0 means: Enable peer verification */ #define DEFAULT_ARPTABLE "/proc/net/arp" +#define DEFAULT_USER "nobody" /* Unprivileged user, used if compiled with capabilities */ +#define DEFAULT_GROUP "nobody" /* Unprivileged group, used if compiled with capabilities */ /*@}*/ /*@{*/ @@ -193,6 +195,8 @@ typedef struct { t_trusted_mac *trustedmaclist; /**< @brief list of trusted macs */ char *arp_table_path; /**< @brief Path to custom ARP table, formatted like /proc/net/arp */ + char *user; /**< @brief Name of non-privileged user */ + char *group; /**< @brief Name of non-privileged group */ t_popular_server *popular_servers; /**< @brief list of popular servers */ } s_config; diff --git a/src/fw_iptables.c b/src/fw_iptables.c index b3ec1089..6a9c92b5 100644 --- a/src/fw_iptables.c +++ b/src/fw_iptables.c @@ -48,6 +48,9 @@ #include "debug.h" #include "util.h" #include "client_list.h" +#include "capabilities.h" + +#include "../config.h" static int iptables_do_command(const char *format, ...); static char *iptables_compile(const char *, const char *, const t_firewall_rule *); @@ -514,7 +517,13 @@ iptables_fw_destroy_mention(const char *table, const char *chain, const char *me safe_asprintf(&command, "iptables -t %s -L %s -n --line-numbers -v", table, chain); iptables_insert_gateway_id(&command); - if ((p = popen(command, "r"))) { + /* TODO: execute() already has privilege handling code */ +#ifdef USE_LIBCAP + p = popen_as_root(command, "r"); +#else + p = popen(command, "r"); +#endif /* USE_LIBCAP */ + if (p) { /* Skip first 2 lines */ while (!feof(p) && fgetc(p) != '\n') ; while (!feof(p) && fgetc(p) != '\n') ; diff --git a/src/gateway.c b/src/gateway.c index 880cf2e8..821f4e3b 100644 --- a/src/gateway.c +++ b/src/gateway.c @@ -59,6 +59,12 @@ #include "ping_thread.h" #include "httpd_thread.h" #include "util.h" +#include "config.h" + + +#ifdef USE_LIBCAP +#include "capabilities.h" +#endif /* USE LIBCAP */ /** XXX Ugly hack * We need to remember the thread IDs of threads that simulate wait with pthread_cond_timedwait @@ -485,6 +491,7 @@ main_loop(void) /* never reached */ } + /** Reads the configuration file and then starts the main loop */ int gw_main(int argc, char **argv) @@ -499,6 +506,10 @@ gw_main(int argc, char **argv) config_read(config->configfile); config_validate(); +#ifdef USE_LIBCAP + drop_privileges(config->user, config->group); +#endif /* USE LIBCAP */ + /* Initializes the linked list of connected clients */ client_list_init(); diff --git a/src/util.c b/src/util.c index e11736f6..9f572637 100644 --- a/src/util.c +++ b/src/util.c @@ -57,6 +57,7 @@ #include "util.h" #include "debug.h" #include "pstring.h" +#include "capabilities.h" #include "../config.h" @@ -108,6 +109,11 @@ execute(const char *cmd_line, int quiet) /* We don't want to see any errors if quiet flag is on */ if (quiet) close(2); + +#ifdef USE_LIBCAP + /* There is no need to lower privileges again; we're exiting anyways */ + switch_to_root(); +#endif if (execvp(WD_SHELL_PATH, (char *const *)new_argv) == -1) { /* execute the command */ debug(LOG_ERR, "execvp(): %s", strerror(errno)); } else { diff --git a/wifidog.conf b/wifidog.conf index 4912bd2e..45ce55f6 100644 --- a/wifidog.conf +++ b/wifidog.conf @@ -1,6 +1,31 @@ # $Id$ # WiFiDog Configuration file +# Parameter: User +# Default: nobody +# Optional +# +# Set this to the name of a non-privileged user. Wifidog will change to this +# user instead of running as root, providing a slight security benefit. +# +# Only set this option if Wifidog is compiled with --enable-libcap. Setting +# this option otherwise is an error and Wifidog will not start. + +# User nobody + + +# Parameter: Group +# Default: nobody +# Optional +# +# Set this to the name of a non-privileged group. Wifidog will change to this +# group instead of running as root, providing a slight security benefit. +# +# Only set this option if Wifidog is compiled with --enable-libcap. Setting +# this option otherwise is an error and Wifidog will not start. + +# User nobody + # Parameter: GatewayID # Default: default # Optional