2
0
mirror of https://github.com/openvswitch/ovs synced 2025-09-02 07:15:17 +00:00

lib/daemon: support --user option for all OVS daemon

OVS daemons can now support --user option to run as a non-root
user with less privileges.

See the manpage patch for more descriptions.

Signed-off-by: Andy Zhou <azhou@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
This commit is contained in:
Andy Zhou
2015-09-11 11:26:39 -07:00
parent 1bbebfb7af
commit e91b927d89
17 changed files with 433 additions and 27 deletions

View File

@@ -19,6 +19,8 @@
#include "daemon-private.h"
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
@@ -26,6 +28,9 @@
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#if HAVE_LIBCAPNG
#include <cap-ng.h>
#endif
#include "command-line.h"
#include "fatal-signal.h"
#include "dirs.h"
@@ -39,6 +44,18 @@
VLOG_DEFINE_THIS_MODULE(daemon_unix);
#ifdef __linux__
#define LINUX 1
#else
#define LINUX 0
#endif
#if HAVE_LIBCAPNG
#define LIBCAPNG 1
#else
#define LIBCAPNG 0
#endif
/* --detach: Should we run in the background? */
bool detach; /* Was --detach specified? */
static bool detached; /* Have we already detached? */
@@ -64,6 +81,15 @@ static int daemonize_fd = -1;
* it dies due to an error signal? */
static bool monitor;
/* --user: Only root can use this option. Switch to new uid:gid after
* initially running as root. */
static bool switch_user = false;
static bool non_root_user = false;
static uid_t uid;
static gid_t gid;
static char *user = NULL;
static void daemon_become_new_user__(bool access_datapath);
static void check_already_running(void);
static int lock_pidfile(FILE *, int command);
static pid_t fork_and_clean_up(void);
@@ -409,11 +435,21 @@ monitor_daemon(pid_t daemon_pid)
* daemon_complete()) or that it failed to start up (by exiting with a nonzero
* exit code). */
void
daemonize_start(void)
daemonize_start(bool access_datapath)
{
assert_single_threaded();
daemonize_fd = -1;
if (switch_user) {
daemon_become_new_user__(access_datapath);
switch_user = false;
}
/* If --user is specified, make sure user switch has completed by now. */
if (non_root_user) {
ovs_assert(geteuid() && getuid());
}
if (detach) {
pid_t pid;
@@ -684,3 +720,326 @@ should_service_stop(void)
{
return false;
}
static bool
gid_matches(gid_t expected, gid_t value)
{
return expected == -1 || expected == value;
}
static bool
gid_verify(gid_t real, gid_t effective, gid_t saved)
{
gid_t r, e, s;
return (getresgid(&r, &e, &s) == 0 &&
gid_matches(real, r) &&
gid_matches(effective, e) &&
gid_matches(saved, s));
}
static void
daemon_switch_group(gid_t real, gid_t effective,
gid_t saved)
{
if ((setresgid(real, effective, saved) == -1) ||
!gid_verify(real, effective, saved)) {
VLOG_FATAL("%s: fail to switch group to gid as %d, aborting",
pidfile, gid);
}
}
static bool
uid_matches(uid_t expected, uid_t value)
{
return expected == -1 || expected == value;
}
static bool
uid_verify(const uid_t real, const uid_t effective, const uid_t saved)
{
uid_t r, e, s;
return (getresuid(&r, &e, &s) == 0 &&
uid_matches(real, r) &&
uid_matches(effective, e) &&
uid_matches(saved, s));
}
static void
daemon_switch_user(const uid_t real, const uid_t effective, const uid_t saved,
const char *user)
{
if ((setresuid(real, effective, saved) == -1) ||
!uid_verify(real, effective, saved)) {
VLOG_FATAL("%s: fail to switch user to %s, aborting",
pidfile, user);
}
}
/* Use portable Unix APIs to switch uid:gid, when datapath
* access is not required. On Linux systems, all capabilities
* will be dropped. */
static void
daemon_become_new_user_unix(void)
{
/* "Setuid Demystified" by Hao Chen, etc outlines some caveats of
* around unix system call setuid() and friends. This implementation
* mostly follow the advice given by the paper. The paper is
* published in 2002, so things could have changed. */
/* Change both real and effective uid and gid will permanently
* drop the process' privilege. "Setuid Demystified" suggested
* that calling getuid() after each setuid() call to verify they
* are actually set, because checking return code alone is not
* sufficient. */
daemon_switch_group(gid, gid, gid);
if (user && initgroups(user, gid) == -1) {
VLOG_FATAL("%s: fail to add supplementary group gid %d, "
"aborting", pidfile, gid);
}
daemon_switch_user(uid, uid, uid, user);
}
/* Linux specific implementation of daemon_become_new_user()
* using libcap-ng. */
#if defined __linux__ && HAVE_LIBCAPNG
static void
daemon_become_new_user_linux(bool access_datapath)
{
int ret;
ret = capng_get_caps_process();
if (!ret) {
if (capng_have_capabilities(CAPNG_SELECT_CAPS) > CAPNG_NONE) {
const capng_type_t cap_sets = CAPNG_EFFECTIVE|CAPNG_PERMITTED;
capng_clear(CAPNG_SELECT_BOTH);
ret = capng_update(CAPNG_ADD, cap_sets, CAP_IPC_LOCK)
|| capng_update(CAPNG_ADD, cap_sets, CAP_NET_BIND_SERVICE);
if (access_datapath && !ret) {
ret = capng_update(CAPNG_ADD, cap_sets, CAP_NET_ADMIN)
|| capng_update(CAPNG_ADD, cap_sets, CAP_NET_RAW);
}
} else {
ret = -1;
}
}
if (!ret) {
/* CAPNG_INIT_SUPP_GRP will be a better choice than
* CAPNG_DROP_SUPP_GRP. However this enum value is only defined
* with libcap-ng higher than version 0.7.4, which is not wildly
* available on many Linux distributions yet. Taking a more
* conservative approach to make sure OVS behaves consistently.
*
* XXX We may change this for future OVS releases.
*/
ret = capng_change_id(uid, gid, CAPNG_DROP_SUPP_GRP
| CAPNG_CLEAR_BOUNDING);
}
if (ret) {
VLOG_FATAL("%s: libcap-ng fail to switch to user and group "
"%d:%d, aborting", pidfile, uid, gid);
}
}
#endif
static void
daemon_become_new_user__(bool access_datapath)
{
if (LINUX) {
if (LIBCAPNG) {
daemon_become_new_user_linux(access_datapath);
} else {
VLOG_FATAL("%s: fail to downgrade user using libcap-ng. "
"(libcap-ng is not configured at compile time), "
"aborting.", pidfile);
}
} else {
daemon_become_new_user_unix();
}
}
/* Noramlly, user switch is embedded within daemonize_start().
* However, there in case the user switch needs to be done
* before daemonize_start(), the following API can be used. */
void
daemon_become_new_user(bool access_datapath)
{
assert_single_threaded();
if (switch_user) {
daemon_become_new_user__(access_datapath);
/* Make sure daemonize_start() will not switch
* user again. */
switch_user = false;
}
}
/* Return the maximun suggested buffer size for both getpwname_r()
* and getgrnam_r().
*
* This size may still not be big enough. in case getpwname_r()
* and friends return ERANGE, a larger buffer should be supplied to
* retry. (The man page did not specify the max size to stop at, we
* will keep trying with doubling the buffer size for each round until
* the size wrapps around size_t. */
static size_t
get_sysconf_buffer_size(void)
{
size_t bufsize, pwd_bs = 0, grp_bs = 0;
const size_t default_bufsize = 1024;
errno = 0;
if ((pwd_bs = sysconf(_SC_GETPW_R_SIZE_MAX)) == -1) {
if (errno) {
VLOG_FATAL("%s: Read initial passwordd struct size "
"failed (%s), aborting. ", pidfile,
ovs_strerror(errno));
}
}
if ((grp_bs = sysconf(_SC_GETGR_R_SIZE_MAX)) == -1) {
if (errno) {
VLOG_FATAL("%s: Read initial group struct size "
"failed (%s), aborting. ", pidfile,
ovs_strerror(errno));
}
}
bufsize = MAX(pwd_bs, grp_bs);
return bufsize ? bufsize : default_bufsize;
}
/* Try to double the size of '*buf', return true
* if successful, and '*sizep' will be updated with
* the new size. Otherwise, return false. */
static bool
enlarge_buffer(char **buf, size_t *sizep)
{
size_t newsize = *sizep * 2;
if (newsize > *sizep) {
*buf = xrealloc(*buf, newsize);
*sizep = newsize;
return true;
}
return false;
}
/* Parse and sanity check user_spec.
*
* If successful, set global variables 'uid' and 'gid'
* with the parsed results. Global variable 'user'
* will be pointing to a string that stores the name
* of the user to be switched into.
*
* Also set 'switch_to_new_user' to true, The actual
* user switching is done as soon as daemonize_start()
* is called. I/O access before calling daemonize_start()
* will still be with root's credential. */
void
daemon_set_new_user(const char *user_spec)
{
char *pos = strchr(user_spec, ':');
size_t init_bufsize, bufsize;
init_bufsize = get_sysconf_buffer_size();
uid = getuid();
gid = getgid();
if (geteuid() || uid) {
VLOG_FATAL("%s: only root can use --user option", pidfile);
}
user_spec += strspn(user_spec, " \t\r\n");
size_t len = pos ? pos - user_spec : strlen(user_spec);
char *buf;
struct passwd pwd, *res;
int e;
bufsize = init_bufsize;
buf = xmalloc(bufsize);
if (len) {
user = xmemdup0(user_spec, len);
while ((e = getpwnam_r(user, &pwd, buf, bufsize, &res)) == ERANGE) {
if (!enlarge_buffer(&buf, &bufsize)) {
break;
}
}
if (e != 0) {
VLOG_FATAL("%s: Failed to retrive user %s's uid (%s), aborting.",
pidfile, user, ovs_strerror(e));
}
} else {
/* User name is not specified, use current user. */
while ((e = getpwuid_r(uid, &pwd, buf, bufsize, &res)) == ERANGE) {
if (!enlarge_buffer(&buf, &bufsize)) {
break;
}
}
if (e != 0) {
VLOG_FATAL("%s: Failed to retrive current user's name "
"(%s), aborting.", pidfile, ovs_strerror(e));
}
user = xstrdup(pwd.pw_name);
}
uid = pwd.pw_uid;
gid = pwd.pw_gid;
free(buf);
if (pos) {
char *grpstr = pos + 1;
grpstr += strspn(grpstr, " \t\r\n");
if (*grpstr) {
struct group grp, *res;
bufsize = init_bufsize;
buf = xmalloc(bufsize);
while ((e = getgrnam_r(grpstr, &grp, buf, bufsize, &res))
== ERANGE) {
if (!enlarge_buffer(&buf, &bufsize)) {
break;
}
}
if (e) {
VLOG_FATAL("%s: Failed to get group entry for %s, "
"(%s), aborting.", pidfile, grpstr,
ovs_strerror(e));
}
if (gid != grp.gr_gid) {
char **mem;
for (mem = grp.gr_mem; *mem; ++mem) {
if (!strcmp(*mem, user)) {
break;
}
}
if (!*mem) {
VLOG_FATAL("%s: Invalid --user option %s (user %s is "
"not in group %s), aborting.", pidfile,
user_spec, user, grpstr);
}
gid = grp.gr_gid;
}
free(buf);
}
}
switch_user = non_root_user = true;
}