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:
@@ -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;
|
||||
}
|
||||
|
Reference in New Issue
Block a user