diff mbox

[04/13] hw/9pfs: File system helper process for qemu 9p proxy FS

Message ID 1320094412-19091-5-git-send-email-mohan@in.ibm.com
State New
Headers show

Commit Message

Mohan Kumar M Oct. 31, 2011, 8:53 p.m. UTC
From: "M. Mohan Kumar" <mohan@in.ibm.com>

Provide root privilege access to QEMU 9p proxy filesystem using socket
communication.

Proxy helper is started by root user as:
~ # virtfs-proxy-helper
 {{-s|--socket <socketname> -u|--uid -g|--gid}|{-f|--fd <socket descriptor>}}
 -p <path-to-share>   [-r <runasuid> -t <runasgid>]

Where uid:gid gives socket access to uid:gid, -r:t combination drops the
privilege to given uid:gid

Signed-off-by: M. Mohan Kumar <mohan@in.ibm.com>
---
 Makefile                      |    2 +
 configure                     |   25 ++++
 hw/9pfs/proxy.h               |    6 +
 hw/9pfs/virtfs-proxy-helper.c |  262 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 295 insertions(+), 0 deletions(-)
 create mode 100644 hw/9pfs/virtfs-proxy-helper.c
diff mbox

Patch

diff --git a/Makefile b/Makefile
index f63fc02..1fd443d 100644
--- a/Makefile
+++ b/Makefile
@@ -153,6 +153,8 @@  qemu-img$(EXESUF): qemu-img.o $(tools-obj-y)
 qemu-nbd$(EXESUF): qemu-nbd.o $(tools-obj-y)
 qemu-io$(EXESUF): qemu-io.o cmd.o $(tools-obj-y)
 
+hw/9pfs/virtfs-proxy-helper$(EXESUF): LIBS+=$(LIBS_PROXY)
+
 qemu-img-cmds.h: $(SRC_PATH)/qemu-img-cmds.hx
 	$(call quiet-command,sh $(SRC_PATH)/scripts/hxtool -h < $< > $@,"  GEN   $@")
 
diff --git a/configure b/configure
index 19e8394..8abd17c 100755
--- a/configure
+++ b/configure
@@ -1866,6 +1866,23 @@  else
 fi
 
 ##########################################
+# libcap probe
+
+if test "$cap" != "no" ; then
+  cat > $TMPC <<EOF
+#include <stdio.h>
+#include <sys/capability.h>
+int main(void) { cap_t caps; caps = cap_init(); }
+EOF
+  if compile_prog "" "-lcap" ; then
+    cap=yes
+    libs_proxy="-lcap"
+  else
+    cap=no
+  fi
+fi
+
+##########################################
 # pthread probe
 PTHREADLIBS_LIST="-pthread -lpthread -lpthreadGC2"
 
@@ -2636,6 +2653,9 @@  confdir=$sysconfdir$confsuffix
 tools=
 if test "$softmmu" = yes ; then
   tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) $tools"
+if test "$cap" = yes; then
+  tools="$tools hw/9pfs/virtfs-proxy-helper\$(EXESUF)"
+fi
   if [ "$linux" = "yes" -o "$bsd" = "yes" -o "$solaris" = "yes" ] ; then
       tools="qemu-nbd\$(EXESUF) $tools"
     if [ "$guest_agent" = "yes" ]; then
@@ -3068,6 +3088,10 @@  if test "$linux_magic_h" = "yes" ; then
   echo "CONFIG_LINUX_MAGIC_H=y" >> $config_host_mak
 fi
 
+if test "$cap" = "yes" ; then
+  echo "CONFIG_CAPABILITY=y" >> $config_host_mak
+fi
+
 # USB host support
 case "$usb" in
 linux)
@@ -3143,6 +3167,7 @@  echo "LIBS+=$LIBS" >> $config_host_mak
 echo "LIBS_TOOLS+=$libs_tools" >> $config_host_mak
 echo "EXESUF=$EXESUF" >> $config_host_mak
 echo "LIBS_QGA+=$libs_qga" >> $config_host_mak
