In the interests of giving a 'heads-up' I'm posting this patch. It
implements least-privilege on Solaris. The basic idea is that all
libvirt clients are forced to go through libvirtd, which verifies a
particular privilege. virtd itself runs with enough privilege to
interact with Xen.
This patch is:
- not to be applied :)
- only against 0.4.0
- subject to further change
- not yet reviewed, not even by myself (properly)
Nonetheless, comments are more than welcome.
regards
john
--- libvirt-0.4.0/qemud/remote.c 2007-12-12 05:30:49.000000000 -0800
+++ libvirt-new/qemud/remote.c 2008-04-10 12:52:18.059618661 -0700
@@ -434,6 +434,15 @@ remoteDispatchOpen (struct qemud_server
flags = args->flags;
if (client->readonly) flags |= VIR_CONNECT_RO;
+#ifdef __sun
+ /*
+ * On Solaris, all clients are forced to go via virtd. As a result,
+ * virtd must indicate it really does want to connect to the
+ * hypervisor.
+ */
+ name = "xen:///";
+#endif
+
client->conn =
flags & VIR_CONNECT_RO
? virConnectOpenReadOnly (name)
--- libvirt-0.4.0/src/libvirt.c 2007-12-17 13:51:09.000000000 -0800
+++ libvirt-new/src/libvirt.c 2008-04-16 08:46:28.767087199 -0700
@@ -34,6 +34,7 @@
#include "uuid.h"
#include "test.h"
+#include "xen_internal.h"
#include "xen_unified.h"
#include "remote_internal.h"
#include "qemu_driver.h"
@@ -202,8 +203,16 @@ virInitialize(void)
if (qemudRegister() == -1) return -1;
#endif
#ifdef WITH_XEN
+ /*
+ * On Solaris, only initialize Xen if we're libvirtd.
+ */
+#ifdef __sun
+ if (geteuid() != 0 && xenHavePrivilege() &&
+ xenUnifiedRegister () == -1) return -1;
+#else
if (xenUnifiedRegister () == -1) return -1;
#endif
+#endif
#ifdef WITH_OPENVZ
if (openvzRegister() == -1) return -1;
#endif
@@ -525,6 +534,16 @@ do_open (const char *name,
if (STREQ (name, "xen://"))
name = "xen:///";
+#ifdef __sun
+ /*
+ * If we're not libvirtd, force us to go via the daemon.
+ */
+ if (geteuid() == 0 || !xenHavePrivilege())
+ name = "remote+unix:///";
+ else if (STREQ (name, "xen:///") && xenUnifiedRegister () == -1)
+ return NULL;
+#endif
+
if (!initialized)
if (virInitialize() < 0)
return NULL;
--- libvirt-0.4.0/qemud/qemud.c 2007-12-12 05:30:49.000000000 -0800
+++ libvirt-new/qemud/qemud.c 2008-04-17 17:16:47.075258251 -0700
@@ -60,6 +60,25 @@
#include "mdns.h"
#endif
+#ifdef __sun
+#include <ucred.h>
+#include <priv.h>
+
+#ifndef PRIV_VIRT_MANAGE
+#define PRIV_VIRT_MANAGE ((const char *)"virt_manage")
+#endif
+
+#ifndef PRIV_XVM_CONTROL
+#define PRIV_XVM_CONTROL ((const char *)"xvm_control")
+#endif
+
+#define PU_RESETGROUPS 0x0001 /* Remove supplemental groups */
+#define PU_CLEARLIMITSET 0x0008 /* L=0 */
+
+extern int __init_daemon_priv(int, uid_t, gid_t, ...);
+
+#endif
+
static int godaemon = 0; /* -d: Be a daemon */
static int verbose = 0; /* -v: Verbose mode */
static int timeout = -1; /* -t: Shutdown timeout */
@@ -668,11 +687,13 @@ static int qemudInitPaths(struct qemud_s
unlink(sockname);
+#ifndef __sun
if (snprintf (roSockname, maxlen, "%s/run/libvirt/libvirt-sock-ro",
LOCAL_STATE_DIR) >= maxlen)
goto snprintf_error;
unlink(roSockname);
+#endif
if (snprintf(server->logDir, PATH_MAX, "%s/log/libvirt/",
LOCAL_STATE_DIR) >= PATH_MAX)
goto snprintf_error;
@@ -1033,6 +1054,29 @@ static int qemudDispatchServer(struct qe
return -1;
}
+#ifdef __sun
+ {
+ ucred_t *ucred = NULL;
+ const priv_set_t *privs;
+
+ if (getpeerucred (fd, &ucred) == -1 ||
+ (privs = ucred_getprivset (ucred, PRIV_EFFECTIVE)) == NULL) {
+ if (ucred != NULL)
+ ucred_free (ucred);
+ close (fd);
+ return -1;
+ }
+
+ if (!priv_ismember (privs, PRIV_VIRT_MANAGE)) {
+ ucred_free (ucred);
+ close (fd);
+ return -1;
+ }
+
+ ucred_free (ucred);
+ }
+#endif /* __sun */
+
/* Disable Nagle. Unix sockets will ignore this. */
setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, (void *)&no_slow_start,
sizeof no_slow_start);
@@ -1864,6 +1908,10 @@ remoteReadConfigFile (struct qemud_serve
if (auth_unix_rw == REMOTE_AUTH_POLKIT)
unix_sock_rw_mask = 0777;
#endif
+#ifdef __sun
+ unix_sock_rw_mask = 0666;
+#endif
+
if (remoteConfigGetAuth(conf, "auth_unix_ro", &auth_unix_ro, filename)
< 0)
return -1;
if (remoteConfigGetAuth(conf, "auth_tcp", &auth_tcp, filename) < 0)
@@ -1955,6 +2003,32 @@ remoteReadConfigFile (struct qemud_serve
return -1;
}
+#ifdef __sun
+static void
+qemudSetupPrivs (struct qemud_server *server)
+{
+ chown ("/var/run/libvirt", 60, 60);
+ chmod ("/var/run/libvirt", 0755);
+ chown ("/var/run/libvirt/libvirt-sock", 60, 60);
+ chmod ("/var/run/libvirt/libvirt-sock", 0666);
+ chown (server->logDir, 60, 60);
+
+ if (__init_daemon_priv (PU_RESETGROUPS | PU_CLEARLIMITSET,
+ 60, 60, PRIV_XVM_CONTROL, NULL)) {
+ fprintf (stderr, "additional privileges are required\n");
+ exit (1);
+ }
+
+ if (priv_set (PRIV_OFF, PRIV_ALLSETS, PRIV_FILE_LINK_ANY, PRIV_PROC_INFO,
+ PRIV_PROC_SESSION, PRIV_PROC_EXEC, PRIV_PROC_FORK, NULL)) {
+ fprintf (stderr, "failed to set reduced privileges\n");
+ exit (1);
+ }
+}
+#else
+#define qemudSetupPrivs
+#endif
+
/* Print command-line usage. */
static void
usage (const char *argv0)
@@ -2139,6 +2213,8 @@ int main(int argc, char **argv) {
goto error2;
}
+ qemudSetupPrivs(server);
+
qemudRunLoop(server);
qemudCleanup(server);
--- libvirt-0.4.0/src/xs_internal.c 2007-12-14 07:33:11.000000000 -0800
+++ libvirt-new/src/xs_internal.c 2008-04-10 09:54:38.676077954 -0700
@@ -31,7 +31,7 @@
#include "driver.h"
#include "xen_unified.h"
#include "xs_internal.h"
-#include "xen_internal.h" /* for xenHypervisorCheckID */
+#include "xen_internal.h"
#ifdef __linux__
#define XEN_HYPERVISOR_SOCKET "/proc/xen/privcmd"
@@ -344,11 +344,11 @@ xenStoreOpen(virConnectPtr conn,
if (priv->xshandle == NULL) {
/*
- * not being able to connect via the socket as a normal user
- * is rather normal, this should fallback to the proxy (or
- * remote) mechanism.
+ * not being able to connect via the socket as an unprivileged
+ * user is rather normal, this should fallback to the proxy (or
+ * remote) mechanism.
*/
- if (getuid() == 0) {
+ if (!xenHavePrivilege()) {
virXenStoreError(NULL, VIR_ERR_NO_XEN,
_("failed to connect to Xen Store"));
}
--- libvirt-0.4.0/src/xen_internal.c 2007-12-12 05:30:49.000000000 -0800
+++ libvirt-new/src/xen_internal.c 2008-04-10 10:28:41.363704244 -0700
@@ -28,6 +28,15 @@
#include <errno.h>
#include <sys/utsname.h>
+#ifdef __sun
+#include <priv.h>
+
+#ifndef PRIV_XVM_CONTROL
+#define PRIV_XVM_CONTROL ((const char *)"xvm_control")
+#endif
+
+#endif
+
#include "xs_internal.h"
#include "stats_linux.h"
#include "xend_internal.h"
@@ -3259,6 +3268,21 @@ xenHypervisorGetVcpuMax(virDomainPtr dom
return maxcpu;
}
+/**
+ * xenHavePrivilege()
+ *
+ * Return true if the current process should be able to connect to Xen.
+ */
+int
+xenHavePrivilege()
+{
+#ifdef __sun
+ return priv_ineffect (PRIV_XVM_CONTROL);
+#else
+ return getuid () == 0;
+#endif
+}
+
#endif /* WITH_XEN */
/*
* vim: set tabstop=4:
--- libvirt-0.4.0/src/xen_internal.h 2007-12-05 12:33:02.000000000 -0800
+++ libvirt-new/src/xen_internal.h 2008-04-10 09:34:16.379186656 -0700
@@ -98,6 +98,9 @@ int xenHypervisorNodeGetCellsFreeMemory(
unsigned long long *freeMems,
int startCell,
int maxCells);
+
+int xenHavePrivilege(void);
+
#ifdef __cplusplus
}
#endif
--- libvirt-0.4.0/src/xen_unified.c 2007-12-12 05:30:49.000000000 -0800
+++ libvirt-new/src/xen_unified.c 2008-04-10 15:10:53.653298774 -0700
@@ -266,8 +266,8 @@ xenUnifiedOpen (virConnectPtr conn, xmlU
priv->xendConfigVersion > 2)
continue;
- /* Ignore proxy for root */
- if (i == XEN_UNIFIED_PROXY_OFFSET && getuid() == 0)
+ /* Ignore proxy if we have privilege */
+ if (i == XEN_UNIFIED_PROXY_OFFSET && xenHavePrivilege())
continue;
if (drivers[i]->open) {
@@ -282,10 +282,10 @@ xenUnifiedOpen (virConnectPtr conn, xmlU
#endif
}
- /* If as root, then all drivers must succeed.
- If non-root, then only proxy must succeed */
+ /* If privileged, then all drivers must succeed.
+ If unprivileged, then only proxy must succeed */
if (!priv->opened[i] &&
- (getuid() == 0 || i == XEN_UNIFIED_PROXY_OFFSET)) {
+ (xenHavePrivilege() || i == XEN_UNIFIED_PROXY_OFFSET)) {
for (j = 0; j < i; ++j)
if (priv->opened[j]) drivers[j]->close (conn);
free (priv);
@@ -1266,6 +1266,12 @@ static virDriver xenUnifiedDriver = {
int
xenUnifiedRegister (void)
{
+ static int driver_priority = 0;
+ static int xen_initialized = 0;
+
+ if (xen_initialized)
+ return driver_priority;
+
/* Ignore failures here. */
(void) xenHypervisorInit ();
(void) xenProxyInit ();
@@ -1273,7 +1279,9 @@ xenUnifiedRegister (void)
(void) xenStoreInit ();
(void) xenXMInit ();
- return virRegisterDriver (&xenUnifiedDriver);
+ driver_priority = virRegisterDriver (&xenUnifiedDriver);
+ xen_initialized = 1;
+ return driver_priority;
}
#endif /* WITH_XEN */
--- libvirt-0.4.0/src/xend_internal.c 2007-12-17 15:05:27.000000000 -0800
+++ libvirt-new/src/xend_internal.c 2008-04-10 09:45:10.262989682 -0700
@@ -42,7 +42,7 @@
#include "uuid.h"
#include "xen_unified.h"
#include "xend_internal.h"
-#include "xen_internal.h" /* for DOM0_INTERFACE_VERSION */
+#include "xen_internal.h"
#include "xs_internal.h" /* To extract VNC port & Serial console TTY */
/* required for cpumap_t */
@@ -235,7 +235,7 @@ do_connect(virConnectPtr xend)
* is rather normal, this should fallback to the proxy (or
* remote) mechanism.
*/
- if ((getuid() == 0) || (xend->flags & VIR_CONNECT_RO)) {
+ if (xenHavePrivilege() || (xend->flags & VIR_CONNECT_RO)) {
virXendError(xend, VIR_ERR_INTERNAL_ERROR,
"failed to connect to xend");
}
--- libvirt-0.4.0/src/virsh.c 2007-12-07 07:00:48.000000000 -0800
+++ libvirt-new/src/virsh.c 2008-04-16 08:44:35.668718168 -0700
@@ -36,6 +36,7 @@
#include <sys/stat.h>
#include <inttypes.h>
#include <test.h>
+#include <signal.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
@@ -50,6 +51,7 @@
#include "console.h"
static char *progname;
+static int sigpipe;
#ifndef TRUE
#define TRUE 1
@@ -202,9 +204,6 @@ typedef struct __vshControl {
virConnectPtr conn; /* connection to hypervisor (MAY BE NULL) */
vshCmd *cmd; /* the current command */
char *cmdstr; /* string with command */
-#ifndef __MINGW32__
- uid_t uid; /* process owner */
-#endif /* __MINGW32__ */
int imode; /* interactive mode? */
int quiet; /* quiet mode */
int debug; /* print debug messages? */
@@ -464,12 +463,8 @@ static vshCmdOptDef opts_console[] = {
static int
cmdConsole(vshControl * ctl, vshCmd * cmd)
{
- xmlDocPtr xml = NULL;
- xmlXPathObjectPtr obj = NULL;
- xmlXPathContextPtr ctxt = NULL;
virDomainPtr dom;
int ret = FALSE;
- char *doc;
if (!vshConnectionUsability(ctl, ctl->conn, TRUE))
return FALSE;
@@ -477,37 +472,16 @@ cmdConsole(vshControl * ctl, vshCmd * cm
if (!(dom = vshCommandOptDomain(ctl, cmd, "domain", NULL)))
return FALSE;
- doc = virDomainGetXMLDesc(dom, 0);
- if (!doc)
- goto cleanup;
-
- xml = xmlReadDoc((const xmlChar *) doc, "domain.xml", NULL,
- XML_PARSE_NOENT | XML_PARSE_NONET |
- XML_PARSE_NOWARNING);
- free(doc);
- if (!xml)
- goto cleanup;
- ctxt = xmlXPathNewContext(xml);
- if (!ctxt)
- goto cleanup;
+ ret = vshConsole(dom);
- obj = xmlXPathEval(BAD_CAST "string(/domain/devices/console/@tty)", ctxt);
- if ((obj != NULL) && ((obj->type == XPATH_STRING) &&
- (obj->stringval != NULL) && (obj->stringval[0] !=
0))) {
- if (vshRunConsole((const char *)obj->stringval) == 0)
- ret = TRUE;
- } else {
+ if (ret == 0) {
vshPrintExtra(ctl, _("No console available for domain\n"));
+ } else if (ret == -1) {
+ fprintf(stderr, _("unable to open console: %s\n"),
+ strerror(errno));
}
- xmlXPathFreeObject(obj);
- cleanup:
- if (ctxt)
- xmlXPathFreeContext(ctxt);
- if (xml)
- xmlFreeDoc(xml);
- virDomainFree(dom);
- return ret;
+ return ret == 1;
}
#else /* __MINGW32__ */
@@ -4523,22 +4497,11 @@ vshInit(vshControl * ctl)
if (ctl->conn)
return FALSE;
-#ifndef __MINGW32__
- ctl->uid = getuid();
-#endif
-
vshOpenLogFile(ctl);
/* set up the library error handler */
virSetErrorFunc(NULL, virshErrorHandler);
-#ifndef __MINGW32__
- /* Force a non-root, Xen connection to readonly */
- if ((ctl->name == NULL ||
- !strcasecmp(ctl->name, "xen")) && ctl->uid != 0)
- ctl->readonly = 1;
-#endif
-
ctl->conn = virConnectOpenAuth(ctl->name,
virConnectAuthPtrDefault,
ctl->readonly ? VIR_CONNECT_RO : 0);
@@ -5021,12 +4984,21 @@ vshParseArgv(vshControl * ctl, int argc,
return TRUE;
}
-int
+static void sigpipe_handler(int sig)
+{
+ sigpipe = 1;
+ /*
+ * Force readline() to exit.
+ */
+ close(STDIN_FILENO);
+}
+
main(int argc, char **argv)
{
vshControl _ctl, *ctl = &_ctl;
char *defaultConn;
int ret = TRUE;
+ struct sigaction sig_action;
if (!setlocale(LC_ALL, "")) {
perror("setlocale");
@@ -5059,6 +5031,12 @@ main(int argc, char **argv)
exit(EXIT_FAILURE);
}
+ sig_action.sa_handler = sigpipe_handler;
+ sig_action.sa_flags = 0;
+ sigemptyset(&sig_action.sa_mask);
+
+ sigaction(SIGPIPE, &sig_action, NULL);
+
if (!vshInit(ctl)) {
vshDeinit(ctl);
exit(EXIT_FAILURE);
@@ -5098,6 +5076,13 @@ main(int argc, char **argv)
fputc('\n', stdout); /* line break after alone prompt */
}
+ /*
+ * If the connection over a socket failed abruptly, it's probably
+ * due to not having the right privileges.
+ */
+ if (sigpipe)
+ vshError(ctl, TRUE, _("failed to connect (insufficient
privileges?)"));
+
vshDeinit(ctl);
exit(ret ? EXIT_SUCCESS : EXIT_FAILURE);
}
--- libvirt-0.4.0/src/console.c 2007-12-07 07:00:48.000000000 -0800
+++ libvirt-new/src/console.c 2008-04-16 16:34:15.533322609 -0700
@@ -34,6 +34,11 @@
#include <errno.h>
#include <unistd.h>
#include <signal.h>
+#include <stdlib.h>
+
+#include <libxml/parser.h>
+#include <libxml/tree.h>
+#include <libxml/xpath.h>
#include "console.h"
#include "internal.h"
@@ -59,8 +64,10 @@ cfmakeraw (struct termios *attr)
}
#endif /* !HAVE_CFMAKERAW */
-int vshRunConsole(const char *tty) {
- int ttyfd, ret = -1;
+static int
+vshDirectConsole(int ttyfd)
+{
+ int ret = -1;
struct termios ttyattr, rawattr;
void (*old_sigquit)(int);
void (*old_sigterm)(int);
@@ -68,14 +75,6 @@ int vshRunConsole(const char *tty) {
void (*old_sighup)(int);
void (*old_sigpipe)(int);
-
- /* We do not want this to become the controlling TTY */
- if ((ttyfd = open(tty, O_NOCTTY | O_RDWR)) < 0) {
- fprintf(stderr, _("unable to open tty %s: %s\n"),
- tty, strerror(errno));
- return -1;
- }
-
/* Put STDIN into raw mode so that stuff typed
does not echo to the screen (the TTY reads will
result in it being echoed back already), and
@@ -174,7 +173,7 @@ int vshRunConsole(const char *tty) {
}
}
done:
- ret = 0;
+ ret = 1;
cleanup:
@@ -195,6 +194,106 @@ int vshRunConsole(const char *tty) {
return ret;
}
+/*
+ * Attempt to access the domain via 'xenconsole', which may have
+ * additional privilege to reach the console.
+ */
+static int
+vshXenConsole(int id)
+{
+ char arg[100];
+ char *argv[3];
+ pid_t pid;
+
+ if ((pid = fork()) < 0) {
+ return -1;
+ } else if (pid) {
+ int wstat;
+
+ if (waitpid(pid, &wstat, 0) < 0)
+ return -1;
+
+ if (WIFSIGNALED(wstat))
+ return -1;
+ if (WIFEXITED(wstat) && WEXITSTATUS(wstat))
+ return -1;
+
+ return 1;
+ }
+
+ /* child */
+
+ if (snprintf(arg, 100, "%d", id) < 0)
+ return -1;
+
+ argv[0] = "/usr/lib/xen/bin/xenconsole";
+ argv[1] = arg;
+ argv[2] = NULL;
+
+ if (execv("/usr/lib/xen/bin/xenconsole", argv))
+ fprintf(stderr, _("failed to run xenconsole: %s\n"),
+ strerror(errno));
+ _exit(1);
+}
+
+/*
+ * Returns 0 if no console is available, 1 if the console was accessed
+ * successfully, or -1 on error.
+ */
+int vshConsole(virDomainPtr dom)
+{
+ xmlDocPtr xml = NULL;
+ xmlXPathObjectPtr obj = NULL;
+ xmlXPathContextPtr ctxt = NULL;
+ char *doc;
+ int ttyfd;
+ int ret = -1;
+
+ doc = virDomainGetXMLDesc(dom, 0);
+ if (!doc)
+ goto cleanup;
+
+ xml = xmlReadDoc((const xmlChar *) doc, "domain.xml", NULL,
+ XML_PARSE_NOENT | XML_PARSE_NONET |
+ XML_PARSE_NOWARNING);
+ free(doc);
+ if (!xml)
+ goto cleanup;
+ ctxt = xmlXPathNewContext(xml);
+ if (!ctxt)
+ goto cleanup;
+
+ obj = xmlXPathEval(BAD_CAST "string(/domain/devices/console/@tty)", ctxt);
+ if (obj == NULL || obj->type != XPATH_STRING || obj->stringval == NULL ||
+ obj->stringval[0] == '\0') {
+ ret = 0;
+ goto cleanup;
+ }
+
+ /* We do not want this to become the controlling TTY */
+ ttyfd = open((char *)obj->stringval, O_NOCTTY | O_RDWR);
+
+ if (ttyfd != -1) {
+ ret = vshDirectConsole(ttyfd);
+ goto cleanup;
+ } else if (errno != EACCES) {
+ fprintf(stderr, _("unable to open tty %s: %s\n"),
+ obj->stringval, strerror(errno));
+ } else {
+ ret = vshXenConsole(virDomainGetID(dom));
+ }
+
+ cleanup:
+ if (obj)
+ xmlXPathFreeObject(obj);
+ if (ctxt)
+ xmlXPathFreeContext(ctxt);
+ if (xml)
+ xmlFreeDoc(xml);
+ virDomainFree(dom);
+ return ret;
+}
+
#endif /* !__MINGW32__ */
/*
--- libvirt-0.4.0/src/console.h 2007-12-07 07:00:48.000000000 -0800
+++ libvirt-new/src/console.h 2008-04-16 08:49:20.165071296 -0700
@@ -25,11 +25,13 @@
#ifndef __MINGW32__
+#include "libvirt/libvirt.h"
+
#ifdef __cplusplus
extern "C" {
#endif
- int vshRunConsole(const char *tty);
+int vshConsole(virDomainPtr dom);
#ifdef __cplusplus
}