From: "M. Mohan Kumar" <mo...@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 <mo...@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 --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; +} -- 1.7.6