+echo "LIBS_PROXY+=$libs_proxy" >> $config_host_mak
 
 # generate list of library paths for linker script
 
diff --git a/hw/9pfs/proxy.h b/hw/9pfs/proxy.h
index 1a47509..205d7b7 100644
--- a/hw/9pfs/proxy.h
+++ b/hw/9pfs/proxy.h
@@ -2,6 +2,12 @@ 
 #define __PROXY_HELP_H
 
 #define BUFF_SZ (4 * 1024)
+#define V9FS_FD_VALID INT_MAX
+
+union MsgControl {
+    struct cmsghdr cmsg;
+    char control[CMSG_SPACE(sizeof(int))];
+};
 
 typedef struct {
     int type;
diff --git a/hw/9pfs/virtfs-proxy-helper.c b/hw/9pfs/virtfs-proxy-helper.c
new file mode 100644
index 0000000..8e82ca7
--- /dev/null
+++ b/hw/9pfs/virtfs-proxy-helper.c
@@ -0,0 +1,262 @@ 
+#include <stdio.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <sys/un.h>
+#include <limits.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <syslog.h>
+#include <sys/prctl.h>
+#include <sys/capability.h>
+#include <sys/fsuid.h>
+#include <stdarg.h>
+#include "bswap.h"
+#include <sys/socket.h>
+#include "qemu-common.h"
+#include "hw/9pfs/proxy.h"
+
+#define PROGNAME "virtfs-proxy-helper"
+
+static struct option helper_opts[] = {
+    {"fd", required_argument, NULL, 'f'},
+    {"path", required_argument, NULL, 'p'},
+    {"nodaemon", no_argument, NULL, 'n'},
+};
+
+int is_daemon;
+
+static void do_perror(const char *string)
+{
+    if (is_daemon) {
+        syslog(LOG_CRIT, "%s:%s", string, strerror(errno));
+    } else {
+        fprintf(stderr, "%s:%s\n", string, strerror(errno));
+    }
+}
+
+static void do_log(int level, const char *string)
+{
+    if (is_daemon) {
+        syslog(level, "%s", string);
+    } else {
+        fprintf(stderr, "%s\n", string);
+    }
+}
+
+static int cap_set(void)
+{
+    int retval;
+    cap_t caps;
+    cap_value_t cap_list[10];
+
+    /* helper needs following capbabilities only */
+    cap_list[0] = CAP_CHOWN;
+    cap_list[1] = CAP_DAC_OVERRIDE;
+    cap_list[2] = CAP_DAC_READ_SEARCH;
+    cap_list[3] = CAP_FOWNER;
+    cap_list[4] = CAP_FSETID;
+    cap_list[5] = CAP_SETGID;
+    cap_list[6] = CAP_MKNOD;
+    cap_list[7] = CAP_SETUID;
+
+    caps = cap_init();
+    if (caps == NULL) {
+        do_perror("cap_init");
+        return -1;
+    }
+    retval = cap_set_flag(caps, CAP_PERMITTED, 8, cap_list, CAP_SET);
+    if (retval < 0) {
+        do_perror("cap_set_flag");
+        goto error;
+    }
+    retval = cap_set_proc(caps);
+    if (retval < 0) {
+        do_perror("cap_set_proc");
+    }
+    retval = cap_set_flag(caps, CAP_EFFECTIVE, 8, cap_list, CAP_SET);
+    if (retval < 0) {
+        do_perror("cap_set_flag");
+        goto error;
+    }
+    retval = cap_set_proc(caps);
+    if (retval < 0) {
+        do_perror("cap_set_proc");
+    }
+
+error:
+    cap_free(caps);
+    return retval;
+}
+
+static int init_capabilities(void)
+{
+    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
+        do_perror("prctl");
+        return -1;
+    }
+    if (cap_set() < 0) {
+        return -1;
+    }
+    return 0;
+}
+
+static int socket_read(int sockfd, void *buff, ssize_t size)
+{
+    int retval;
+
+    do {
+        retval = read(sockfd, buff, size);
+    } while (retval < 0 && errno == EINTR);
+    if (retval != size) {
+        if (errno != ENOENT) {
+            do_perror("socket read");
+        }
+        return -EIO;
+    }
+    return retval;
+}
+
+static int socket_write(int sockfd, void *buff, ssize_t size)
+{
+    int retval;
+
+    do {
+        retval = write(sockfd, buff, size);
+    } while (retval < 0 && errno == EINTR);
+    if (retval != size) {
+        do_perror("socket_write");
+        return -EIO;
+    }
+    return retval;
+}
+
+static int read_request(int sockfd, struct iovec *iovec)
+{
+    int retval;
+    ProxyHeader header;
+
+    do {
+        retval = socket_read(sockfd, &header, sizeof(header));
+        if (retval != sizeof(header)) {
+            return -EIO;
+        }
+        retval = socket_read(sockfd, iovec->iov_base, header.size);
+        if (retval != header.size) {
+            return -EIO;
+        }
+        return header.type;
+    } while (1);
+}
+
+static void usage(char *prog)
+{
+    fprintf(stderr, "usage: %s\n"
+        " -p|--path <path> 9p path to export\n"
+        " {-f|--fd <socket-descriptor>} socket file descriptor to be used\n"
+        " [-n|--nodaemon] Run as a normal program\n",
+        basename(prog));
+}
+
+static int process_requests(int sock)
+{
+    int type;
+    struct iovec iovec;
+
+    iovec.iov_base = g_malloc(BUFF_SZ);
+    iovec.iov_len = BUFF_SZ;
+    while (1) {
+        type = read_request(sock, &iovec);
+        if (type <= 0) {
+            goto error;
+        }
+    }
+    (void)socket_write;
+error:
+    g_free(iovec.iov_base);
+    return -1;
+}
+
+int main(int argc, char **argv)
+{
+    int sock;
+    char rpath[PATH_MAX];
+    struct stat stbuf;
+    int c, option_index;
+
+    is_daemon = 1;
+    rpath[0] = '\0';
+    sock = -1;
+    while (1) {
+        option_index = 0;
+        c = getopt_long(argc, argv, "p:nh?f:", helper_opts,
+                        &option_index);
+        if (c == -1) {
+            break;
+        }
+        switch (c) {
+        case 'p':
+            strcpy(rpath, optarg);
+            break;
+        case 'n':
+            is_daemon = 0;
+            break;
+	case 'f':
+            sock = atoi(optarg);
+            break;
+        case '?':
+        case 'h':
+        default:
+            usage(argv[0]);
+            return -1;
+            break;
+        }
+    }
+
+    /* Parameter validation */
+    if (sock == -1 || rpath[0] == '\0') {
+        fprintf(stderr, "socket descriptor or path not specified\n");
+        usage(argv[0]);
+        return -1;
+    }
+
+    if (lstat(rpath, &stbuf) < 0) {
+        fprintf(stderr, "invalid path \"%s\" specified?\n", rpath);
+        return -1;
+    }
+
+    if (!S_ISDIR(stbuf.st_mode)) {
+        fprintf(stderr, "specified path \"%s\" is not directory\n", rpath);
+        return -1;
+    }
+
+    if (is_daemon) {
+        if (daemon(1, 0) < 0) {
+            fprintf(stderr, "deamon error\n");
+            return -1;
+        }
+        openlog(PROGNAME, LOG_PID, LOG_DAEMON);
+    }
+
+    do_log(LOG_INFO, "Started");
+
+    if (chroot(rpath) < 0) {
+        do_perror("chroot");
+        goto error;
+    }
+    umask(0);
+
+    if (init_capabilities() < 0) {
+        goto error;
+    }
+
+    process_requests(sock);
+error:
+    do_log(LOG_INFO, "Done");
+    closelog();
+    return 0;
+}