X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=fsdev%2Fvirtfs-proxy-helper.c;h=54f7ad1c48f0fe529f77de3b9ddf48119ed801e6;hb=458f3b2c953bdc7110896cb3691251023652523f;hp=9b3c833058da2b7f05ce4f718ac1624ec66e33ad;hpb=b178adc3e78fa1ad274f8d78e43443c39f862143;p=mirror_qemu.git diff --git a/fsdev/virtfs-proxy-helper.c b/fsdev/virtfs-proxy-helper.c index 9b3c833058..54f7ad1c48 100644 --- a/fsdev/virtfs-proxy-helper.c +++ b/fsdev/virtfs-proxy-helper.c @@ -8,41 +8,55 @@ * This work is licensed under the terms of the GNU GPL, version 2. See * the COPYING file in the top-level directory. */ -#include -#include -#include -#include -#include -#include -#include -#include + +#include "qemu/osdep.h" #include -#include #include -#include #include #include #include -#include -#include #include -#include +#include +#include +#ifdef CONFIG_LINUX_MAGIC_H +#include +#endif #include "qemu-common.h" -#include "virtio-9p-marshal.h" -#include "hw/9pfs/virtio-9p-proxy.h" -#include "fsdev/virtio-9p-marshal.h" +#include "qemu/sockets.h" +#include "qemu/xattr.h" +#include "9p-iov-marshal.h" +#include "hw/9pfs/9p-proxy.h" +#include "fsdev/9p-iov-marshal.h" #define PROGNAME "virtfs-proxy-helper" +#ifndef XFS_SUPER_MAGIC +#define XFS_SUPER_MAGIC 0x58465342 +#endif +#ifndef EXT2_SUPER_MAGIC +#define EXT2_SUPER_MAGIC 0xEF53 +#endif +#ifndef REISERFS_SUPER_MAGIC +#define REISERFS_SUPER_MAGIC 0x52654973 +#endif +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E +#endif + static struct option helper_opts[] = { {"fd", required_argument, NULL, 'f'}, {"path", required_argument, NULL, 'p'}, {"nodaemon", no_argument, NULL, 'n'}, + {"socket", required_argument, NULL, 's'}, + {"uid", required_argument, NULL, 'u'}, + {"gid", required_argument, NULL, 'g'}, + {}, }; static bool is_daemon; +static bool get_version; /* IOC getversion IOCTL supported */ -static void do_log(int loglevel, const char *format, ...) +static void GCC_FMT_ATTR(2, 3) do_log(int loglevel, const char *format, ...) { va_list ap; @@ -105,7 +119,7 @@ error: static int init_capabilities(void) { - /* helper needs following capbabilities only */ + /* helper needs following capabilities only */ cap_value_t cap_list[] = { CAP_CHOWN, CAP_DAC_OVERRIDE, @@ -236,7 +250,7 @@ static int send_fd(int sockfd, int fd) static int send_status(int sockfd, struct iovec *iovec, int status) { ProxyHeader header; - int retval, msg_size;; + int retval, msg_size; if (status < 0) { header.type = T_ERROR; @@ -250,6 +264,9 @@ static int send_status(int sockfd, struct iovec *iovec, int status) */ msg_size = proxy_marshal(iovec, 0, "ddd", header.type, header.size, status); + if (msg_size < 0) { + return msg_size; + } retval = socket_write(sockfd, iovec->iov_base, msg_size); if (retval < 0) { return retval; @@ -260,31 +277,76 @@ static int send_status(int sockfd, struct iovec *iovec, int status) /* * from man 7 capabilities, section * Effect of User ID Changes on Capabilities: - * 4. If the file system user ID is changed from 0 to nonzero (see setfsuid(2)) - * then the following capabilities are cleared from the effective set: - * CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, - * CAP_LINUX_IMMUTABLE (since Linux 2.2.30), CAP_MAC_OVERRIDE, and CAP_MKNOD - * (since Linux 2.2.30). If the file system UID is changed from nonzero to 0, - * then any of these capabilities that are enabled in the permitted set - * are enabled in the effective set. + * If the effective user ID is changed from nonzero to 0, then the permitted + * set is copied to the effective set. If the effective user ID is changed + * from 0 to nonzero, then all capabilities are are cleared from the effective + * set. + * + * The setfsuid/setfsgid man pages warn that changing the effective user ID may + * expose the program to unwanted signals, but this is not true anymore: for an + * unprivileged (without CAP_KILL) program to send a signal, the real or + * effective user ID of the sending process must equal the real or saved user + * ID of the target process. Even when dropping privileges, it is enough to + * keep the saved UID to a "privileged" value and virtfs-proxy-helper won't + * be exposed to signals. So just use setresuid/setresgid. */ -static int setfsugid(int uid, int gid) +static int setugid(int uid, int gid, int *suid, int *sgid) { + int retval; + /* - * We still need DAC_OVERRIDE because we don't change + * We still need DAC_OVERRIDE because we don't change * supplementary group ids, and hence may be subjected DAC rules */ cap_value_t cap_list[] = { CAP_DAC_OVERRIDE, }; - setfsgid(gid); - setfsuid(uid); + *suid = geteuid(); + *sgid = getegid(); + + if (setresgid(-1, gid, *sgid) == -1) { + retval = -errno; + goto err_out; + } + + if (setresuid(-1, uid, *suid) == -1) { + retval = -errno; + goto err_sgid; + } if (uid != 0 || gid != 0) { - return do_cap_set(cap_list, ARRAY_SIZE(cap_list), 0); + if (do_cap_set(cap_list, ARRAY_SIZE(cap_list), 0) < 0) { + retval = -errno; + goto err_suid; + } } return 0; + +err_suid: + if (setresuid(-1, *suid, *suid) == -1) { + abort(); + } +err_sgid: + if (setresgid(-1, *sgid, *sgid) == -1) { + abort(); + } +err_out: + return retval; +} + +/* + * This is used to reset the ugid back with the saved values + * There is nothing much we can do checking error values here. + */ +static void resetugid(int suid, int sgid) +{ + if (setresgid(-1, sgid, sgid) == -1) { + abort(); + } + if (setresuid(-1, suid, suid) == -1) { + abort(); + } } /* @@ -324,11 +386,110 @@ static int send_response(int sock, struct iovec *iovec, int size) proxy_marshal(iovec, 0, "dd", header.type, header.size); retval = socket_write(sock, iovec->iov_base, header.size + PROXY_HDR_SZ); if (retval < 0) { - return retval;; + return retval; } return 0; } +/* + * gets generation number + * returns -errno on failure and sizeof(generation number) on success + */ +static int do_getversion(struct iovec *iovec, struct iovec *out_iovec) +{ + uint64_t version; + int retval = -ENOTTY; +#ifdef FS_IOC_GETVERSION + int fd; + V9fsString path; +#endif + + + /* no need to issue ioctl */ + if (!get_version) { + version = 0; + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, "q", version); + return retval; + } +#ifdef FS_IOC_GETVERSION + retval = proxy_unmarshal(iovec, PROXY_HDR_SZ, "s", &path); + if (retval < 0) { + return retval; + } + + fd = open(path.data, O_RDONLY); + if (fd < 0) { + retval = -errno; + goto err_out; + } + if (ioctl(fd, FS_IOC_GETVERSION, &version) < 0) { + retval = -errno; + } else { + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, "q", version); + } + close(fd); +err_out: + v9fs_string_free(&path); +#endif + return retval; +} + +static int do_getxattr(int type, struct iovec *iovec, struct iovec *out_iovec) +{ + int size = 0, offset, retval; + V9fsString path, name, xattr; + + v9fs_string_init(&xattr); + v9fs_string_init(&path); + retval = proxy_unmarshal(iovec, PROXY_HDR_SZ, "ds", &size, &path); + if (retval < 0) { + return retval; + } + offset = PROXY_HDR_SZ + retval; + + if (size) { + xattr.data = g_malloc(size); + xattr.size = size; + } + switch (type) { + case T_LGETXATTR: + v9fs_string_init(&name); + retval = proxy_unmarshal(iovec, offset, "s", &name); + if (retval > 0) { + retval = lgetxattr(path.data, name.data, xattr.data, size); + if (retval < 0) { + retval = -errno; + } else { + xattr.size = retval; + } + } + v9fs_string_free(&name); + break; + case T_LLISTXATTR: + retval = llistxattr(path.data, xattr.data, size); + if (retval < 0) { + retval = -errno; + } else { + xattr.size = retval; + } + break; + } + if (retval < 0) { + goto err_out; + } + + if (!size) { + proxy_marshal(out_iovec, PROXY_HDR_SZ, "d", retval); + retval = sizeof(retval); + } else { + retval = proxy_marshal(out_iovec, PROXY_HDR_SZ, "s", &xattr); + } +err_out: + v9fs_string_free(&xattr); + v9fs_string_free(&path); + return retval; +} + static void stat_to_prstat(ProxyStat *pr_stat, struct stat *stat) { memset(pr_stat, 0, sizeof(*pr_stat)); @@ -439,7 +600,7 @@ static int do_readlink(struct iovec *iovec, struct iovec *out_iovec) } buffer = g_malloc(size); v9fs_string_init(&target); - retval = readlink(path.data, buffer, size); + retval = readlink(path.data, buffer, size - 1); if (retval > 0) { buffer[retval] = '\0'; v9fs_string_sprintf(&target, "%s", buffer); @@ -467,18 +628,15 @@ static int do_create_others(int type, struct iovec *iovec) v9fs_string_init(&path); v9fs_string_init(&oldpath); - cur_uid = geteuid(); - cur_gid = getegid(); retval = proxy_unmarshal(iovec, offset, "dd", &uid, &gid); if (retval < 0) { return retval; } offset += retval; - retval = setfsugid(uid, gid); + retval = setugid(uid, gid, &cur_uid, &cur_gid); if (retval < 0) { - retval = -errno; - goto err_out; + goto unmarshal_err_out; } switch (type) { case T_MKNOD: @@ -508,9 +666,10 @@ static int do_create_others(int type, struct iovec *iovec) } err_out: + resetugid(cur_uid, cur_gid); +unmarshal_err_out: v9fs_string_free(&path); v9fs_string_free(&oldpath); - setfsugid(cur_uid, cur_gid); return retval; } @@ -530,24 +689,16 @@ static int do_create(struct iovec *iovec) if (ret < 0) { goto unmarshal_err_out; } - cur_uid = geteuid(); - cur_gid = getegid(); - ret = setfsugid(uid, gid); + ret = setugid(uid, gid, &cur_uid, &cur_gid); if (ret < 0) { - /* - * On failure reset back to the - * old uid/gid - */ - ret = -errno; - goto err_out; + goto unmarshal_err_out; } ret = open(path.data, flags, mode); if (ret < 0) { ret = -errno; } -err_out: - setfsugid(cur_uid, cur_gid); + resetugid(cur_uid, cur_gid); unmarshal_err_out: v9fs_string_free(&path); return ret; @@ -576,11 +727,73 @@ err_out: return ret; } +/* create unix domain socket and return the descriptor */ +static int proxy_socket(const char *path, uid_t uid, gid_t gid) +{ + int sock, client; + struct sockaddr_un proxy, qemu; + socklen_t size; + + /* requested socket already exists, refuse to start */ + if (!access(path, F_OK)) { + do_log(LOG_CRIT, "socket already exists\n"); + return -1; + } + + if (strlen(path) >= sizeof(proxy.sun_path)) { + do_log(LOG_CRIT, "UNIX domain socket path exceeds %zu characters\n", + sizeof(proxy.sun_path)); + return -1; + } + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { + do_perror("socket"); + return -1; + } + + /* mask other part of mode bits */ + umask(7); + + proxy.sun_family = AF_UNIX; + strcpy(proxy.sun_path, path); + if (bind(sock, (struct sockaddr *)&proxy, + sizeof(struct sockaddr_un)) < 0) { + do_perror("bind"); + goto error; + } + if (chown(proxy.sun_path, uid, gid) < 0) { + do_perror("chown"); + goto error; + } + if (listen(sock, 1) < 0) { + do_perror("listen"); + goto error; + } + + size = sizeof(qemu); + client = accept(sock, (struct sockaddr *)&qemu, &size); + if (client < 0) { + do_perror("accept"); + goto error; + } + close(sock); + return client; + +error: + close(sock); + return -1; +} + static void usage(char *prog) { fprintf(stderr, "usage: %s\n" " -p|--path 9p path to export\n" " {-f|--fd } socket file descriptor to be used\n" + " {-s|--socket socket file used for communication\n" + " \t-u|--uid -g|--gid } - uid:gid combination to give " + " access to this socket\n" + " \tNote: -s & -f can not be used together\n" " [-n|--nodaemon] Run as a normal program\n", basename(prog)); } @@ -599,6 +812,14 @@ static int process_reply(int sock, int type, case T_MKDIR: case T_SYMLINK: case T_LINK: + case T_CHMOD: + case T_CHOWN: + case T_TRUNCATE: + case T_UTIME: + case T_RENAME: + case T_REMOVE: + case T_LSETXATTR: + case T_LREMOVEXATTR: if (send_status(sock, out_iovec, retval) < 0) { return -1; } @@ -606,6 +827,9 @@ static int process_reply(int sock, int type, case T_LSTAT: case T_STATFS: case T_READLINK: + case T_LGETXATTR: + case T_LLISTXATTR: + case T_GETVERSION: if (send_response(sock, out_iovec, retval) < 0) { return -1; } @@ -619,8 +843,14 @@ static int process_reply(int sock, int type, static int process_requests(int sock) { + int flags; + int size = 0; int retval = 0; + uint64_t offset; ProxyHeader header; + int mode, uid, gid; + V9fsString name, value; + struct timespec spec[2]; V9fsString oldpath, path; struct iovec in_iovec, out_iovec; @@ -673,6 +903,118 @@ static int process_requests(int sock) case T_READLINK: retval = do_readlink(&in_iovec, &out_iovec); break; + case T_CHMOD: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, + "sd", &path, &mode); + if (retval > 0) { + retval = chmod(path.data, mode); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_CHOWN: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sdd", &path, + &uid, &gid); + if (retval > 0) { + retval = lchown(path.data, uid, gid); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_TRUNCATE: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sq", + &path, &offset); + if (retval > 0) { + retval = truncate(path.data, offset); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_UTIME: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sqqqq", &path, + &spec[0].tv_sec, &spec[0].tv_nsec, + &spec[1].tv_sec, &spec[1].tv_nsec); + if (retval > 0) { + retval = qemu_utimens(path.data, spec); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_RENAME: + v9fs_string_init(&path); + v9fs_string_init(&oldpath); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, + "ss", &oldpath, &path); + if (retval > 0) { + retval = rename(oldpath.data, path.data); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&oldpath); + v9fs_string_free(&path); + break; + case T_REMOVE: + v9fs_string_init(&path); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "s", &path); + if (retval > 0) { + retval = remove(path.data); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + break; + case T_LGETXATTR: + case T_LLISTXATTR: + retval = do_getxattr(header.type, &in_iovec, &out_iovec); + break; + case T_LSETXATTR: + v9fs_string_init(&path); + v9fs_string_init(&name); + v9fs_string_init(&value); + retval = proxy_unmarshal(&in_iovec, PROXY_HDR_SZ, "sssdd", &path, + &name, &value, &size, &flags); + if (retval > 0) { + retval = lsetxattr(path.data, + name.data, value.data, size, flags); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + v9fs_string_free(&name); + v9fs_string_free(&value); + break; + case T_LREMOVEXATTR: + v9fs_string_init(&path); + v9fs_string_init(&name); + retval = proxy_unmarshal(&in_iovec, + PROXY_HDR_SZ, "ss", &path, &name); + if (retval > 0) { + retval = lremovexattr(path.data, name.data); + if (retval < 0) { + retval = -errno; + } + } + v9fs_string_free(&path); + v9fs_string_free(&name); + break; + case T_GETVERSION: + retval = do_getversion(&in_iovec, &out_iovec); + break; default: goto err_out; break; @@ -691,22 +1033,30 @@ err_out: int main(int argc, char **argv) { int sock; + uid_t own_u; + gid_t own_g; char *rpath = NULL; + char *sock_name = NULL; struct stat stbuf; int c, option_index; +#ifdef FS_IOC_GETVERSION + int retval; + struct statfs st_fs; +#endif is_daemon = true; sock = -1; + own_u = own_g = -1; while (1) { option_index = 0; - c = getopt_long(argc, argv, "p:nh?f:", helper_opts, + c = getopt_long(argc, argv, "p:nh?f:s:u:g:", helper_opts, &option_index); if (c == -1) { break; } switch (c) { case 'p': - rpath = strdup(optarg); + rpath = g_strdup(optarg); break; case 'n': is_daemon = false; @@ -714,6 +1064,15 @@ int main(int argc, char **argv) case 'f': sock = atoi(optarg); break; + case 's': + sock_name = g_strdup(optarg); + break; + case 'u': + own_u = atoi(optarg); + break; + case 'g': + own_g = atoi(optarg); + break; case '?': case 'h': default: @@ -723,8 +1082,22 @@ int main(int argc, char **argv) } /* Parameter validation */ - if (sock == -1 || rpath == NULL) { - fprintf(stderr, "socket descriptor or path not specified\n"); + if ((sock_name == NULL && sock == -1) || rpath == NULL) { + fprintf(stderr, "socket, socket descriptor or path not specified\n"); + usage(argv[0]); + return -1; + } + + if (sock_name && sock != -1) { + fprintf(stderr, "both named socket and socket descriptor specified\n"); + usage(argv[0]); + exit(EXIT_FAILURE); + } + + if (sock_name && (own_u == -1 || own_g == -1)) { + fprintf(stderr, "owner uid:gid not specified, "); + fprintf(stderr, + "owner uid:gid specifies who can access the socket file\n"); usage(argv[0]); exit(EXIT_FAILURE); } @@ -749,6 +1122,12 @@ int main(int argc, char **argv) } do_log(LOG_INFO, "Started\n"); + if (sock_name) { + sock = proxy_socket(sock_name, own_u, own_g); + if (sock < 0) { + goto error; + } + } if (chdir("/") < 0) { do_perror("chdir"); @@ -758,8 +1137,24 @@ int main(int argc, char **argv) do_perror("chroot"); goto error; } - umask(0); + get_version = false; +#ifdef FS_IOC_GETVERSION + /* check whether underlying FS support IOC_GETVERSION */ + retval = statfs("/", &st_fs); + if (!retval) { + switch (st_fs.f_type) { + case EXT2_SUPER_MAGIC: + case BTRFS_SUPER_MAGIC: + case REISERFS_SUPER_MAGIC: + case XFS_SUPER_MAGIC: + get_version = true; + break; + } + } +#endif + + umask(0); if (init_capabilities() < 0) { goto error; }