-
Notifications
You must be signed in to change notification settings - Fork 317
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Capabilities #185
base: master
Are you sure you want to change the base?
RFC: Capabilities #185
Changes from 23 commits
db3d867
6a2e306
5f71eb7
9326edf
d280fb5
78b537f
93c94db
6f6f526
c0ef73c
6c18daf
fbf3556
2a54bdf
c0d284c
bd19dc8
1d324c5
2131aae
05af333
6302dda
a0a78a8
8ad25bc
6f6cdac
be3e019
ef379f3
79e60f1
9af8215
05c04c7
9c26326
58984c3
b750717
f507078
5dee3cf
3e26b42
3cc6aca
142d289
426a1c1
fcde454
b471afb
e445b38
87c5572
56c8fd2
c78e25c
d03694d
3031faa
a83394c
070435a
42e5341
897a695
7a24ddb
d64cd9f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
User nobody | ||
Group nobody | ||
|
||
ExternalInterface eth0 | ||
GatewayInterface internal0 | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
/* 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 [email protected] * | ||
* * | ||
\********************************************************************/ | ||
|
||
/** @file capabilities.c | ||
@author Copyright (C) 2015 Michael Haas <[email protected]> | ||
*/ | ||
|
||
#include "../config.h" | ||
|
||
#ifdef USE_LIBCAP | ||
|
||
#include <errno.h> | ||
|
||
#include <syslog.h> | ||
#include <sys/capability.h> | ||
#include <sys/prctl.h> | ||
#include <unistd.h> | ||
/* FILE and popen */ | ||
#include <stdio.h> | ||
/* For strerror */ | ||
#include <string.h> | ||
/* For exit */ | ||
#include <stdlib.h> | ||
/* For getpwnam */ | ||
#include <pwd.h> | ||
/* For getgrnam */ | ||
#include <grp.h> | ||
|
||
#include "capabilities.h" | ||
#include "debug.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) | ||
{ | ||
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 | ||
* 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); | ||
/* 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 | ||
*/ | ||
const int num_caps = 2; | ||
cap_value_t cap_values[] = { CAP_NET_RAW, CAP_NET_ADMIN }; | ||
cap_t caps; | ||
|
||
caps = cap_get_proc(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This allocates ram therefore could return a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixes. |
||
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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also not There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed all occurences. |
||
if (ret == -1) { | ||
debug(LOG_ERR, "Could not set capabilities!"); | ||
exit(1); | ||
} | ||
caps = cap_get_proc(); | ||
debug(LOG_DEBUG, "Dropped caps, now: %s", cap_to_text(caps, NULL)); | ||
cap_free(caps); | ||
caps = cap_get_proc(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a |
||
debug(LOG_DEBUG, "Current capabilities: %s", cap_to_text(caps, NULL)); | ||
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); | ||
/* | ||
* Note that we keep CAP_INHERITABLE empty. In theory, CAP_INHERITABLE | ||
* would be useful to execve iptables as non-root. In practice, Wifidog | ||
* often runs on embedded systems (OpenWrt) where the required file-based | ||
* capabilities are not available as the underlying file system does not | ||
* support extended attributes. | ||
* | ||
* The linux capabilities implementation requires that the executable is | ||
* specifically marked as being able to inherit capabilities from a calling | ||
* process. This can be done by setting the Inheritable+Effective file | ||
* capabilities on the executable. Alas, it's not relevant here. | ||
* | ||
* This is also the main reason why we only seteuid() instead of setuid(): | ||
* When an executable is called as root (real UID == 0), the INHERITABLE | ||
* and PERMITTED file capability sets are implicitly marked as enabled. | ||
*/ | ||
ret = cap_set_proc(caps); | ||
if (ret == -1) { | ||
debug(LOG_ERR, "Could not set capabilities!"); | ||
exit(1); | ||
} | ||
caps = cap_get_proc(); | ||
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); | ||
/* don't free grp, see getpwnam() for details */ | ||
struct group *grp = getgrnam(group); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I might be over cautious here as this might only ever be called from gw_main before threads are spawned... But I don't like future land mines either. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
if (NULL == grp) { | ||
debug(LOG_ERR, "Failed to look up GID for group %s: %s", group, strerror(errno)); | ||
exit(1); | ||
} | ||
gid_t gid = grp->gr_gid; | ||
struct passwd *pwd = getpwnam(user); | ||
if (NULL == pwd) { | ||
debug(LOG_ERR, "Failed to look up UID for user %s", user); | ||
exit(1); | ||
} | ||
uid_t uid = pwd->pw_uid; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why you need this line or the similar line for set_uid_gid(pwd->pw_uid, grp->gr_gid); Why force the C compiler to allocate a variable in the stack? (Actually at -O > 1, gcc will just drop the variable altogether.) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
set_uid_gid(uid, gid); | ||
|
||
} | ||
|
||
/** | ||
* 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 */ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
/* 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 [email protected] * | ||
* * | ||
\********************************************************************/ | ||
|
||
/** @file capabilities.h | ||
@author Copyright (C) 2015 Michael Haas <[email protected]> | ||
*/ | ||
|
||
#include "../config.h" | ||
|
||
#ifdef USE_LIBCAP | ||
|
||
#ifndef _CAPABILITIES_H_ | ||
#define _CAPABILITIES_H_ | ||
|
||
void | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Declarations in .h should be all on one line. In .c, they should be broken up with return on the previous line. Exactly the reverse of the capabilities.[ch] There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed. |
||
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 */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Declaring variables all over is a bit of bad form in C, usually you declare them at start of scope (i.e.: after an
{
), this is however legal as of C99. I'm just a crufty curmudgeon. It's probably safe to ignore me ;-)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.