Provide root privilege access to QEMU 9p proxy filesystem using socket communication.
Proxy helper is started by root user as: ~ # virtfs-proxy-helper -f|--fd <socket descriptor> -p|--path <path-to-share> Signed-off-by: M. Mohan Kumar <mo...@in.ibm.com> --- Makefile | 3 + configure | 19 +++ fsdev/virtfs-proxy-helper.c | 271 +++++++++++++++++++++++++++++++++++++++++++ hw/9pfs/virtio-9p-proxy.h | 10 ++ 4 files changed, 303 insertions(+), 0 deletions(-) create mode 100644 fsdev/virtfs-proxy-helper.c diff --git a/Makefile b/Makefile index ba8d738..19b481a 100644 --- a/Makefile +++ b/Makefile @@ -153,6 +153,9 @@ qemu-img$(EXESUF): qemu-img.o $(tools-obj-y) $(block-obj-y) qemu-nbd$(EXESUF): qemu-nbd.o $(tools-obj-y) $(block-obj-y) qemu-io$(EXESUF): qemu-io.o cmd.o $(tools-obj-y) $(block-obj-y) +fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/virtio-9p-marshal.o +fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap + 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 a6cf6d6..a2b55e8 100755 --- a/configure +++ b/configure @@ -1873,6 +1873,22 @@ 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 + else + cap=no + fi +fi + +########################################## # pthread probe PTHREADLIBS_LIST="-pthread -lpthread -lpthreadGC2" @@ -2662,6 +2678,9 @@ confdir=$sysconfdir$confsuffix tools= if test "$softmmu" = yes ; then tools="qemu-img\$(EXESUF) qemu-io\$(EXESUF) $tools" + if [ "$cap" = "yes" -a "$linux" = "yes" ] ; then + tools="$tools fsdev/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 diff --git a/fsdev/virtfs-proxy-helper.c b/fsdev/virtfs-proxy-helper.c new file mode 100644 index 0000000..69daf7c --- /dev/null +++ b/fsdev/virtfs-proxy-helper.c @@ -0,0 +1,271 @@ +/* + * Helper for QEMU Proxy FS Driver + * Copyright IBM, Corp. 2011 + * + * Authors: + * M. Mohan Kumar <mo...@in.ibm.com> + * + * This work is licensed under the terms of the GNU GPL, version 2. See + * the COPYING file in the top-level directory. + */ +#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 "virtio-9p-marshal.h" +#include "hw/9pfs/virtio-9p-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) { + 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) { + return -EIO; + } + return retval; +} + +static int read_request(int sockfd, struct iovec *iovec) +{ + int retval; + ProxyHeader header; + + /* read the header */ + retval = socket_read(sockfd, iovec->iov_base, sizeof(header)); + if (retval != sizeof(header)) { + return -EIO; + } + /* unmarshal header */ + proxy_unmarshal(iovec, 1, 0, "dd", &header.type, &header.size); + /* read the request */ + retval = socket_read(sockfd, iovec->iov_base + sizeof(header), header.size); + if (retval != header.size) { + return -EIO; + } + return header.type; +} + +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(0, 0) < 0) { + fprintf(stderr, "daemon call failed\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; +} diff --git a/hw/9pfs/virtio-9p-proxy.h b/hw/9pfs/virtio-9p-proxy.h index f5e1a02..120e940 100644 --- a/hw/9pfs/virtio-9p-proxy.h +++ b/hw/9pfs/virtio-9p-proxy.h @@ -3,6 +3,16 @@ #define BUFF_SZ (4 * 1024) +#define proxy_unmarshal(in_sg, in_elem, offset, fmt, args...) \ + v9fs_unmarshal(in_sg, in_elem, offset, 0 /* convert */, fmt, ##args) +#define proxy_marshal(out_sg, out_elem, offset, fmt, args...) \ + v9fs_marshal(out_sg, out_elem, offset, 0 /* convert */, fmt, ##args) + +union MsgControl { + struct cmsghdr cmsg; + char control[CMSG_SPACE(sizeof(int))]; +}; + typedef struct { int type; int size; -- 1.7.6