* [Qemu-devel] [PATCH v2 00/28] Series short description
@ 2017-02-26 22:41 Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 01/28] 9pfs: local: move xattr security ops to 9p-xattr.c Greg Kurz
` (30 more replies)
0 siblings, 31 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:41 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
This series tries to fix CVE-2016-9602 reported by Jann Horn of Google
Project Zero:
https://bugzilla.redhat.com/show_bug.cgi?id=1413929
This vulnerability affects all accesses to the underlying filesystem in
the "local" backend code.
If QEMU is started with:
-fsdev local,security_model=<passthrough|none>,path=/foo/bar
then the guest can cause QEMU to create symlinks in /foo/bar.
This causes accesses to any path /foo/bar/some/path to be unsafe, since
untrusted code within the guest (or in another guest sharing the same
virtfs folder) could change some/path to point to a random path of the
host filesystem.
The core problem is that the "local" backend relies on path-based syscalls
to access the underlying filesystem. All path-based syscalls are vulnerable
to this issue, even open(O_NOFOLLOW) or syscalls that explicitly don't
dereference symlinks, since the kernel only checks the rightmost element of
the path. Depending on the privilege level of the QEMU process, a guest can
end up opening, renaming, changing ACLs, unlinking... files on the host
filesystem.
The right way to address this is to use "at" variants of all syscalls in
the "local" backend code. This requires to open directories without
traversing any symlink in the intermediate path elements. There was a
tentative to introduce an O_BENEATH flag for openat() that would address
this:
https://patchwork.kernel.org/patch/7007181/
Unfortunately this never got merged. An alternative is to walk through all
path elements manually with openat(O_NOFOLLOW).
v2:
- /proc based implementation for xattr code (fixes metadata perf drop
observed with v1)
- some code refactoring
Stefan.
I had to rework some patches you had already reviewed, please consider
giving your Reviewed-by again if the changes are ok.
Thanks.
--
Greg
---
Greg Kurz (28):
9pfs: local: move xattr security ops to 9p-xattr.c
9pfs: remove side-effects in local_init()
9pfs: remove side-effects in local_open() and local_opendir()
9pfs: introduce openat_nofollow() helper
9pfs: local: keep a file descriptor on the shared folder
9pfs: local: open/opendir: don't follow symlinks
9pfs: local: lgetxattr: don't follow symlinks
9pfs: local: llistxattr: don't follow symlinks
9pfs: local: lsetxattr: don't follow symlinks
9pfs: local: lremovexattr: don't follow symlinks
9pfs: local: unlinkat: don't follow symlinks
9pfs: local: remove: don't follow symlinks
9pfs: local: utimensat: don't follow symlinks
9pfs: local: statfs: don't follow symlinks
9pfs: local: truncate: don't follow symlinks
9pfs: local: readlink: don't follow symlinks
9pfs: local: lstat: don't follow symlinks
9pfs: local: renameat: don't follow symlinks
9pfs: local: rename: use renameat
9pfs: local: improve error handling in link op
9pfs: local: link: don't follow symlinks
9pfs: local: chmod: don't follow symlinks
9pfs: local: chown: don't follow symlinks
9pfs: local: symlink: don't follow symlinks
9pfs: local: mknod: don't follow symlinks
9pfs: local: mkdir: don't follow symlinks
9pfs: local: open2: don't follow symlinks
9pfs: local: drop unused code
hw/9pfs/9p-local.c | 1023 ++++++++++++++++++++++++++---------------------
hw/9pfs/9p-local.h | 20 +
hw/9pfs/9p-posix-acl.c | 44 --
hw/9pfs/9p-util.c | 65 +++
hw/9pfs/9p-util.h | 52 ++
hw/9pfs/9p-xattr-user.c | 24 -
hw/9pfs/9p-xattr.c | 166 +++++++-
hw/9pfs/9p-xattr.h | 87 +---
hw/9pfs/Makefile.objs | 2
9 files changed, 887 insertions(+), 596 deletions(-)
create mode 100644 hw/9pfs/9p-local.h
create mode 100644 hw/9pfs/9p-util.c
create mode 100644 hw/9pfs/9p-util.h
^ permalink raw reply [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 01/28] 9pfs: local: move xattr security ops to 9p-xattr.c
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
@ 2017-02-26 22:41 ` Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 02/28] 9pfs: remove side-effects in local_init() Greg Kurz
` (29 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:41 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
These functions are always called indirectly. It really doesn't make sense
for them to sit in a header file.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-xattr.c | 61 ++++++++++++++++++++++++++++++++++++++++
hw/9pfs/9p-xattr.h | 80 +++++++++-------------------------------------------
2 files changed, 75 insertions(+), 66 deletions(-)
diff --git a/hw/9pfs/9p-xattr.c b/hw/9pfs/9p-xattr.c
index 5d8595ed932a..19a2daf02f5c 100644
--- a/hw/9pfs/9p-xattr.c
+++ b/hw/9pfs/9p-xattr.c
@@ -143,6 +143,67 @@ int v9fs_remove_xattr(FsContext *ctx,
}
+ssize_t pt_getxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size)
+{
+ char *buffer;
+ ssize_t ret;
+
+ buffer = rpath(ctx, path);
+ ret = lgetxattr(buffer, name, value, size);
+ g_free(buffer);
+ return ret;
+}
+
+int pt_setxattr(FsContext *ctx, const char *path, const char *name, void *value,
+ size_t size, int flags)
+{
+ char *buffer;
+ int ret;
+
+ buffer = rpath(ctx, path);
+ ret = lsetxattr(buffer, name, value, size, flags);
+ g_free(buffer);
+ return ret;
+}
+
+int pt_removexattr(FsContext *ctx, const char *path, const char *name)
+{
+ char *buffer;
+ int ret;
+
+ buffer = rpath(ctx, path);
+ ret = lremovexattr(path, name);
+ g_free(buffer);
+ return ret;
+}
+
+ssize_t notsup_getxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+int notsup_setxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
+ssize_t notsup_listxattr(FsContext *ctx, const char *path, char *name,
+ void *value, size_t size)
+{
+ return 0;
+}
+
+int notsup_removexattr(FsContext *ctx, const char *path, const char *name)
+{
+ errno = ENOTSUP;
+ return -1;
+}
+
XattrOperations *mapped_xattr_ops[] = {
&mapped_user_xattr,
&mapped_pacl_xattr,
diff --git a/hw/9pfs/9p-xattr.h b/hw/9pfs/9p-xattr.h
index a853ea641c0b..3f43f5153f3c 100644
--- a/hw/9pfs/9p-xattr.h
+++ b/hw/9pfs/9p-xattr.h
@@ -49,73 +49,21 @@ ssize_t v9fs_list_xattr(FsContext *ctx, const char *path, void *value,
int v9fs_set_xattr(FsContext *ctx, const char *path, const char *name,
void *value, size_t size, int flags);
int v9fs_remove_xattr(FsContext *ctx, const char *path, const char *name);
+
ssize_t pt_listxattr(FsContext *ctx, const char *path, char *name, void *value,
size_t size);
-
-static inline ssize_t pt_getxattr(FsContext *ctx, const char *path,
- const char *name, void *value, size_t size)
-{
- char *buffer;
- ssize_t ret;
-
- buffer = rpath(ctx, path);
- ret = lgetxattr(buffer, name, value, size);
- g_free(buffer);
- return ret;
-}
-
-static inline int pt_setxattr(FsContext *ctx, const char *path,
- const char *name, void *value,
- size_t size, int flags)
-{
- char *buffer;
- int ret;
-
- buffer = rpath(ctx, path);
- ret = lsetxattr(buffer, name, value, size, flags);
- g_free(buffer);
- return ret;
-}
-
-static inline int pt_removexattr(FsContext *ctx,
- const char *path, const char *name)
-{
- char *buffer;
- int ret;
-
- buffer = rpath(ctx, path);
- ret = lremovexattr(path, name);
- g_free(buffer);
- return ret;
-}
-
-static inline ssize_t notsup_getxattr(FsContext *ctx, const char *path,
- const char *name, void *value,
- size_t size)
-{
- errno = ENOTSUP;
- return -1;
-}
-
-static inline int notsup_setxattr(FsContext *ctx, const char *path,
- const char *name, void *value,
- size_t size, int flags)
-{
- errno = ENOTSUP;
- return -1;
-}
-
-static inline ssize_t notsup_listxattr(FsContext *ctx, const char *path,
- char *name, void *value, size_t size)
-{
- return 0;
-}
-
-static inline int notsup_removexattr(FsContext *ctx,
- const char *path, const char *name)
-{
- errno = ENOTSUP;
- return -1;
-}
+ssize_t pt_getxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size);
+int pt_setxattr(FsContext *ctx, const char *path, const char *name, void *value,
+ size_t size, int flags);
+int pt_removexattr(FsContext *ctx, const char *path, const char *name);
+
+ssize_t notsup_getxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size);
+int notsup_setxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size, int flags);
+ssize_t notsup_listxattr(FsContext *ctx, const char *path, char *name,
+ void *value, size_t size);
+int notsup_removexattr(FsContext *ctx, const char *path, const char *name);
#endif
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 02/28] 9pfs: remove side-effects in local_init()
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 01/28] 9pfs: local: move xattr security ops to 9p-xattr.c Greg Kurz
@ 2017-02-26 22:41 ` Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 03/28] 9pfs: remove side-effects in local_open() and local_opendir() Greg Kurz
` (28 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:41 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
If this function fails, it should not modify *ctx.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - s/iocl/ioctl in comment
---
hw/9pfs/9p-local.c | 37 +++++++++++++++++++------------------
1 file changed, 19 insertions(+), 18 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 7de07e1ba67f..4a8e628117ae 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -1168,9 +1168,25 @@ static int local_ioc_getversion(FsContext *ctx, V9fsPath *path,
static int local_init(FsContext *ctx)
{
- int err = 0;
struct statfs stbuf;
+#ifdef FS_IOC_GETVERSION
+ /*
+ * use ioc_getversion only if the ioctl is definied
+ */
+ if (statfs(ctx->fs_root, &stbuf) < 0) {
+ return -1;
+ }
+ switch (stbuf.f_type) {
+ case EXT2_SUPER_MAGIC:
+ case BTRFS_SUPER_MAGIC:
+ case REISERFS_SUPER_MAGIC:
+ case XFS_SUPER_MAGIC:
+ ctx->exops.get_st_gen = local_ioc_getversion;
+ break;
+ }
+#endif
+
if (ctx->export_flags & V9FS_SM_PASSTHROUGH) {
ctx->xops = passthrough_xattr_ops;
} else if (ctx->export_flags & V9FS_SM_MAPPED) {
@@ -1185,23 +1201,8 @@ static int local_init(FsContext *ctx)
ctx->xops = passthrough_xattr_ops;
}
ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
-#ifdef FS_IOC_GETVERSION
- /*
- * use ioc_getversion only if the iocl is definied
- */
- err = statfs(ctx->fs_root, &stbuf);
- if (!err) {
- switch (stbuf.f_type) {
- case EXT2_SUPER_MAGIC:
- case BTRFS_SUPER_MAGIC:
- case REISERFS_SUPER_MAGIC:
- case XFS_SUPER_MAGIC:
- ctx->exops.get_st_gen = local_ioc_getversion;
- break;
- }
- }
-#endif
- return err;
+
+ return 0;
}
static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 03/28] 9pfs: remove side-effects in local_open() and local_opendir()
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 01/28] 9pfs: local: move xattr security ops to 9p-xattr.c Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 02/28] 9pfs: remove side-effects in local_init() Greg Kurz
@ 2017-02-26 22:41 ` Greg Kurz
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper Greg Kurz
` (27 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:41 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
If these functions fail, they should not change *fs. Let's use local
variables to fix this.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 13 ++++++++++---
1 file changed, 10 insertions(+), 3 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 4a8e628117ae..607cd2aeceea 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -356,10 +356,15 @@ static int local_open(FsContext *ctx, V9fsPath *fs_path,
{
char *buffer;
char *path = fs_path->data;
+ int fd;
buffer = rpath(ctx, path);
- fs->fd = open(buffer, flags | O_NOFOLLOW);
+ fd = open(buffer, flags | O_NOFOLLOW);
g_free(buffer);
+ if (fd == -1) {
+ return -1;
+ }
+ fs->fd = fd;
return fs->fd;
}
@@ -368,13 +373,15 @@ static int local_opendir(FsContext *ctx,
{
char *buffer;
char *path = fs_path->data;
+ DIR *stream;
buffer = rpath(ctx, path);
- fs->dir.stream = opendir(buffer);
+ stream = opendir(buffer);
g_free(buffer);
- if (!fs->dir.stream) {
+ if (!stream) {
return -1;
}
+ fs->dir.stream = stream;
return 0;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (2 preceding siblings ...)
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 03/28] 9pfs: remove side-effects in local_open() and local_opendir() Greg Kurz
@ 2017-02-26 22:42 ` Greg Kurz
2017-02-27 12:44 ` Stefan Hajnoczi
2017-02-27 23:28 ` Eric Blake
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 05/28] 9pfs: local: keep a file descriptor on the shared folder Greg Kurz
` (26 subsequent siblings)
30 siblings, 2 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:42 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
When using the passthrough security mode, symbolic links created by the
guest are actual symbolic links on the host file system.
Since the resolution of symbolic links during path walk is supposed to
occur on the client side. The server should hence never receive any path
pointing to an actual symbolic link. This isn't guaranteed by the protocol
though, and malicious code in the guest can trick the server to issue
various syscalls on paths whose one or more elements are symbolic links.
In the case of the "local" backend using the "passthrough" or "none"
security modes, the guest can directly create symbolic links to arbitrary
locations on the host (as per spec). The "mapped-xattr" and "mapped-file"
security modes are also affected to a lesser extent as they require some
help from an external entity to create actual symbolic links on the host,
i.e. another guest using "passthrough" mode for example.
The current code hence relies on O_NOFOLLOW and "l*()" variants of system
calls. Unfortunately, this only applies to the rightmost path component.
A guest could maliciously replace any component in a trusted path with a
symbolic link. This could allow any guest to escape a virtfs shared folder.
This patch introduces a variant of the openat() syscall that successively
opens each path element with O_NOFOLLOW. When passing a file descriptor
pointing to a trusted directory, one is guaranteed to be returned a
file descriptor pointing to a path which is beneath the trusted directory.
This will be used by subsequent patches to implement symlink-safe path walk
for any access to the backend.
Symbolic links aren't the only threats actually: a malicious guest could
change a path element to point to other types of file with undesirable
effects:
- a named pipe or any other thing that would cause openat() to block
- a terminal device which would become QEMU's controlling terminal
These issues can be addressed with O_NONBLOCK and O_NOCTTY.
Two helpers are introduced: one to open intermediate path elements and one
to open the rightmost path element.
Suggested-by: Jann Horn <jannh@google.com>
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - introduced openat_dir() and openat_file() helpers
- move stripping of leading '/' characters to caller
---
hw/9pfs/9p-util.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++
hw/9pfs/9p-util.h | 48 ++++++++++++++++++++++++++++++++++++++++++++
hw/9pfs/Makefile.objs | 2 +-
3 files changed, 102 insertions(+), 1 deletion(-)
create mode 100644 hw/9pfs/9p-util.c
create mode 100644 hw/9pfs/9p-util.h
diff --git a/hw/9pfs/9p-util.c b/hw/9pfs/9p-util.c
new file mode 100644
index 000000000000..62fd7a76212a
--- /dev/null
+++ b/hw/9pfs/9p-util.c
@@ -0,0 +1,53 @@
+/*
+ * 9p utilities
+ *
+ * Copyright IBM, Corp. 2017
+ *
+ * Authors:
+ * Greg Kurz <groug@kaod.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "9p-util.h"
+
+int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
+{
+ int fd;
+
+ fd = dup(dirfd);
+ if (fd == -1) {
+ return -1;
+ }
+
+ while (*path) {
+ const char *c;
+ int next_fd;
+ char *head;
+
+ head = g_strdup(path);
+ c = strchr(path, '/');
+ if (c) {
+ head[c - path] = 0;
+ next_fd = openat_dir(fd, head);
+ } else {
+ next_fd = openat_file(fd, head, flags, mode);
+ }
+ g_free(head);
+ if (next_fd == -1) {
+ close_preserve_errno(fd);
+ return -1;
+ }
+ close(fd);
+ fd = next_fd;
+
+ if (!c) {
+ break;
+ }
+ path = c + 1;
+ }
+
+ return fd;
+}
diff --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h
new file mode 100644
index 000000000000..ca0d440ddc1e
--- /dev/null
+++ b/hw/9pfs/9p-util.h
@@ -0,0 +1,48 @@
+/*
+ * 9p utilities
+ *
+ * Copyright IBM, Corp. 2017
+ *
+ * Authors:
+ * Greg Kurz <groug@kaod.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_9P_UTIL_H
+#define QEMU_9P_UTIL_H
+
+static inline void close_preserve_errno(int fd)
+{
+ int serrno = errno;
+ close(fd);
+ errno = serrno;
+}
+
+static inline int openat_dir(int dirfd, const char *name)
+{
+ return openat(dirfd, name, O_DIRECTORY | O_RDONLY | O_PATH);
+}
+
+static inline int openat_file(int dirfd, const char *name, int flags,
+ mode_t mode)
+{
+ int fd, serrno;
+
+ fd = openat(dirfd, name, flags | O_NOFOLLOW | O_NOCTTY | O_NONBLOCK,
+ mode);
+ if (fd == -1) {
+ return -1;
+ }
+
+ serrno = errno;
+ /* O_NONBLOCK was only needed to open the file. Let's drop it. */
+ assert(!fcntl(fd, F_SETFL, flags));
+ errno = serrno;
+ return fd;
+}
+
+int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode);
+
+#endif
diff --git a/hw/9pfs/Makefile.objs b/hw/9pfs/Makefile.objs
index da0ae0cfdbae..32197e6671dd 100644
--- a/hw/9pfs/Makefile.objs
+++ b/hw/9pfs/Makefile.objs
@@ -1,4 +1,4 @@
-common-obj-y = 9p.o
+common-obj-y = 9p.o 9p-util.o
common-obj-y += 9p-local.o 9p-xattr.o
common-obj-y += 9p-xattr-user.o 9p-posix-acl.o
common-obj-y += coth.o cofs.o codir.o cofile.o
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 05/28] 9pfs: local: keep a file descriptor on the shared folder
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (3 preceding siblings ...)
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper Greg Kurz
@ 2017-02-26 22:42 ` Greg Kurz
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 06/28] 9pfs: local: open/opendir: don't follow symlinks Greg Kurz
` (25 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:42 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
This patch opens the shared folder and caches the file descriptor, so that
it can be used to do symlink-safe path walk.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - introduce LocalData type
---
hw/9pfs/9p-local.c | 30 ++++++++++++++++++++++++++++--
1 file changed, 28 insertions(+), 2 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 607cd2aeceea..be6be615149b 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -14,6 +14,7 @@
#include "qemu/osdep.h"
#include "9p.h"
#include "9p-xattr.h"
+#include "9p-util.h"
#include "fsdev/qemu-fsdev.h" /* local_ops */
#include <arpa/inet.h>
#include <pwd.h>
@@ -43,6 +44,10 @@
#define BTRFS_SUPER_MAGIC 0x9123683E
#endif
+typedef struct {
+ int mountfd;
+} LocalData;
+
#define VIRTFS_META_DIR ".virtfs_metadata"
static char *local_mapped_attr_path(FsContext *ctx, const char *path)
@@ -1176,13 +1181,20 @@ static int local_ioc_getversion(FsContext *ctx, V9fsPath *path,
static int local_init(FsContext *ctx)
{
struct statfs stbuf;
+ LocalData *data = g_malloc(sizeof(*data));
+
+ data->mountfd = open(ctx->fs_root, O_DIRECTORY | O_RDONLY);
+ if (data->mountfd == -1) {
+ goto err;
+ }
#ifdef FS_IOC_GETVERSION
/*
* use ioc_getversion only if the ioctl is definied
*/
- if (statfs(ctx->fs_root, &stbuf) < 0) {
- return -1;
+ if (fstatfs(data->mountfd, &stbuf) < 0) {
+ close_preserve_errno(data->mountfd);
+ goto err;
}
switch (stbuf.f_type) {
case EXT2_SUPER_MAGIC:
@@ -1209,7 +1221,20 @@ static int local_init(FsContext *ctx)
}
ctx->export_flags |= V9FS_PATHNAME_FSCONTEXT;
+ ctx->private = data;
return 0;
+
+err:
+ g_free(data);
+ return -1;
+}
+
+static void local_cleanup(FsContext *ctx)
+{
+ LocalData *data = ctx->private;
+
+ close(data->mountfd);
+ g_free(data);
}
static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
@@ -1252,6 +1277,7 @@ static int local_parse_opts(QemuOpts *opts, struct FsDriverEntry *fse)
FileOperations local_ops = {
.parse_opts = local_parse_opts,
.init = local_init,
+ .cleanup = local_cleanup,
.lstat = local_lstat,
.readlink = local_readlink,
.close = local_close,
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 06/28] 9pfs: local: open/opendir: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (4 preceding siblings ...)
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 05/28] 9pfs: local: keep a file descriptor on the shared folder Greg Kurz
@ 2017-02-26 22:42 ` Greg Kurz
2017-02-27 12:49 ` Stefan Hajnoczi
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 07/28] 9pfs: local: lgetxattr: " Greg Kurz
` (24 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:42 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_open() and local_opendir() callbacks are vulnerable to symlink
attacks because they call:
(1) open(O_NOFOLLOW) which follows symbolic links in all path elements but
the rightmost one
(2) opendir() which follows symbolic links in all path elements
This patch converts both callbacks to use new helpers based on
openat_nofollow() to only open files and directories if they are
below the virtfs shared folder
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - use LocalData type
- strip leading '/' characters in local_open_nofollow()
---
hw/9pfs/9p-local.c | 37 +++++++++++++++++++++++++++----------
hw/9pfs/9p-local.h | 20 ++++++++++++++++++++
2 files changed, 47 insertions(+), 10 deletions(-)
create mode 100644 hw/9pfs/9p-local.h
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index be6be615149b..74b921e65316 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -13,6 +13,7 @@
#include "qemu/osdep.h"
#include "9p.h"
+#include "9p-local.h"
#include "9p-xattr.h"
#include "9p-util.h"
#include "fsdev/qemu-fsdev.h" /* local_ops */
@@ -48,6 +49,24 @@ typedef struct {
int mountfd;
} LocalData;
+int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
+ mode_t mode)
+{
+ LocalData *data = fs_ctx->private;
+
+ /* All paths are relative to the path data->mountfd points to */
+ while (*path == '/') {
+ path++;
+ }
+
+ return openat_nofollow(data->mountfd, path, flags, mode);
+}
+
+int local_opendir_nofollow(FsContext *fs_ctx, const char *path)
+{
+ return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0);
+}
+
#define VIRTFS_META_DIR ".virtfs_metadata"
static char *local_mapped_attr_path(FsContext *ctx, const char *path)
@@ -359,13 +378,9 @@ static int local_closedir(FsContext *ctx, V9fsFidOpenState *fs)
static int local_open(FsContext *ctx, V9fsPath *fs_path,
int flags, V9fsFidOpenState *fs)
{
- char *buffer;
- char *path = fs_path->data;
int fd;
- buffer = rpath(ctx, path);
- fd = open(buffer, flags | O_NOFOLLOW);
- g_free(buffer);
+ fd = local_open_nofollow(ctx, fs_path->data, flags, 0);
if (fd == -1) {
return -1;
}
@@ -376,13 +391,15 @@ static int local_open(FsContext *ctx, V9fsPath *fs_path,
static int local_opendir(FsContext *ctx,
V9fsPath *fs_path, V9fsFidOpenState *fs)
{
- char *buffer;
- char *path = fs_path->data;
+ int dirfd;
DIR *stream;
- buffer = rpath(ctx, path);
- stream = opendir(buffer);
- g_free(buffer);
+ dirfd = local_opendir_nofollow(ctx, fs_path->data);
+ if (dirfd == -1) {
+ return -1;
+ }
+
+ stream = fdopendir(dirfd);
if (!stream) {
return -1;
}
diff --git a/hw/9pfs/9p-local.h b/hw/9pfs/9p-local.h
new file mode 100644
index 000000000000..32c72749d9df
--- /dev/null
+++ b/hw/9pfs/9p-local.h
@@ -0,0 +1,20 @@
+/*
+ * 9p local backend utilities
+ *
+ * Copyright IBM, Corp. 2017
+ *
+ * Authors:
+ * Greg Kurz <groug@kaod.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef QEMU_9P_LOCAL_H
+#define QEMU_9P_LOCAL_H
+
+int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
+ mode_t mode);
+int local_opendir_nofollow(FsContext *fs_ctx, const char *path);
+
+#endif
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 07/28] 9pfs: local: lgetxattr: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (5 preceding siblings ...)
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 06/28] 9pfs: local: open/opendir: don't follow symlinks Greg Kurz
@ 2017-02-26 22:42 ` Greg Kurz
2017-02-27 12:58 ` Stefan Hajnoczi
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 08/28] 9pfs: local: llistxattr: " Greg Kurz
` (23 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:42 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_lgetxattr() callback is vulnerable to symlink attacks because
it calls lgetxattr() which follows symbolic links in all path elements but
the rightmost one.
This patch introduces a helper to emulate the non-existing fgetxattrat()
function: it is implemented with /proc/self/fd which provides a trusted
path that can be safely passed to lgetxattr().
local_lgetxattr() is converted to use this helper and opendir_nofollow().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - introduce /proc based fgetxattrat_nofollow()
---
hw/9pfs/9p-posix-acl.c | 16 ++--------------
hw/9pfs/9p-util.c | 12 ++++++++++++
hw/9pfs/9p-util.h | 2 ++
hw/9pfs/9p-xattr-user.c | 8 +-------
hw/9pfs/9p-xattr.c | 31 ++++++++++++++++++++++++-------
hw/9pfs/9p-xattr.h | 2 ++
6 files changed, 43 insertions(+), 28 deletions(-)
diff --git a/hw/9pfs/9p-posix-acl.c b/hw/9pfs/9p-posix-acl.c
index ec003181cd33..9435e27a368c 100644
--- a/hw/9pfs/9p-posix-acl.c
+++ b/hw/9pfs/9p-posix-acl.c
@@ -25,13 +25,7 @@
static ssize_t mp_pacl_getxattr(FsContext *ctx, const char *path,
const char *name, void *value, size_t size)
{
- char *buffer;
- ssize_t ret;
-
- buffer = rpath(ctx, path);
- ret = lgetxattr(buffer, MAP_ACL_ACCESS, value, size);
- g_free(buffer);
- return ret;
+ return local_getxattr_nofollow(ctx, path, MAP_ACL_ACCESS, value, size);
}
static ssize_t mp_pacl_listxattr(FsContext *ctx, const char *path,
@@ -89,13 +83,7 @@ static int mp_pacl_removexattr(FsContext *ctx,
static ssize_t mp_dacl_getxattr(FsContext *ctx, const char *path,
const char *name, void *value, size_t size)
{
- char *buffer;
- ssize_t ret;
-
- buffer = rpath(ctx, path);
- ret = lgetxattr(buffer, MAP_ACL_DEFAULT, value, size);
- g_free(buffer);
- return ret;
+ return local_getxattr_nofollow(ctx, path, MAP_ACL_DEFAULT, value, size);
}
static ssize_t mp_dacl_listxattr(FsContext *ctx, const char *path,
diff --git a/hw/9pfs/9p-util.c b/hw/9pfs/9p-util.c
index 62fd7a76212a..b3dd1f160280 100644
--- a/hw/9pfs/9p-util.c
+++ b/hw/9pfs/9p-util.c
@@ -11,6 +11,7 @@
*/
#include "qemu/osdep.h"
+#include "qemu/xattr.h"
#include "9p-util.h"
int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
@@ -51,3 +52,14 @@ int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
return fd;
}
+
+ssize_t fgetxattrat_nofollow(int dirfd, const char *filename, const char *name,
+ void *value, size_t size)
+{
+ char *proc_path = g_strdup_printf("/proc/self/fd/%d/%s", dirfd, filename);
+ int ret;
+
+ ret = lgetxattr(proc_path, name, value, size);
+ g_free(proc_path);
+ return ret;
+}
diff --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h
index ca0d440ddc1e..5a1be712130e 100644
--- a/hw/9pfs/9p-util.h
+++ b/hw/9pfs/9p-util.h
@@ -44,5 +44,7 @@ static inline int openat_file(int dirfd, const char *name, int flags,
}
int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode);
+ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name,
+ void *value, size_t size);
#endif
diff --git a/hw/9pfs/9p-xattr-user.c b/hw/9pfs/9p-xattr-user.c
index f87530c8b526..4071fbc4c086 100644
--- a/hw/9pfs/9p-xattr-user.c
+++ b/hw/9pfs/9p-xattr-user.c
@@ -20,9 +20,6 @@
static ssize_t mp_user_getxattr(FsContext *ctx, const char *path,
const char *name, void *value, size_t size)
{
- char *buffer;
- ssize_t ret;
-
if (strncmp(name, "user.virtfs.", 12) == 0) {
/*
* Don't allow fetch of user.virtfs namesapce
@@ -31,10 +28,7 @@ static ssize_t mp_user_getxattr(FsContext *ctx, const char *path,
errno = ENOATTR;
return -1;
}
- buffer = rpath(ctx, path);
- ret = lgetxattr(buffer, name, value, size);
- g_free(buffer);
- return ret;
+ return local_getxattr_nofollow(ctx, path, name, value, size);
}
static ssize_t mp_user_listxattr(FsContext *ctx, const char *path,
diff --git a/hw/9pfs/9p-xattr.c b/hw/9pfs/9p-xattr.c
index 19a2daf02f5c..aa4391e6b317 100644
--- a/hw/9pfs/9p-xattr.c
+++ b/hw/9pfs/9p-xattr.c
@@ -15,6 +15,8 @@
#include "9p.h"
#include "fsdev/file-op-9p.h"
#include "9p-xattr.h"
+#include "9p-util.h"
+#include "9p-local.h"
static XattrOperations *get_xattr_operations(XattrOperations **h,
@@ -143,18 +145,33 @@ int v9fs_remove_xattr(FsContext *ctx,
}
-ssize_t pt_getxattr(FsContext *ctx, const char *path, const char *name,
- void *value, size_t size)
+ssize_t local_getxattr_nofollow(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size)
{
- char *buffer;
- ssize_t ret;
+ char *dirpath = g_path_get_dirname(path);
+ char *filename = g_path_get_basename(path);
+ int dirfd;
+ ssize_t ret = -1;
+
+ dirfd = local_opendir_nofollow(ctx, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
- buffer = rpath(ctx, path);
- ret = lgetxattr(buffer, name, value, size);
- g_free(buffer);
+ ret = fgetxattrat_nofollow(dirfd, filename, name, value, size);
+ close_preserve_errno(dirfd);
+out:
+ g_free(dirpath);
+ g_free(filename);
return ret;
}
+ssize_t pt_getxattr(FsContext *ctx, const char *path, const char *name,
+ void *value, size_t size)
+{
+ return local_getxattr_nofollow(ctx, path, name, value, size);
+}
+
int pt_setxattr(FsContext *ctx, const char *path, const char *name, void *value,
size_t size, int flags)
{
diff --git a/hw/9pfs/9p-xattr.h b/hw/9pfs/9p-xattr.h
index 3f43f5153f3c..69a8b6b62e3c 100644
--- a/hw/9pfs/9p-xattr.h
+++ b/hw/9pfs/9p-xattr.h
@@ -29,6 +29,8 @@ typedef struct xattr_operations
const char *path, const char *name);
} XattrOperations;
+ssize_t local_getxattr_nofollow(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size);
extern XattrOperations mapped_user_xattr;
extern XattrOperations passthrough_user_xattr;
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 08/28] 9pfs: local: llistxattr: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (6 preceding siblings ...)
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 07/28] 9pfs: local: lgetxattr: " Greg Kurz
@ 2017-02-26 22:42 ` Greg Kurz
2017-02-27 13:08 ` Stefan Hajnoczi
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 09/28] 9pfs: local: lsetxattr: " Greg Kurz
` (22 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:42 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_llistxattr() callback is vulnerable to symlink attacks because
it calls llistxattr() which follows symbolic links in all path elements but
the rightmost one.
This patch introduces a helper to emulate the non-existing flistxattrat()
function: it is implemented with /proc/self/fd which provides a trusted
path that can be safely passed to llistxattr().
local_llistxattr() is converted to use this helper and opendir_nofollow().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - introduce /proc based flistxattrat_nofollow()
---
hw/9pfs/9p-xattr.c | 35 +++++++++++++++++++++++++++++------
1 file changed, 29 insertions(+), 6 deletions(-)
diff --git a/hw/9pfs/9p-xattr.c b/hw/9pfs/9p-xattr.c
index aa4391e6b317..54193c630c9d 100644
--- a/hw/9pfs/9p-xattr.c
+++ b/hw/9pfs/9p-xattr.c
@@ -60,6 +60,16 @@ ssize_t pt_listxattr(FsContext *ctx, const char *path,
return name_size;
}
+static ssize_t flistxattrat_nofollow(int dirfd, const char *filename,
+ char *list, size_t size)
+{
+ char *proc_path = g_strdup_printf("/proc/self/fd/%d/%s", dirfd, filename);
+ int ret;
+
+ ret = llistxattr(proc_path, list, size);
+ g_free(proc_path);
+ return ret;
+}
/*
* Get the list and pass to each layer to find out whether
@@ -69,24 +79,37 @@ ssize_t v9fs_list_xattr(FsContext *ctx, const char *path,
void *value, size_t vsize)
{
ssize_t size = 0;
- char *buffer;
void *ovalue = value;
XattrOperations *xops;
char *orig_value, *orig_value_start;
ssize_t xattr_len, parsed_len = 0, attr_len;
+ char *dirpath, *name;
+ int dirfd;
/* Get the actual len */
- buffer = rpath(ctx, path);
- xattr_len = llistxattr(buffer, value, 0);
+ dirpath = g_path_get_dirname(path);
+ dirfd = local_opendir_nofollow(ctx, dirpath);
+ g_free(dirpath);
+ if (dirfd == -1) {
+ return -1;
+ }
+
+ name = g_path_get_basename(path);
+ xattr_len = flistxattrat_nofollow(dirfd, name, value, 0);
if (xattr_len <= 0) {
- g_free(buffer);
+ g_free(name);
+ close_preserve_errno(dirfd);
return xattr_len;
}
/* Now fetch the xattr and find the actual size */
orig_value = g_malloc(xattr_len);
- xattr_len = llistxattr(buffer, orig_value, xattr_len);
- g_free(buffer);
+ xattr_len = flistxattrat_nofollow(dirfd, name, orig_value, xattr_len);
+ g_free(name);
+ close_preserve_errno(dirfd);
+ if (xattr_len < 0) {
+ return -1;
+ }
/* store the orig pointer */
orig_value_start = orig_value;
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 09/28] 9pfs: local: lsetxattr: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (7 preceding siblings ...)
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 08/28] 9pfs: local: llistxattr: " Greg Kurz
@ 2017-02-26 22:42 ` Greg Kurz
2017-02-27 13:10 ` Stefan Hajnoczi
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 10/28] 9pfs: local: lremovexattr: " Greg Kurz
` (21 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:42 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_lsetxattr() callback is vulnerable to symlink attacks because
it calls lsetxattr() which follows symbolic links in all path elements but
the rightmost one.
This patch introduces a helper to emulate the non-existing fsetxattrat()
function: it is implemented with /proc/self/fd which provides a trusted
path that can be safely passed to lsetxattr().
local_lsetxattr() is converted to use this helper and opendir_nofollow().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - introduce /proc based fsetxattrat_nofollow()
---
hw/9pfs/9p-posix-acl.c | 18 ++++--------------
hw/9pfs/9p-util.h | 2 ++
hw/9pfs/9p-xattr-user.c | 8 +-------
hw/9pfs/9p-xattr.c | 39 +++++++++++++++++++++++++++++++++------
hw/9pfs/9p-xattr.h | 3 +++
5 files changed, 43 insertions(+), 27 deletions(-)
diff --git a/hw/9pfs/9p-posix-acl.c b/hw/9pfs/9p-posix-acl.c
index 9435e27a368c..0154e2a7605f 100644
--- a/hw/9pfs/9p-posix-acl.c
+++ b/hw/9pfs/9p-posix-acl.c
@@ -50,13 +50,8 @@ static ssize_t mp_pacl_listxattr(FsContext *ctx, const char *path,
static int mp_pacl_setxattr(FsContext *ctx, const char *path, const char *name,
void *value, size_t size, int flags)
{
- char *buffer;
- int ret;
-
- buffer = rpath(ctx, path);
- ret = lsetxattr(buffer, MAP_ACL_ACCESS, value, size, flags);
- g_free(buffer);
- return ret;
+ return local_setxattr_nofollow(ctx, path, MAP_ACL_ACCESS, value, size,
+ flags);
}
static int mp_pacl_removexattr(FsContext *ctx,
@@ -108,13 +103,8 @@ static ssize_t mp_dacl_listxattr(FsContext *ctx, const char *path,
static int mp_dacl_setxattr(FsContext *ctx, const char *path, const char *name,
void *value, size_t size, int flags)
{
- char *buffer;
- int ret;
-
- buffer = rpath(ctx, path);
- ret = lsetxattr(buffer, MAP_ACL_DEFAULT, value, size, flags);
- g_free(buffer);
- return ret;
+ return local_setxattr_nofollow(ctx, path, MAP_ACL_DEFAULT, value, size,
+ flags);
}
static int mp_dacl_removexattr(FsContext *ctx,
diff --git a/hw/9pfs/9p-util.h b/hw/9pfs/9p-util.h
index 5a1be712130e..d4adac62956d 100644
--- a/hw/9pfs/9p-util.h
+++ b/hw/9pfs/9p-util.h
@@ -46,5 +46,7 @@ static inline int openat_file(int dirfd, const char *name, int flags,
int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode);
ssize_t fgetxattrat_nofollow(int dirfd, const char *path, const char *name,
void *value, size_t size);
+int fsetxattrat_nofollow(int dirfd, const char *path, const char *name,
+ void *value, size_t size, int flags);
#endif
diff --git a/hw/9pfs/9p-xattr-user.c b/hw/9pfs/9p-xattr-user.c
index 4071fbc4c086..1840a5db66f3 100644
--- a/hw/9pfs/9p-xattr-user.c
+++ b/hw/9pfs/9p-xattr-user.c
@@ -67,9 +67,6 @@ static ssize_t mp_user_listxattr(FsContext *ctx, const char *path,
static int mp_user_setxattr(FsContext *ctx, const char *path, const char *name,
void *value, size_t size, int flags)
{
- char *buffer;
- int ret;
-
if (strncmp(name, "user.virtfs.", 12) == 0) {
/*
* Don't allow fetch of user.virtfs namesapce
@@ -78,10 +75,7 @@ static int mp_user_setxattr(FsContext *ctx, const char *path, const char *name,
errno = EACCES;
return -1;
}
- buffer = rpath(ctx, path);
- ret = lsetxattr(buffer, name, value, size, flags);
- g_free(buffer);
- return ret;
+ return local_setxattr_nofollow(ctx, path, name, value, size, flags);
}
static int mp_user_removexattr(FsContext *ctx,
diff --git a/hw/9pfs/9p-xattr.c b/hw/9pfs/9p-xattr.c
index 54193c630c9d..a0167dd4d898 100644
--- a/hw/9pfs/9p-xattr.c
+++ b/hw/9pfs/9p-xattr.c
@@ -195,18 +195,45 @@ ssize_t pt_getxattr(FsContext *ctx, const char *path, const char *name,
return local_getxattr_nofollow(ctx, path, name, value, size);
}
-int pt_setxattr(FsContext *ctx, const char *path, const char *name, void *value,
- size_t size, int flags)
+int fsetxattrat_nofollow(int dirfd, const char *filename, const char *name,
+ void *value, size_t size, int flags)
{
- char *buffer;
+ char *proc_path = g_strdup_printf("/proc/self/fd/%d/%s", dirfd, filename);
int ret;
- buffer = rpath(ctx, path);
- ret = lsetxattr(buffer, name, value, size, flags);
- g_free(buffer);
+ ret = lsetxattr(proc_path, name, value, size, flags);
+ g_free(proc_path);
+ return ret;
+}
+
+ssize_t local_setxattr_nofollow(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size,
+ int flags)
+{
+ char *dirpath = g_path_get_dirname(path);
+ char *filename = g_path_get_basename(path);
+ int dirfd;
+ ssize_t ret = -1;
+
+ dirfd = local_opendir_nofollow(ctx, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
+
+ ret = fsetxattrat_nofollow(dirfd, filename, name, value, size, flags);
+ close_preserve_errno(dirfd);
+out:
+ g_free(dirpath);
+ g_free(filename);
return ret;
}
+int pt_setxattr(FsContext *ctx, const char *path, const char *name, void *value,
+ size_t size, int flags)
+{
+ return local_setxattr_nofollow(ctx, path, name, value, size, flags);
+}
+
int pt_removexattr(FsContext *ctx, const char *path, const char *name)
{
char *buffer;
diff --git a/hw/9pfs/9p-xattr.h b/hw/9pfs/9p-xattr.h
index 69a8b6b62e3c..7558970d8511 100644
--- a/hw/9pfs/9p-xattr.h
+++ b/hw/9pfs/9p-xattr.h
@@ -31,6 +31,9 @@ typedef struct xattr_operations
ssize_t local_getxattr_nofollow(FsContext *ctx, const char *path,
const char *name, void *value, size_t size);
+ssize_t local_setxattr_nofollow(FsContext *ctx, const char *path,
+ const char *name, void *value, size_t size,
+ int flags);
extern XattrOperations mapped_user_xattr;
extern XattrOperations passthrough_user_xattr;
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 10/28] 9pfs: local: lremovexattr: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (8 preceding siblings ...)
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 09/28] 9pfs: local: lsetxattr: " Greg Kurz
@ 2017-02-26 22:42 ` Greg Kurz
2017-02-27 13:12 ` Stefan Hajnoczi
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 11/28] 9pfs: local: unlinkat: " Greg Kurz
` (20 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:42 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_lremovexattr() callback is vulnerable to symlink attacks because
it calls lremovexattr() which follows symbolic links in all path elements
but the rightmost one.
This patch introduces a helper to emulate the non-existing fremovexattrat()
function: it is implemented with /proc/self/fd which provides a trusted
path that can be safely passed to lremovexattr().
local_lremovexattr() is converted to use this helper and opendir_nofollow().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - introduce /proc based fremovexattrat_nofollow()
- fix arguments passed to local_removexattr_nofollow()
---
hw/9pfs/9p-posix-acl.c | 10 ++--------
hw/9pfs/9p-xattr-user.c | 8 +-------
hw/9pfs/9p-xattr.c | 36 +++++++++++++++++++++++++++++++-----
hw/9pfs/9p-xattr.h | 2 ++
4 files changed, 36 insertions(+), 20 deletions(-)
diff --git a/hw/9pfs/9p-posix-acl.c b/hw/9pfs/9p-posix-acl.c
index 0154e2a7605f..bbf89064f7ae 100644
--- a/hw/9pfs/9p-posix-acl.c
+++ b/hw/9pfs/9p-posix-acl.c
@@ -58,10 +58,8 @@ static int mp_pacl_removexattr(FsContext *ctx,
const char *path, const char *name)
{
int ret;
- char *buffer;
- buffer = rpath(ctx, path);
- ret = lremovexattr(buffer, MAP_ACL_ACCESS);
+ ret = local_removexattr_nofollow(ctx, path, MAP_ACL_ACCESS);
if (ret == -1 && errno == ENODATA) {
/*
* We don't get ENODATA error when trying to remove a
@@ -71,7 +69,6 @@ static int mp_pacl_removexattr(FsContext *ctx,
errno = 0;
ret = 0;
}
- g_free(buffer);
return ret;
}
@@ -111,10 +108,8 @@ static int mp_dacl_removexattr(FsContext *ctx,
const char *path, const char *name)
{
int ret;
- char *buffer;
- buffer = rpath(ctx, path);
- ret = lremovexattr(buffer, MAP_ACL_DEFAULT);
+ ret = local_removexattr_nofollow(ctx, path, MAP_ACL_DEFAULT);
if (ret == -1 && errno == ENODATA) {
/*
* We don't get ENODATA error when trying to remove a
@@ -124,7 +119,6 @@ static int mp_dacl_removexattr(FsContext *ctx,
errno = 0;
ret = 0;
}
- g_free(buffer);
return ret;
}
diff --git a/hw/9pfs/9p-xattr-user.c b/hw/9pfs/9p-xattr-user.c
index 1840a5db66f3..2c90817b75a6 100644
--- a/hw/9pfs/9p-xattr-user.c
+++ b/hw/9pfs/9p-xattr-user.c
@@ -81,9 +81,6 @@ static int mp_user_setxattr(FsContext *ctx, const char *path, const char *name,
static int mp_user_removexattr(FsContext *ctx,
const char *path, const char *name)
{
- char *buffer;
- int ret;
-
if (strncmp(name, "user.virtfs.", 12) == 0) {
/*
* Don't allow fetch of user.virtfs namesapce
@@ -92,10 +89,7 @@ static int mp_user_removexattr(FsContext *ctx,
errno = EACCES;
return -1;
}
- buffer = rpath(ctx, path);
- ret = lremovexattr(buffer, name);
- g_free(buffer);
- return ret;
+ return local_removexattr_nofollow(ctx, path, name);
}
XattrOperations mapped_user_xattr = {
diff --git a/hw/9pfs/9p-xattr.c b/hw/9pfs/9p-xattr.c
index a0167dd4d898..eec160b3c2ac 100644
--- a/hw/9pfs/9p-xattr.c
+++ b/hw/9pfs/9p-xattr.c
@@ -234,17 +234,43 @@ int pt_setxattr(FsContext *ctx, const char *path, const char *name, void *value,
return local_setxattr_nofollow(ctx, path, name, value, size, flags);
}
-int pt_removexattr(FsContext *ctx, const char *path, const char *name)
+static ssize_t fremovexattrat_nofollow(int dirfd, const char *filename,
+ const char *name)
{
- char *buffer;
+ char *proc_path = g_strdup_printf("/proc/self/fd/%d/%s", dirfd, filename);
int ret;
- buffer = rpath(ctx, path);
- ret = lremovexattr(path, name);
- g_free(buffer);
+ ret = lremovexattr(proc_path, name);
+ g_free(proc_path);
return ret;
}
+ssize_t local_removexattr_nofollow(FsContext *ctx, const char *path,
+ const char *name)
+{
+ char *dirpath = g_path_get_dirname(path);
+ char *filename = g_path_get_basename(path);
+ int dirfd;
+ ssize_t ret = -1;
+
+ dirfd = local_opendir_nofollow(ctx, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
+
+ ret = fremovexattrat_nofollow(dirfd, filename, name);
+ close_preserve_errno(dirfd);
+out:
+ g_free(dirpath);
+ g_free(filename);
+ return ret;
+}
+
+int pt_removexattr(FsContext *ctx, const char *path, const char *name)
+{
+ return local_removexattr_nofollow(ctx, path, name);
+}
+
ssize_t notsup_getxattr(FsContext *ctx, const char *path, const char *name,
void *value, size_t size)
{
diff --git a/hw/9pfs/9p-xattr.h b/hw/9pfs/9p-xattr.h
index 7558970d8511..0d83996575e1 100644
--- a/hw/9pfs/9p-xattr.h
+++ b/hw/9pfs/9p-xattr.h
@@ -34,6 +34,8 @@ ssize_t local_getxattr_nofollow(FsContext *ctx, const char *path,
ssize_t local_setxattr_nofollow(FsContext *ctx, const char *path,
const char *name, void *value, size_t size,
int flags);
+ssize_t local_removexattr_nofollow(FsContext *ctx, const char *path,
+ const char *name);
extern XattrOperations mapped_user_xattr;
extern XattrOperations passthrough_user_xattr;
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 11/28] 9pfs: local: unlinkat: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (9 preceding siblings ...)
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 10/28] 9pfs: local: lremovexattr: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-27 13:14 ` Stefan Hajnoczi
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 12/28] 9pfs: local: remove: " Greg Kurz
` (19 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_unlinkat() callback is vulnerable to symlink attacks because it
calls remove() which follows symbolic links in all path elements but the
rightmost one.
This patch converts local_unlinkat() to rely on opendir_nofollow() and
unlinkat() instead.
Most of the code is moved to a separate local_unlinkat_common() helper
which will be reused in a subsequent patch to fix the same issue in
local_remove().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - use openat_dir()
---
hw/9pfs/9p-local.c | 99 +++++++++++++++++++++++++++++-----------------------
1 file changed, 56 insertions(+), 43 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 74b921e65316..69439714cd91 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -969,6 +969,56 @@ static int local_utimensat(FsContext *s, V9fsPath *fs_path,
return ret;
}
+static int local_unlinkat_common(FsContext *ctx, int dirfd, const char *name,
+ int flags)
+{
+ int ret = -1;
+
+ if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ int map_dirfd;
+
+ if (flags == AT_REMOVEDIR) {
+ int fd;
+
+ fd = openat(dirfd, name, O_RDONLY | O_DIRECTORY | O_PATH);
+ if (fd == -1) {
+ goto err_out;
+ }
+ /*
+ * If directory remove .virtfs_metadata contained in the
+ * directory
+ */
+ ret = unlinkat(fd, VIRTFS_META_DIR, AT_REMOVEDIR);
+ close_preserve_errno(fd);
+ if (ret < 0 && errno != ENOENT) {
+ /*
+ * We didn't had the .virtfs_metadata file. May be file created
+ * in non-mapped mode ?. Ignore ENOENT.
+ */
+ goto err_out;
+ }
+ }
+ /*
+ * Now remove the name from parent directory
+ * .virtfs_metadata directory.
+ */
+ map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
+ ret = unlinkat(map_dirfd, name, 0);
+ close_preserve_errno(map_dirfd);
+ if (ret < 0 && errno != ENOENT) {
+ /*
+ * We didn't had the .virtfs_metadata file. May be file created
+ * in non-mapped mode ?. Ignore ENOENT.
+ */
+ goto err_out;
+ }
+ }
+
+ ret = unlinkat(dirfd, name, flags);
+err_out:
+ return ret;
+}
+
static int local_remove(FsContext *ctx, const char *path)
{
int err;
@@ -1118,52 +1168,15 @@ static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
const char *name, int flags)
{
int ret;
- V9fsString fullname;
- char *buffer;
-
- v9fs_string_init(&fullname);
+ int dirfd;
- v9fs_string_sprintf(&fullname, "%s/%s", dir->data, name);
- if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- if (flags == AT_REMOVEDIR) {
- /*
- * If directory remove .virtfs_metadata contained in the
- * directory
- */
- buffer = g_strdup_printf("%s/%s/%s", ctx->fs_root,
- fullname.data, VIRTFS_META_DIR);
- ret = remove(buffer);
- g_free(buffer);
- if (ret < 0 && errno != ENOENT) {
- /*
- * We didn't had the .virtfs_metadata file. May be file created
- * in non-mapped mode ?. Ignore ENOENT.
- */
- goto err_out;
- }
- }
- /*
- * Now remove the name from parent directory
- * .virtfs_metadata directory.
- */
- buffer = local_mapped_attr_path(ctx, fullname.data);
- ret = remove(buffer);
- g_free(buffer);
- if (ret < 0 && errno != ENOENT) {
- /*
- * We didn't had the .virtfs_metadata file. May be file created
- * in non-mapped mode ?. Ignore ENOENT.
- */
- goto err_out;
- }
+ dirfd = local_opendir_nofollow(ctx, dir->data);
+ if (dirfd == -1) {
+ return -1;
}
- /* Remove the name finally */
- buffer = rpath(ctx, fullname.data);
- ret = remove(buffer);
- g_free(buffer);
-err_out:
- v9fs_string_free(&fullname);
+ ret = local_unlinkat_common(ctx, dirfd, name, flags);
+ close_preserve_errno(dirfd);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 12/28] 9pfs: local: remove: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (10 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 11/28] 9pfs: local: unlinkat: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 13/28] 9pfs: local: utimensat: " Greg Kurz
` (18 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_remove() callback is vulnerable to symlink attacks because it
calls:
(1) lstat() which follows symbolic links in all path elements but the
rightmost one
(2) remove() which follows symbolic links in all path elements but the
rightmost one
This patch converts local_remove() to rely on opendir_nofollow(),
fstatat(AT_SYMLINK_NOFOLLOW) to fix (1) and unlinkat() to fix (2).
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 64 +++++++++++++++++-----------------------------------
1 file changed, 21 insertions(+), 43 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 69439714cd91..b118a7632362 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -1021,54 +1021,32 @@ err_out:
static int local_remove(FsContext *ctx, const char *path)
{
- int err;
struct stat stbuf;
- char *buffer;
+ char *dirpath = g_path_get_dirname(path);
+ char *name = g_path_get_basename(path);
+ int flags = 0;
+ int dirfd;
+ int err = -1;
- if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- buffer = rpath(ctx, path);
- err = lstat(buffer, &stbuf);
- g_free(buffer);
- if (err) {
- goto err_out;
- }
- /*
- * If directory remove .virtfs_metadata contained in the
- * directory
- */
- if (S_ISDIR(stbuf.st_mode)) {
- buffer = g_strdup_printf("%s/%s/%s", ctx->fs_root,
- path, VIRTFS_META_DIR);
- err = remove(buffer);
- g_free(buffer);
- if (err < 0 && errno != ENOENT) {
- /*
- * We didn't had the .virtfs_metadata file. May be file created
- * in non-mapped mode ?. Ignore ENOENT.
- */
- goto err_out;
- }
- }
- /*
- * Now remove the name from parent directory
- * .virtfs_metadata directory
- */
- buffer = local_mapped_attr_path(ctx, path);
- err = remove(buffer);
- g_free(buffer);
- if (err < 0 && errno != ENOENT) {
- /*
- * We didn't had the .virtfs_metadata file. May be file created
- * in non-mapped mode ?. Ignore ENOENT.
- */
- goto err_out;
- }
+ dirfd = local_opendir_nofollow(ctx, dirpath);
+ if (dirfd) {
+ goto out;
}
- buffer = rpath(ctx, path);
- err = remove(buffer);
- g_free(buffer);
+ if (fstatat(dirfd, path, &stbuf, AT_SYMLINK_NOFOLLOW) < 0) {
+ goto err_out;
+ }
+
+ if (S_ISDIR(stbuf.st_mode)) {
+ flags |= AT_REMOVEDIR;
+ }
+
+ err = local_unlinkat_common(ctx, dirfd, name, flags);
err_out:
+ close_preserve_errno(dirfd);
+out:
+ g_free(name);
+ g_free(dirpath);
return err;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 13/28] 9pfs: local: utimensat: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (11 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 12/28] 9pfs: local: remove: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 14/28] 9pfs: local: statfs: " Greg Kurz
` (17 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_utimensat() callback is vulnerable to symlink attacks because it
calls qemu_utimens()->utimensat(AT_SYMLINK_NOFOLLOW) which follows symbolic
links in all path elements but the rightmost one or qemu_utimens()->utimes()
which follows symbolic links for all path elements.
This patch converts local_utimensat() to rely on opendir_nofollow() and
utimensat(AT_SYMLINK_NOFOLLOW) directly instead of using qemu_utimens().
It is hence assumed that the OS supports utimensat(), i.e. has glibc 2.6
or higher and linux 2.6.22 or higher, which seems reasonable nowadays.
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 19 +++++++++++++------
1 file changed, 13 insertions(+), 6 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index b118a7632362..9b2069f9120e 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -959,13 +959,20 @@ static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
static int local_utimensat(FsContext *s, V9fsPath *fs_path,
const struct timespec *buf)
{
- char *buffer;
- int ret;
- char *path = fs_path->data;
+ char *dirpath = g_path_get_dirname(fs_path->data);
+ char *name = g_path_get_basename(fs_path->data);
+ int dirfd, ret = -1;
- buffer = rpath(s, path);
- ret = qemu_utimens(buffer, buf);
- g_free(buffer);
+ dirfd = local_opendir_nofollow(s, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
+
+ ret = utimensat(dirfd, name, buf, AT_SYMLINK_NOFOLLOW);
+ close_preserve_errno(dirfd);
+out:
+ g_free(dirpath);
+ g_free(name);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 14/28] 9pfs: local: statfs: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (12 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 13/28] 9pfs: local: utimensat: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 15/28] 9pfs: local: truncate: " Greg Kurz
` (16 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_statfs() callback is vulnerable to symlink attacks because it
calls statfs() which follows symbolic links in all path elements.
This patch converts local_statfs() to rely on open_nofollow() and fstatfs()
instead.
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 9b2069f9120e..e88426dfebe5 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -1077,13 +1077,11 @@ static int local_fsync(FsContext *ctx, int fid_type,
static int local_statfs(FsContext *s, V9fsPath *fs_path, struct statfs *stbuf)
{
- char *buffer;
- int ret;
- char *path = fs_path->data;
+ int fd, ret;
- buffer = rpath(s, path);
- ret = statfs(buffer, stbuf);
- g_free(buffer);
+ fd = local_open_nofollow(s, fs_path->data, O_RDONLY, 0);
+ ret = fstatfs(fd, stbuf);
+ close_preserve_errno(fd);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 15/28] 9pfs: local: truncate: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (13 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 14/28] 9pfs: local: statfs: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 16/28] 9pfs: local: readlink: " Greg Kurz
` (15 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_truncate() callback is vulnerable to symlink attacks because
it calls truncate() which follows symbolic links in all path elements.
This patch converts local_truncate() to rely on open_nofollow() and
ftruncate() instead.
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index e88426dfebe5..1d460a35aa7f 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -894,13 +894,14 @@ err_out:
static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
{
- char *buffer;
- int ret;
- char *path = fs_path->data;
+ int fd, ret;
- buffer = rpath(ctx, path);
- ret = truncate(buffer, size);
- g_free(buffer);
+ fd = local_open_nofollow(ctx, fs_path->data, O_WRONLY, 0);
+ if (fd == -1) {
+ return -1;
+ }
+ ret = ftruncate(fd, size);
+ close_preserve_errno(fd);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 16/28] 9pfs: local: readlink: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (14 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 15/28] 9pfs: local: truncate: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 17/28] 9pfs: local: lstat: " Greg Kurz
` (14 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_readlink() callback is vulnerable to symlink attacks because it
calls:
(1) open(O_NOFOLLOW) which follows symbolic links for all path elements but
the rightmost one
(2) readlink() which follows symbolic links for all path elements but the
rightmost one
This patch converts local_readlink() to rely on open_nofollow() to fix (1)
and opendir_nofollow(), readlinkat() to fix (2).
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 26 +++++++++++++++++---------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 1d460a35aa7f..a47b7476545a 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -340,27 +340,35 @@ static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
char *buf, size_t bufsz)
{
ssize_t tsize = -1;
- char *buffer;
- char *path = fs_path->data;
if ((fs_ctx->export_flags & V9FS_SM_MAPPED) ||
(fs_ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
int fd;
- buffer = rpath(fs_ctx, path);
- fd = open(buffer, O_RDONLY | O_NOFOLLOW);
- g_free(buffer);
+
+ fd = local_open_nofollow(fs_ctx, fs_path->data, O_RDONLY, 0);
if (fd == -1) {
return -1;
}
do {
tsize = read(fd, (void *)buf, bufsz);
} while (tsize == -1 && errno == EINTR);
- close(fd);
+ close_preserve_errno(fd);
} else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
(fs_ctx->export_flags & V9FS_SM_NONE)) {
- buffer = rpath(fs_ctx, path);
- tsize = readlink(buffer, buf, bufsz);
- g_free(buffer);
+ char *dirpath = g_path_get_dirname(fs_path->data);
+ char *name = g_path_get_basename(fs_path->data);
+ int dirfd;
+
+ dirfd = local_opendir_nofollow(fs_ctx, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
+
+ tsize = readlinkat(dirfd, name, buf, bufsz);
+ close_preserve_errno(dirfd);
+ out:
+ g_free(name);
+ g_free(dirpath);
}
return tsize;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 17/28] 9pfs: local: lstat: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (15 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 16/28] 9pfs: local: readlink: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 18/28] 9pfs: local: renameat: " Greg Kurz
` (13 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_lstat() callback is vulnerable to symlink attacks because it
calls:
(1) lstat() which follows symbolic links in all path elements but the
rightmost one
(2) getxattr() which follows symbolic links in all path elements
(3) local_mapped_file_attr()->local_fopen()->openat(O_NOFOLLOW) which
follows symbolic links in all path elements but the rightmost
one
This patch converts local_lstat() to rely on opendir_nofollow() and
fstatat(AT_SYMLINK_NOFOLLOW) to fix (1), fgetxattrat_nofollow() to
fix (2).
A new local_fopenat() helper is introduced as a replacement to
local_fopen() to fix (3). No effort is made to factor out code
because local_fopen() will be dropped when all users have been
converted to call local_fopenat().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - use openat_file()
---
hw/9pfs/9p-local.c | 78 +++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 61 insertions(+), 17 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index a47b7476545a..7f31d5a508bc 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -111,17 +111,49 @@ static FILE *local_fopen(const char *path, const char *mode)
return fp;
}
+static FILE *local_fopenat(int dirfd, const char *name, const char *mode)
+{
+ int fd, o_mode = 0;
+ FILE *fp;
+ int flags;
+ /*
+ * only supports two modes
+ */
+ if (mode[0] == 'r') {
+ flags = O_RDONLY;
+ } else if (mode[0] == 'w') {
+ flags = O_WRONLY | O_TRUNC | O_CREAT;
+ o_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
+ } else {
+ return NULL;
+ }
+ fd = openat_file(dirfd, name, flags, o_mode);
+ if (fd == -1) {
+ return NULL;
+ }
+ fp = fdopen(fd, mode);
+ if (!fp) {
+ close(fd);
+ }
+ return fp;
+}
+
#define ATTR_MAX 100
-static void local_mapped_file_attr(FsContext *ctx, const char *path,
+static void local_mapped_file_attr(int dirfd, const char *name,
struct stat *stbuf)
{
FILE *fp;
char buf[ATTR_MAX];
- char *attr_path;
+ int map_dirfd;
- attr_path = local_mapped_attr_path(ctx, path);
- fp = local_fopen(attr_path, "r");
- g_free(attr_path);
+ map_dirfd = openat(dirfd, VIRTFS_META_DIR,
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (map_dirfd == -1) {
+ return;
+ }
+
+ fp = local_fopenat(map_dirfd, name, "r");
+ close_preserve_errno(map_dirfd);
if (!fp) {
return;
}
@@ -143,12 +175,17 @@ static void local_mapped_file_attr(FsContext *ctx, const char *path,
static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
{
- int err;
- char *buffer;
- char *path = fs_path->data;
+ int err = -1;
+ char *dirpath = g_path_get_dirname(fs_path->data);
+ char *name = g_path_get_basename(fs_path->data);
+ int dirfd;
- buffer = rpath(fs_ctx, path);
- err = lstat(buffer, stbuf);
+ dirfd = local_opendir_nofollow(fs_ctx, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
+
+ err = fstatat(dirfd, name, stbuf, AT_SYMLINK_NOFOLLOW);
if (err) {
goto err_out;
}
@@ -158,25 +195,32 @@ static int local_lstat(FsContext *fs_ctx, V9fsPath *fs_path, struct stat *stbuf)
gid_t tmp_gid;
mode_t tmp_mode;
dev_t tmp_dev;
- if (getxattr(buffer, "user.virtfs.uid", &tmp_uid, sizeof(uid_t)) > 0) {
+
+ if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.uid", &tmp_uid,
+ sizeof(uid_t)) > 0) {
stbuf->st_uid = le32_to_cpu(tmp_uid);
}
- if (getxattr(buffer, "user.virtfs.gid", &tmp_gid, sizeof(gid_t)) > 0) {
+ if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.gid", &tmp_gid,
+ sizeof(gid_t)) > 0) {
stbuf->st_gid = le32_to_cpu(tmp_gid);
}
- if (getxattr(buffer, "user.virtfs.mode",
- &tmp_mode, sizeof(mode_t)) > 0) {
+ if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.mode", &tmp_mode,
+ sizeof(mode_t)) > 0) {
stbuf->st_mode = le32_to_cpu(tmp_mode);
}
- if (getxattr(buffer, "user.virtfs.rdev", &tmp_dev, sizeof(dev_t)) > 0) {
+ if (fgetxattrat_nofollow(dirfd, name, "user.virtfs.rdev", &tmp_dev,
+ sizeof(dev_t)) > 0) {
stbuf->st_rdev = le64_to_cpu(tmp_dev);
}
} else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- local_mapped_file_attr(fs_ctx, path, stbuf);
+ local_mapped_file_attr(dirfd, name, stbuf);
}
err_out:
- g_free(buffer);
+ close_preserve_errno(dirfd);
+out:
+ g_free(name);
+ g_free(dirpath);
return err;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 18/28] 9pfs: local: renameat: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (16 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 17/28] 9pfs: local: lstat: " Greg Kurz
@ 2017-02-26 22:43 ` Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 19/28] 9pfs: local: rename: use renameat Greg Kurz
` (12 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:43 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_renameat() callback is currently a wrapper around local_rename()
which is vulnerable to symlink attacks.
This patch rewrites local_renameat() to have its own implementation, based
on local_opendir_nofollow() and renameat().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - use openat_dir()
---
hw/9pfs/9p-local.c | 74 +++++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 64 insertions(+), 10 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 7f31d5a508bc..1c378c369733 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -67,6 +67,14 @@ int local_opendir_nofollow(FsContext *fs_ctx, const char *path)
return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0);
}
+static void renameat_preserve_errno(int odirfd, const char *opath, int ndirfd,
+ const char *npath)
+{
+ int serrno = errno;
+ renameat(odirfd, opath, ndirfd, npath);
+ errno = serrno;
+}
+
#define VIRTFS_META_DIR ".virtfs_metadata"
static char *local_mapped_attr_path(FsContext *ctx, const char *path)
@@ -146,8 +154,7 @@ static void local_mapped_file_attr(int dirfd, const char *name,
char buf[ATTR_MAX];
int map_dirfd;
- map_dirfd = openat(dirfd, VIRTFS_META_DIR,
- O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
if (map_dirfd == -1) {
return;
}
@@ -1186,17 +1193,64 @@ static int local_renameat(FsContext *ctx, V9fsPath *olddir,
const char *new_name)
{
int ret;
- V9fsString old_full_name, new_full_name;
+ int odirfd, ndirfd;
+
+ odirfd = local_opendir_nofollow(ctx, olddir->data);
+ if (odirfd == -1) {
+ return -1;
+ }
+
+ ndirfd = local_opendir_nofollow(ctx, newdir->data);
+ if (ndirfd == -1) {
+ close_preserve_errno(odirfd);
+ return -1;
+ }
+
+ ret = renameat(odirfd, old_name, ndirfd, new_name);
+ if (ret < 0) {
+ goto out;
+ }
- v9fs_string_init(&old_full_name);
- v9fs_string_init(&new_full_name);
+ if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ int omap_dirfd, nmap_dirfd;
- v9fs_string_sprintf(&old_full_name, "%s/%s", olddir->data, old_name);
- v9fs_string_sprintf(&new_full_name, "%s/%s", newdir->data, new_name);
+ ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
+ if (ret < 0 && errno != EEXIST) {
+ goto err_undo_rename;
+ }
- ret = local_rename(ctx, old_full_name.data, new_full_name.data);
- v9fs_string_free(&old_full_name);
- v9fs_string_free(&new_full_name);
+ omap_dirfd = openat(odirfd, VIRTFS_META_DIR,
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (omap_dirfd == -1) {
+ goto err;
+ }
+
+ nmap_dirfd = openat(ndirfd, VIRTFS_META_DIR,
+ O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ if (nmap_dirfd == -1) {
+ close_preserve_errno(omap_dirfd);
+ goto err;
+ }
+
+ /* rename the .virtfs_metadata files */
+ ret = renameat(omap_dirfd, old_name, nmap_dirfd, new_name);
+ close_preserve_errno(nmap_dirfd);
+ close_preserve_errno(omap_dirfd);
+ if (ret < 0 && errno != ENOENT) {
+ goto err_undo_rename;
+ }
+
+ ret = 0;
+ }
+ goto out;
+
+err:
+ ret = -1;
+err_undo_rename:
+ renameat_preserve_errno(ndirfd, new_name, odirfd, old_name);
+out:
+ close_preserve_errno(ndirfd);
+ close_preserve_errno(odirfd);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 19/28] 9pfs: local: rename: use renameat
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (17 preceding siblings ...)
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 18/28] 9pfs: local: renameat: " Greg Kurz
@ 2017-02-26 22:44 ` Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 20/28] 9pfs: local: improve error handling in link op Greg Kurz
` (11 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:44 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_rename() callback is vulnerable to symlink attacks because it
uses rename() which follows symbolic links in all path elements but the
rightmost one.
This patch simply transforms local_rename() into a wrapper around
local_renameat() which is symlink-attack safe.
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 57 +++++++++++++++++++++++++---------------------------
1 file changed, 27 insertions(+), 30 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 1c378c369733..442d0475c7cd 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -964,36 +964,6 @@ static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
return ret;
}
-static int local_rename(FsContext *ctx, const char *oldpath,
- const char *newpath)
-{
- int err;
- char *buffer, *buffer1;
-
- if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- err = local_create_mapped_attr_dir(ctx, newpath);
- if (err < 0) {
- return err;
- }
- /* rename the .virtfs_metadata files */
- buffer = local_mapped_attr_path(ctx, oldpath);
- buffer1 = local_mapped_attr_path(ctx, newpath);
- err = rename(buffer, buffer1);
- g_free(buffer);
- g_free(buffer1);
- if (err < 0 && errno != ENOENT) {
- return err;
- }
- }
-
- buffer = rpath(ctx, oldpath);
- buffer1 = rpath(ctx, newpath);
- err = rename(buffer, buffer1);
- g_free(buffer);
- g_free(buffer1);
- return err;
-}
-
static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
{
char *buffer;
@@ -1254,6 +1224,33 @@ out:
return ret;
}
+static void v9fs_path_init_dirname(V9fsPath *path, const char *str)
+{
+ path->data = g_path_get_dirname(str);
+ path->size = strlen(path->data) + 1;
+}
+
+static int local_rename(FsContext *ctx, const char *oldpath,
+ const char *newpath)
+{
+ int err;
+ char *oname = g_path_get_basename(oldpath);
+ char *nname = g_path_get_basename(newpath);
+ V9fsPath olddir, newdir;
+
+ v9fs_path_init_dirname(&olddir, oldpath);
+ v9fs_path_init_dirname(&newdir, newpath);
+
+ err = local_renameat(ctx, &olddir, oname, &newdir, nname);
+
+ v9fs_path_free(&newdir);
+ v9fs_path_free(&olddir);
+ g_free(nname);
+ g_free(oname);
+
+ return err;
+}
+
static int local_unlinkat(FsContext *ctx, V9fsPath *dir,
const char *name, int flags)
{
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 20/28] 9pfs: local: improve error handling in link op
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (18 preceding siblings ...)
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 19/28] 9pfs: local: rename: use renameat Greg Kurz
@ 2017-02-26 22:44 ` Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 21/28] 9pfs: local: link: don't follow symlinks Greg Kurz
` (10 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:44 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
When using the mapped-file security model, we also have to create a link
for the metadata file if it exists. In case of failure, we should rollback.
That's what this patch does.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - use openat_dir()
---
hw/9pfs/9p-local.c | 32 +++++++++++++++++++++-----------
1 file changed, 21 insertions(+), 11 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 442d0475c7cd..03f4f5e913c6 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -920,6 +920,7 @@ static int local_link(FsContext *ctx, V9fsPath *oldpath,
int ret;
V9fsString newpath;
char *buffer, *buffer1;
+ int serrno;
v9fs_string_init(&newpath);
v9fs_string_sprintf(&newpath, "%s/%s", dirpath->data, name);
@@ -928,25 +929,36 @@ static int local_link(FsContext *ctx, V9fsPath *oldpath,
buffer1 = rpath(ctx, newpath.data);
ret = link(buffer, buffer1);
g_free(buffer);
- g_free(buffer1);
+ if (ret < 0) {
+ goto out;
+ }
/* now link the virtfs_metadata files */
- if (!ret && (ctx->export_flags & V9FS_SM_MAPPED_FILE)) {
+ if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ char *vbuffer, *vbuffer1;
+
/* Link the .virtfs_metadata files. Create the metada directory */
ret = local_create_mapped_attr_dir(ctx, newpath.data);
if (ret < 0) {
goto err_out;
}
- buffer = local_mapped_attr_path(ctx, oldpath->data);
- buffer1 = local_mapped_attr_path(ctx, newpath.data);
- ret = link(buffer, buffer1);
- g_free(buffer);
- g_free(buffer1);
+ vbuffer = local_mapped_attr_path(ctx, oldpath->data);
+ vbuffer1 = local_mapped_attr_path(ctx, newpath.data);
+ ret = link(vbuffer, vbuffer1);
+ g_free(vbuffer);
+ g_free(vbuffer1);
if (ret < 0 && errno != ENOENT) {
goto err_out;
}
}
+ goto out;
+
err_out:
+ serrno = errno;
+ remove(buffer1);
+ errno = serrno;
+out:
+ g_free(buffer1);
v9fs_string_free(&newpath);
return ret;
}
@@ -1189,14 +1201,12 @@ static int local_renameat(FsContext *ctx, V9fsPath *olddir,
goto err_undo_rename;
}
- omap_dirfd = openat(odirfd, VIRTFS_META_DIR,
- O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
if (omap_dirfd == -1) {
goto err;
}
- nmap_dirfd = openat(ndirfd, VIRTFS_META_DIR,
- O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
+ nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
if (nmap_dirfd == -1) {
close_preserve_errno(omap_dirfd);
goto err;
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 21/28] 9pfs: local: link: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (19 preceding siblings ...)
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 20/28] 9pfs: local: improve error handling in link op Greg Kurz
@ 2017-02-26 22:44 ` Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 22/28] 9pfs: local: chmod: " Greg Kurz
` (9 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:44 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_link() callback is vulnerable to symlink attacks because it calls:
(1) link() which follows symbolic links for all path elements but the
rightmost one
(2) local_create_mapped_attr_dir()->mkdir() which follows symbolic links
for all path elements but the rightmost one
This patch converts local_link() to rely on opendir_nofollow() and linkat()
to fix (1), mkdirat() to fix (2).
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - use openat_dir()
---
hw/9pfs/9p-local.c | 84 ++++++++++++++++++++++++++++++++++------------------
1 file changed, 55 insertions(+), 29 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 03f4f5e913c6..27781a8afed7 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -75,6 +75,13 @@ static void renameat_preserve_errno(int odirfd, const char *opath, int ndirfd,
errno = serrno;
}
+static void unlinkat_preserve_errno(int dirfd, const char *path, int flags)
+{
+ int serrno = errno;
+ unlinkat(dirfd, path, flags);
+ errno = serrno;
+}
+
#define VIRTFS_META_DIR ".virtfs_metadata"
static char *local_mapped_attr_path(FsContext *ctx, const char *path)
@@ -917,49 +924,68 @@ out:
static int local_link(FsContext *ctx, V9fsPath *oldpath,
V9fsPath *dirpath, const char *name)
{
- int ret;
- V9fsString newpath;
- char *buffer, *buffer1;
- int serrno;
+ char *odirpath = g_path_get_dirname(oldpath->data);
+ char *oname = g_path_get_basename(oldpath->data);
+ int ret = -1;
+ int odirfd, ndirfd;
- v9fs_string_init(&newpath);
- v9fs_string_sprintf(&newpath, "%s/%s", dirpath->data, name);
+ odirfd = local_opendir_nofollow(ctx, odirpath);
+ if (odirfd == -1) {
+ goto out;
+ }
- buffer = rpath(ctx, oldpath->data);
- buffer1 = rpath(ctx, newpath.data);
- ret = link(buffer, buffer1);
- g_free(buffer);
- if (ret < 0) {
+ ndirfd = local_opendir_nofollow(ctx, dirpath->data);
+ if (ndirfd == -1) {
+ close_preserve_errno(odirfd);
goto out;
}
+ ret = linkat(odirfd, oname, ndirfd, name, 0);
+ if (ret < 0) {
+ goto out_close;
+ }
+
/* now link the virtfs_metadata files */
if (ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- char *vbuffer, *vbuffer1;
+ int omap_dirfd, nmap_dirfd;
- /* Link the .virtfs_metadata files. Create the metada directory */
- ret = local_create_mapped_attr_dir(ctx, newpath.data);
- if (ret < 0) {
- goto err_out;
+ ret = mkdirat(ndirfd, VIRTFS_META_DIR, 0700);
+ if (ret < 0 && errno != EEXIST) {
+ goto err_undo_link;
}
- vbuffer = local_mapped_attr_path(ctx, oldpath->data);
- vbuffer1 = local_mapped_attr_path(ctx, newpath.data);
- ret = link(vbuffer, vbuffer1);
- g_free(vbuffer);
- g_free(vbuffer1);
+
+ omap_dirfd = openat_dir(odirfd, VIRTFS_META_DIR);
+ if (omap_dirfd == -1) {
+ goto err;
+ }
+
+ nmap_dirfd = openat_dir(ndirfd, VIRTFS_META_DIR);
+ if (nmap_dirfd == -1) {
+ close_preserve_errno(omap_dirfd);
+ goto err;
+ }
+
+ ret = linkat(omap_dirfd, oname, nmap_dirfd, name, 0);
+ close_preserve_errno(nmap_dirfd);
+ close_preserve_errno(omap_dirfd);
if (ret < 0 && errno != ENOENT) {
- goto err_out;
+ goto err_undo_link;
}
+
+ ret = 0;
}
- goto out;
+ goto out_close;
-err_out:
- serrno = errno;
- remove(buffer1);
- errno = serrno;
+err:
+ ret = -1;
+err_undo_link:
+ unlinkat_preserve_errno(ndirfd, name, 0);
+out_close:
+ close_preserve_errno(ndirfd);
+ close_preserve_errno(odirfd);
out:
- g_free(buffer1);
- v9fs_string_free(&newpath);
+ g_free(oname);
+ g_free(odirpath);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 22/28] 9pfs: local: chmod: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (20 preceding siblings ...)
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 21/28] 9pfs: local: link: don't follow symlinks Greg Kurz
@ 2017-02-26 22:44 ` Greg Kurz
2017-02-27 13:17 ` Stefan Hajnoczi
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 23/28] 9pfs: local: chown: " Greg Kurz
` (8 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:44 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_chmod() callback is vulnerable to symlink attacks because it
calls:
(1) chmod() which follows symbolic links for all path elements
(2) local_set_xattr()->setxattr() which follows symbolic links for all
path elements
(3) local_set_mapped_file_attr() which calls in turn local_fopen() and
mkdir(), both functions following symbolic links for all path
elements but the rightmost one
We would need fchmodat() to implement AT_SYMLINK_NOFOLLOW to fix (1). This
isn't the case on linux unfortunately: the kernel doesn't even have a flags
argument to the syscall :-\ It is impossible to fix it in userspace in
a race-free manner. This patch hence converts local_chmod() to rely on
open_nofollow() and fchmod(). This fixes the vulnerability but introduces
a limitation: the target file must readable and/or writable for the call
to openat() to succeed.
It introduces a local_set_xattrat() replacement to local_set_xattr()
based on fsetxattrat() to fix (2), and a local_set_mapped_file_attrat()
replacement to local_set_mapped_file_attr() based on local_fopenat()
and mkdirat() to fix (3). No effort is made to factor out code because
both local_set_xattr() and local_set_mapped_file_attr() will be dropped
when all users have been converted to use the "at" versions.
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - use openat_dir()
- updated the changelog and added a comment for fchmod()
---
hw/9pfs/9p-local.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 167 insertions(+), 11 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 27781a8afed7..72d219ec3d2b 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -367,6 +367,155 @@ static int local_set_xattr(const char *path, FsCred *credp)
return 0;
}
+static int local_set_mapped_file_attrat(int dirfd, const char *name,
+ FsCred *credp)
+{
+ FILE *fp;
+ int ret;
+ char buf[ATTR_MAX];
+ int uid = -1, gid = -1, mode = -1, rdev = -1;
+ int map_dirfd;
+
+ ret = mkdirat(dirfd, VIRTFS_META_DIR, 0700);
+ if (ret < 0 && errno != EEXIST) {
+ return -1;
+ }
+
+ map_dirfd = openat_dir(dirfd, VIRTFS_META_DIR);
+ if (map_dirfd == -1) {
+ return -1;
+ }
+
+ fp = local_fopenat(map_dirfd, name, "r");
+ if (!fp) {
+ if (errno == ENOENT) {
+ goto update_map_file;
+ } else {
+ close_preserve_errno(map_dirfd);
+ return -1;
+ }
+ }
+ memset(buf, 0, ATTR_MAX);
+ while (fgets(buf, ATTR_MAX, fp)) {
+ if (!strncmp(buf, "virtfs.uid", 10)) {
+ uid = atoi(buf + 11);
+ } else if (!strncmp(buf, "virtfs.gid", 10)) {
+ gid = atoi(buf + 11);
+ } else if (!strncmp(buf, "virtfs.mode", 11)) {
+ mode = atoi(buf + 12);
+ } else if (!strncmp(buf, "virtfs.rdev", 11)) {
+ rdev = atoi(buf + 12);
+ }
+ memset(buf, 0, ATTR_MAX);
+ }
+ fclose(fp);
+
+update_map_file:
+ fp = local_fopenat(map_dirfd, name, "w");
+ close_preserve_errno(map_dirfd);
+ if (!fp) {
+ return -1;
+ }
+
+ if (credp->fc_uid != -1) {
+ uid = credp->fc_uid;
+ }
+ if (credp->fc_gid != -1) {
+ gid = credp->fc_gid;
+ }
+ if (credp->fc_mode != -1) {
+ mode = credp->fc_mode;
+ }
+ if (credp->fc_rdev != -1) {
+ rdev = credp->fc_rdev;
+ }
+
+ if (uid != -1) {
+ fprintf(fp, "virtfs.uid=%d\n", uid);
+ }
+ if (gid != -1) {
+ fprintf(fp, "virtfs.gid=%d\n", gid);
+ }
+ if (mode != -1) {
+ fprintf(fp, "virtfs.mode=%d\n", mode);
+ }
+ if (rdev != -1) {
+ fprintf(fp, "virtfs.rdev=%d\n", rdev);
+ }
+ fclose(fp);
+
+ return 0;
+}
+
+static int fchmodat_nofollow(int dirfd, const char *name, mode_t mode)
+{
+ int fd, ret;
+
+ /* FIXME: this should be handled with fchmodat(AT_SYMLINK_NOFOLLOW).
+ * Unfortunately, the linux kernel doesn't implement it yet. As an
+ * alternative, let's open the file and use fchmod() instead. This
+ * may fail depending on the permissions of the file, but it is the
+ * best we can do to avoid TOCTTOU. We first try to open read-only
+ * in case name points to a directory. If that fails, we try write-only
+ * in case name doesn't point to a directory.
+ */
+ fd = openat_file(dirfd, name, O_RDONLY, 0);
+ if (fd == -1) {
+ /* In case the file is writable-only and isn't a directory. */
+ if (errno == EACCES) {
+ fd = openat_file(dirfd, name, O_WRONLY, 0);
+ }
+ if (fd == -1 && errno == EISDIR) {
+ errno = EACCES;
+ }
+ }
+ if (fd == -1) {
+ return -1;
+ }
+ ret = fchmod(fd, mode);
+ close_preserve_errno(fd);
+ return ret;
+}
+
+static int local_set_xattrat(int dirfd, const char *path, FsCred *credp)
+{
+ int err;
+
+ if (credp->fc_uid != -1) {
+ uint32_t tmp_uid = cpu_to_le32(credp->fc_uid);
+ err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.uid", &tmp_uid,
+ sizeof(uid_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ if (credp->fc_gid != -1) {
+ uint32_t tmp_gid = cpu_to_le32(credp->fc_gid);
+ err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.gid", &tmp_gid,
+ sizeof(gid_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ if (credp->fc_mode != -1) {
+ uint32_t tmp_mode = cpu_to_le32(credp->fc_mode);
+ err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.mode", &tmp_mode,
+ sizeof(mode_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ if (credp->fc_rdev != -1) {
+ uint64_t tmp_rdev = cpu_to_le64(credp->fc_rdev);
+ err = fsetxattrat_nofollow(dirfd, path, "user.virtfs.rdev", &tmp_rdev,
+ sizeof(dev_t), 0);
+ if (err) {
+ return err;
+ }
+ }
+ return 0;
+}
+
static int local_post_create_passthrough(FsContext *fs_ctx, const char *path,
FsCred *credp)
{
@@ -558,22 +707,29 @@ static ssize_t local_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
{
- char *buffer;
+ char *dirpath = g_path_get_dirname(fs_path->data);
+ char *name = g_path_get_basename(fs_path->data);
int ret = -1;
- char *path = fs_path->data;
+ int dirfd;
+
+ dirfd = local_opendir_nofollow(fs_ctx, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
- buffer = rpath(fs_ctx, path);
- ret = local_set_xattr(buffer, credp);
- g_free(buffer);
+ ret = local_set_xattrat(dirfd, name, credp);
} else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- return local_set_mapped_file_attr(fs_ctx, path, credp);
- } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
- (fs_ctx->export_flags & V9FS_SM_NONE)) {
- buffer = rpath(fs_ctx, path);
- ret = chmod(buffer, credp->fc_mode);
- g_free(buffer);
+ ret = local_set_mapped_file_attrat(dirfd, name, credp);
+ } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
+ fs_ctx->export_flags & V9FS_SM_NONE) {
+ ret = fchmodat_nofollow(dirfd, name, credp->fc_mode);
}
+ close_preserve_errno(dirfd);
+
+out:
+ g_free(dirpath);
+ g_free(name);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 23/28] 9pfs: local: chown: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (21 preceding siblings ...)
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 22/28] 9pfs: local: chmod: " Greg Kurz
@ 2017-02-26 22:44 ` Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 24/28] 9pfs: local: symlink: " Greg Kurz
` (7 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:44 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_chown() callback is vulnerable to symlink attacks because it
calls:
(1) lchown() which follows symbolic links for all path elements but the
rightmost one
(2) local_set_xattr()->setxattr() which follows symbolic links for all
path elements
(3) local_set_mapped_file_attr() which calls in turn local_fopen() and
mkdir(), both functions following symbolic links for all path
elements but the rightmost one
This patch converts local_chown() to rely on open_nofollow() and
fchownat() to fix (1), as well as local_set_xattrat() and
local_set_mapped_file_attrat() to fix (2) and (3) respectively.
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 26 +++++++++++++++++---------
1 file changed, 17 insertions(+), 9 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 72d219ec3d2b..0bacf722edfe 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -1160,23 +1160,31 @@ static int local_truncate(FsContext *ctx, V9fsPath *fs_path, off_t size)
static int local_chown(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
{
- char *buffer;
+ char *dirpath = g_path_get_dirname(fs_path->data);
+ char *name = g_path_get_basename(fs_path->data);
int ret = -1;
- char *path = fs_path->data;
+ int dirfd;
+
+ dirfd = local_opendir_nofollow(fs_ctx, dirpath);
+ if (dirfd == -1) {
+ goto out;
+ }
if ((credp->fc_uid == -1 && credp->fc_gid == -1) ||
(fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
(fs_ctx->export_flags & V9FS_SM_NONE)) {
- buffer = rpath(fs_ctx, path);
- ret = lchown(buffer, credp->fc_uid, credp->fc_gid);
- g_free(buffer);
+ ret = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
+ AT_SYMLINK_NOFOLLOW);
} else if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
- buffer = rpath(fs_ctx, path);
- ret = local_set_xattr(buffer, credp);
- g_free(buffer);
+ ret = local_set_xattrat(dirfd, name, credp);
} else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- return local_set_mapped_file_attr(fs_ctx, path, credp);
+ ret = local_set_mapped_file_attrat(dirfd, name, credp);
}
+
+ close_preserve_errno(dirfd);
+out:
+ g_free(name);
+ g_free(dirpath);
return ret;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 24/28] 9pfs: local: symlink: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (22 preceding siblings ...)
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 23/28] 9pfs: local: chown: " Greg Kurz
@ 2017-02-26 22:44 ` Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 25/28] 9pfs: local: mknod: " Greg Kurz
` (6 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:44 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_symlink() callback is vulnerable to symlink attacks because it
calls:
(1) symlink() which follows symbolic links for all path elements but the
rightmost one
(2) open(O_NOFOLLOW) which follows symbolic links for all path elements but
the rightmost one
(3) local_set_xattr()->setxattr() which follows symbolic links for all
path elements
(4) local_set_mapped_file_attr() which calls in turn local_fopen() and
mkdir(), both functions following symbolic links for all path
elements but the rightmost one
This patch converts local_symlink() to rely on opendir_nofollow() and
symlinkat() to fix (1), openat(O_NOFOLLOW) to fix (2), as well as
local_set_xattrat() and local_set_mapped_file_attrat() to fix (3) and
(4) respectively.
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
v2: - use openat_file()
---
hw/9pfs/9p-local.c | 81 ++++++++++++++++------------------------------------
1 file changed, 25 insertions(+), 56 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 0bacf722edfe..b87cb7defca5 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -978,23 +978,22 @@ static int local_symlink(FsContext *fs_ctx, const char *oldpath,
V9fsPath *dir_path, const char *name, FsCred *credp)
{
int err = -1;
- int serrno = 0;
- char *newpath;
- V9fsString fullname;
- char *buffer = NULL;
+ int dirfd;
- v9fs_string_init(&fullname);
- v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
- newpath = fullname.data;
+ dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
+ if (dirfd == -1) {
+ return -1;
+ }
/* Determine the security model */
- if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
+ fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
int fd;
ssize_t oldpath_size, write_size;
- buffer = rpath(fs_ctx, newpath);
- fd = open(buffer, O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW, SM_LOCAL_MODE_BITS);
+
+ fd = openat_file(dirfd, name, O_CREAT | O_EXCL | O_RDWR,
+ SM_LOCAL_MODE_BITS);
if (fd == -1) {
- err = fd;
goto out;
}
/* Write the oldpath (target) to the file. */
@@ -1002,78 +1001,48 @@ static int local_symlink(FsContext *fs_ctx, const char *oldpath,
do {
write_size = write(fd, (void *)oldpath, oldpath_size);
} while (write_size == -1 && errno == EINTR);
+ close_preserve_errno(fd);
if (write_size != oldpath_size) {
- serrno = errno;
- close(fd);
- err = -1;
goto err_end;
}
- close(fd);
/* Set cleint credentials in symlink's xattr */
- credp->fc_mode = credp->fc_mode|S_IFLNK;
- err = local_set_xattr(buffer, credp);
- if (err == -1) {
- serrno = errno;
- goto err_end;
- }
- } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- int fd;
- ssize_t oldpath_size, write_size;
- buffer = rpath(fs_ctx, newpath);
- fd = open(buffer, O_CREAT|O_EXCL|O_RDWR|O_NOFOLLOW, SM_LOCAL_MODE_BITS);
- if (fd == -1) {
- err = fd;
- goto out;
- }
- /* Write the oldpath (target) to the file. */
- oldpath_size = strlen(oldpath);
- do {
- write_size = write(fd, (void *)oldpath, oldpath_size);
- } while (write_size == -1 && errno == EINTR);
+ credp->fc_mode = credp->fc_mode | S_IFLNK;
- if (write_size != oldpath_size) {
- serrno = errno;
- close(fd);
- err = -1;
- goto err_end;
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ err = local_set_xattrat(dirfd, name, credp);
+ } else {
+ err = local_set_mapped_file_attrat(dirfd, name, credp);
}
- close(fd);
- /* Set cleint credentials in symlink's xattr */
- credp->fc_mode = credp->fc_mode|S_IFLNK;
- err = local_set_mapped_file_attr(fs_ctx, newpath, credp);
if (err == -1) {
- serrno = errno;
goto err_end;
}
- } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
- (fs_ctx->export_flags & V9FS_SM_NONE)) {
- buffer = rpath(fs_ctx, newpath);
- err = symlink(oldpath, buffer);
+ } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
+ fs_ctx->export_flags & V9FS_SM_NONE) {
+ err = symlinkat(oldpath, dirfd, name);
if (err) {
goto out;
}
- err = lchown(buffer, credp->fc_uid, credp->fc_gid);
+ err = fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
+ AT_SYMLINK_NOFOLLOW);
if (err == -1) {
/*
* If we fail to change ownership and if we are
* using security model none. Ignore the error
*/
if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
- serrno = errno;
goto err_end;
- } else
+ } else {
err = 0;
+ }
}
}
goto out;
err_end:
- remove(buffer);
- errno = serrno;
+ unlinkat_preserve_errno(dirfd, name, 0);
out:
- g_free(buffer);
- v9fs_string_free(&fullname);
+ close_preserve_errno(dirfd);
return err;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 25/28] 9pfs: local: mknod: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (23 preceding siblings ...)
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 24/28] 9pfs: local: symlink: " Greg Kurz
@ 2017-02-26 22:44 ` Greg Kurz
2017-02-27 13:18 ` Stefan Hajnoczi
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 26/28] 9pfs: local: mkdir: " Greg Kurz
` (5 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:44 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_mknod() callback is vulnerable to symlink attacks because it
calls:
(1) mknod() which follows symbolic links for all path elements but the
rightmost one
(2) local_set_xattr()->setxattr() which follows symbolic links for all
path elements
(3) local_set_mapped_file_attr() which calls in turn local_fopen() and
mkdir(), both functions following symbolic links for all path
elements but the rightmost one
(4) local_post_create_passthrough() which calls in turn lchown() and
chmod(), both functions also following symbolic links
This patch converts local_mknod() to rely on opendir_nofollow() and
mknodat() to fix (1), as well as local_set_xattrat() and
local_set_mapped_file_attrat() to fix (2) and (3) respectively.
A new local_set_cred_passthrough() helper based on fchownat() and
fchmodat_nofollow() is introduced as a replacement to
local_post_create_passthrough() to fix (4).
The mapped and mapped-file security modes are supposed to be identical,
except for the place where credentials and file modes are stored. While
here, we also make that explicit by sharing the call to mknodat().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - use fchmodat_nofollow()
---
hw/9pfs/9p-local.c | 68 +++++++++++++++++++++++++++-------------------------
1 file changed, 35 insertions(+), 33 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index b87cb7defca5..4766175dda72 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -543,6 +543,23 @@ err:
return -1;
}
+static int local_set_cred_passthrough(FsContext *fs_ctx, int dirfd,
+ const char *name, FsCred *credp)
+{
+ if (fchownat(dirfd, name, credp->fc_uid, credp->fc_gid,
+ AT_SYMLINK_NOFOLLOW | AT_EMPTY_PATH) < 0) {
+ /*
+ * If we fail to change ownership and if we are
+ * using security model none. Ignore the error
+ */
+ if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
+ return -1;
+ }
+ }
+
+ return fchmodat_nofollow(dirfd, name, credp->fc_mode & 07777);
+}
+
static ssize_t local_readlink(FsContext *fs_ctx, V9fsPath *fs_path,
char *buf, size_t bufsz)
{
@@ -736,61 +753,46 @@ out:
static int local_mknod(FsContext *fs_ctx, V9fsPath *dir_path,
const char *name, FsCred *credp)
{
- char *path;
int err = -1;
- int serrno = 0;
- V9fsString fullname;
- char *buffer = NULL;
+ int dirfd;
- v9fs_string_init(&fullname);
- v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
- path = fullname.data;
+ dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
+ if (dirfd == -1) {
+ return -1;
+ }
- /* Determine the security model */
- if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
- buffer = rpath(fs_ctx, path);
- err = mknod(buffer, SM_LOCAL_MODE_BITS|S_IFREG, 0);
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
+ fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ err = mknodat(dirfd, name, SM_LOCAL_MODE_BITS | S_IFREG, 0);
if (err == -1) {
goto out;
}
- err = local_set_xattr(buffer, credp);
- if (err == -1) {
- serrno = errno;
- goto err_end;
- }
- } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- buffer = rpath(fs_ctx, path);
- err = mknod(buffer, SM_LOCAL_MODE_BITS|S_IFREG, 0);
- if (err == -1) {
- goto out;
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ err = local_set_xattrat(dirfd, name, credp);
+ } else {
+ err = local_set_mapped_file_attrat(dirfd, name, credp);
}
- err = local_set_mapped_file_attr(fs_ctx, path, credp);
if (err == -1) {
- serrno = errno;
goto err_end;
}
- } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
- (fs_ctx->export_flags & V9FS_SM_NONE)) {
- buffer = rpath(fs_ctx, path);
- err = mknod(buffer, credp->fc_mode, credp->fc_rdev);
+ } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
+ fs_ctx->export_flags & V9FS_SM_NONE) {
+ err = mknodat(dirfd, name, credp->fc_mode, credp->fc_rdev);
if (err == -1) {
goto out;
}
- err = local_post_create_passthrough(fs_ctx, path, credp);
+ err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
if (err == -1) {
- serrno = errno;
goto err_end;
}
}
goto out;
err_end:
- remove(buffer);
- errno = serrno;
+ unlinkat_preserve_errno(dirfd, name, 0);
out:
- g_free(buffer);
- v9fs_string_free(&fullname);
+ close_preserve_errno(dirfd);
return err;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 26/28] 9pfs: local: mkdir: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (24 preceding siblings ...)
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 25/28] 9pfs: local: mknod: " Greg Kurz
@ 2017-02-26 22:45 ` Greg Kurz
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 27/28] 9pfs: local: open2: " Greg Kurz
` (4 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:45 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_mkdir() callback is vulnerable to symlink attacks because it
calls:
(1) mkdir() which follows symbolic links for all path elements but the
rightmost one
(2) local_set_xattr()->setxattr() which follows symbolic links for all
path elements
(3) local_set_mapped_file_attr() which calls in turn local_fopen() and
mkdir(), both functions following symbolic links for all path
elements but the rightmost one
(4) local_post_create_passthrough() which calls in turn lchown() and
chmod(), both functions also following symbolic links
This patch converts local_mkdir() to rely on opendir_nofollow() and
mkdirat() to fix (1), as well as local_set_xattrat(),
local_set_mapped_file_attrat() and local_set_cred_passthrough() to
fix (2), (3) and (4) respectively.
The mapped and mapped-file security modes are supposed to be identical,
except for the place where credentials and file modes are stored. While
here, we also make that explicit by sharing the call to mkdirat().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 55 +++++++++++++++++++---------------------------------
1 file changed, 20 insertions(+), 35 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 4766175dda72..9b28bb530ae9 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -799,62 +799,47 @@ out:
static int local_mkdir(FsContext *fs_ctx, V9fsPath *dir_path,
const char *name, FsCred *credp)
{
- char *path;
int err = -1;
- int serrno = 0;
- V9fsString fullname;
- char *buffer = NULL;
+ int dirfd;
- v9fs_string_init(&fullname);
- v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
- path = fullname.data;
+ dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
+ if (dirfd == -1) {
+ return -1;
+ }
- /* Determine the security model */
- if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
- buffer = rpath(fs_ctx, path);
- err = mkdir(buffer, SM_LOCAL_DIR_MODE_BITS);
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
+ fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ err = mkdirat(dirfd, name, SM_LOCAL_DIR_MODE_BITS);
if (err == -1) {
goto out;
}
- credp->fc_mode = credp->fc_mode|S_IFDIR;
- err = local_set_xattr(buffer, credp);
- if (err == -1) {
- serrno = errno;
- goto err_end;
- }
- } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- buffer = rpath(fs_ctx, path);
- err = mkdir(buffer, SM_LOCAL_DIR_MODE_BITS);
- if (err == -1) {
- goto out;
+ credp->fc_mode = credp->fc_mode | S_IFDIR;
+
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ err = local_set_xattrat(dirfd, name, credp);
+ } else {
+ err = local_set_mapped_file_attrat(dirfd, name, credp);
}
- credp->fc_mode = credp->fc_mode|S_IFDIR;
- err = local_set_mapped_file_attr(fs_ctx, path, credp);
if (err == -1) {
- serrno = errno;
goto err_end;
}
- } else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
- (fs_ctx->export_flags & V9FS_SM_NONE)) {
- buffer = rpath(fs_ctx, path);
- err = mkdir(buffer, credp->fc_mode);
+ } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
+ fs_ctx->export_flags & V9FS_SM_NONE) {
+ err = mkdirat(dirfd, name, credp->fc_mode);
if (err == -1) {
goto out;
}
- err = local_post_create_passthrough(fs_ctx, path, credp);
+ err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
if (err == -1) {
- serrno = errno;
goto err_end;
}
}
goto out;
err_end:
- remove(buffer);
- errno = serrno;
+ unlinkat_preserve_errno(dirfd, name, AT_REMOVEDIR);
out:
- g_free(buffer);
- v9fs_string_free(&fullname);
+ close_preserve_errno(dirfd);
return err;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 27/28] 9pfs: local: open2: don't follow symlinks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (25 preceding siblings ...)
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 26/28] 9pfs: local: mkdir: " Greg Kurz
@ 2017-02-26 22:45 ` Greg Kurz
2017-02-27 13:18 ` Stefan Hajnoczi
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 28/28] 9pfs: local: drop unused code Greg Kurz
` (3 subsequent siblings)
30 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:45 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
The local_open2() callback is vulnerable to symlink attacks because it
calls:
(1) open() which follows symbolic links for all path elements but the
rightmost one
(2) local_set_xattr()->setxattr() which follows symbolic links for all
path elements
(3) local_set_mapped_file_attr() which calls in turn local_fopen() and
mkdir(), both functions following symbolic links for all path
elements but the rightmost one
(4) local_post_create_passthrough() which calls in turn lchown() and
chmod(), both functions also following symbolic links
This patch converts local_open2() to rely on opendir_nofollow() and
mkdirat() to fix (1), as well as local_set_xattrat(),
local_set_mapped_file_attrat() and local_set_cred_passthrough() to
fix (2), (3) and (4) respectively. Since local_open2() already opens
a descriptor to the target file, local_set_cred_passthrough() is
modified to reuse it instead of opening a new one.
The mapped and mapped-file security modes are supposed to be identical,
except for the place where credentials and file modes are stored. While
here, we also make that explicit by sharing the call to openat().
This partly fixes CVE-2016-9602.
Signed-off-by: Greg Kurz <groug@kaod.org>
---
v2: - use openat_file()
- pass dirfd and name to local_set_cred_passthrough()
---
hw/9pfs/9p-local.c | 56 ++++++++++++++++++----------------------------------
1 file changed, 19 insertions(+), 37 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index 9b28bb530ae9..da1c141fc840 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -887,62 +887,45 @@ static int local_fstat(FsContext *fs_ctx, int fid_type,
static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
int flags, FsCred *credp, V9fsFidOpenState *fs)
{
- char *path;
int fd = -1;
int err = -1;
- int serrno = 0;
- V9fsString fullname;
- char *buffer = NULL;
+ int dirfd;
/*
* Mark all the open to not follow symlinks
*/
flags |= O_NOFOLLOW;
- v9fs_string_init(&fullname);
- v9fs_string_sprintf(&fullname, "%s/%s", dir_path->data, name);
- path = fullname.data;
+ dirfd = local_opendir_nofollow(fs_ctx, dir_path->data);
+ if (dirfd == -1) {
+ return -1;
+ }
/* Determine the security model */
- if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
- buffer = rpath(fs_ctx, path);
- fd = open(buffer, flags, SM_LOCAL_MODE_BITS);
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
+ fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
+ fd = openat_file(dirfd, name, flags, SM_LOCAL_MODE_BITS);
if (fd == -1) {
- err = fd;
goto out;
}
credp->fc_mode = credp->fc_mode|S_IFREG;
- /* Set cleint credentials in xattr */
- err = local_set_xattr(buffer, credp);
- if (err == -1) {
- serrno = errno;
- goto err_end;
- }
- } else if (fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
- buffer = rpath(fs_ctx, path);
- fd = open(buffer, flags, SM_LOCAL_MODE_BITS);
- if (fd == -1) {
- err = fd;
- goto out;
+ if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
+ /* Set cleint credentials in xattr */
+ err = local_set_xattrat(dirfd, name, credp);
+ } else {
+ err = local_set_mapped_file_attrat(dirfd, name, credp);
}
- credp->fc_mode = credp->fc_mode|S_IFREG;
- /* Set client credentials in .virtfs_metadata directory files */
- err = local_set_mapped_file_attr(fs_ctx, path, credp);
if (err == -1) {
- serrno = errno;
goto err_end;
}
} else if ((fs_ctx->export_flags & V9FS_SM_PASSTHROUGH) ||
(fs_ctx->export_flags & V9FS_SM_NONE)) {
- buffer = rpath(fs_ctx, path);
- fd = open(buffer, flags, credp->fc_mode);
+ fd = openat_file(dirfd, name, flags, credp->fc_mode);
if (fd == -1) {
- err = fd;
goto out;
}
- err = local_post_create_passthrough(fs_ctx, path, credp);
+ err = local_set_cred_passthrough(fs_ctx, dirfd, name, credp);
if (err == -1) {
- serrno = errno;
goto err_end;
}
}
@@ -951,12 +934,11 @@ static int local_open2(FsContext *fs_ctx, V9fsPath *dir_path, const char *name,
goto out;
err_end:
- close(fd);
- remove(buffer);
- errno = serrno;
+ unlinkat_preserve_errno(dirfd, name,
+ flags & O_DIRECTORY ? AT_REMOVEDIR : 0);
+ close_preserve_errno(fd);
out:
- g_free(buffer);
- v9fs_string_free(&fullname);
+ close_preserve_errno(dirfd);
return err;
}
^ permalink raw reply related [flat|nested] 47+ messages in thread
* [Qemu-devel] [PATCH v2 28/28] 9pfs: local: drop unused code
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (26 preceding siblings ...)
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 27/28] 9pfs: local: open2: " Greg Kurz
@ 2017-02-26 22:45 ` Greg Kurz
2017-02-26 23:45 ` [Qemu-devel] [PATCH v2 00/28] 9pfs: local: fix vulnerability to symlink attacks Greg Kurz
` (2 subsequent siblings)
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 22:45 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Greg Kurz, Aneesh Kumar K.V,
Stefan Hajnoczi, Eric Blake
Now that the all callbacks have been converted to use "at" syscalls, we
can drop this code.
Signed-off-by: Greg Kurz <groug@kaod.org>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
hw/9pfs/9p-local.c | 198 ----------------------------------------------------
1 file changed, 198 deletions(-)
diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
index da1c141fc840..428d1bf0adc4 100644
--- a/hw/9pfs/9p-local.c
+++ b/hw/9pfs/9p-local.c
@@ -84,48 +84,6 @@ static void unlinkat_preserve_errno(int dirfd, const char *path, int flags)
#define VIRTFS_META_DIR ".virtfs_metadata"
-static char *local_mapped_attr_path(FsContext *ctx, const char *path)
-{
- int dirlen;
- const char *name = strrchr(path, '/');
- if (name) {
- dirlen = name - path;
- ++name;
- } else {
- name = path;
- dirlen = 0;
- }
- return g_strdup_printf("%s/%.*s/%s/%s", ctx->fs_root,
- dirlen, path, VIRTFS_META_DIR, name);
-}
-
-static FILE *local_fopen(const char *path, const char *mode)
-{
- int fd, o_mode = 0;
- FILE *fp;
- int flags = O_NOFOLLOW;
- /*
- * only supports two modes
- */
- if (mode[0] == 'r') {
- flags |= O_RDONLY;
- } else if (mode[0] == 'w') {
- flags |= O_WRONLY | O_TRUNC | O_CREAT;
- o_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
- } else {
- return NULL;
- }
- fd = open(path, flags, o_mode);
- if (fd == -1) {
- return NULL;
- }
- fp = fdopen(fd, mode);
- if (!fp) {
- close(fd);
- }
- return fp;
-}
-
static FILE *local_fopenat(int dirfd, const char *name, const char *mode)
{
int fd, o_mode = 0;
@@ -238,135 +196,6 @@ out:
return err;
}
-static int local_create_mapped_attr_dir(FsContext *ctx, const char *path)
-{
- int err;
- char *attr_dir;
- char *tmp_path = g_strdup(path);
-
- attr_dir = g_strdup_printf("%s/%s/%s",
- ctx->fs_root, dirname(tmp_path), VIRTFS_META_DIR);
-
- err = mkdir(attr_dir, 0700);
- if (err < 0 && errno == EEXIST) {
- err = 0;
- }
- g_free(attr_dir);
- g_free(tmp_path);
- return err;
-}
-
-static int local_set_mapped_file_attr(FsContext *ctx,
- const char *path, FsCred *credp)
-{
- FILE *fp;
- int ret = 0;
- char buf[ATTR_MAX];
- char *attr_path;
- int uid = -1, gid = -1, mode = -1, rdev = -1;
-
- attr_path = local_mapped_attr_path(ctx, path);
- fp = local_fopen(attr_path, "r");
- if (!fp) {
- goto create_map_file;
- }
- memset(buf, 0, ATTR_MAX);
- while (fgets(buf, ATTR_MAX, fp)) {
- if (!strncmp(buf, "virtfs.uid", 10)) {
- uid = atoi(buf+11);
- } else if (!strncmp(buf, "virtfs.gid", 10)) {
- gid = atoi(buf+11);
- } else if (!strncmp(buf, "virtfs.mode", 11)) {
- mode = atoi(buf+12);
- } else if (!strncmp(buf, "virtfs.rdev", 11)) {
- rdev = atoi(buf+12);
- }
- memset(buf, 0, ATTR_MAX);
- }
- fclose(fp);
- goto update_map_file;
-
-create_map_file:
- ret = local_create_mapped_attr_dir(ctx, path);
- if (ret < 0) {
- goto err_out;
- }
-
-update_map_file:
- fp = local_fopen(attr_path, "w");
- if (!fp) {
- ret = -1;
- goto err_out;
- }
-
- if (credp->fc_uid != -1) {
- uid = credp->fc_uid;
- }
- if (credp->fc_gid != -1) {
- gid = credp->fc_gid;
- }
- if (credp->fc_mode != -1) {
- mode = credp->fc_mode;
- }
- if (credp->fc_rdev != -1) {
- rdev = credp->fc_rdev;
- }
-
-
- if (uid != -1) {
- fprintf(fp, "virtfs.uid=%d\n", uid);
- }
- if (gid != -1) {
- fprintf(fp, "virtfs.gid=%d\n", gid);
- }
- if (mode != -1) {
- fprintf(fp, "virtfs.mode=%d\n", mode);
- }
- if (rdev != -1) {
- fprintf(fp, "virtfs.rdev=%d\n", rdev);
- }
- fclose(fp);
-
-err_out:
- g_free(attr_path);
- return ret;
-}
-
-static int local_set_xattr(const char *path, FsCred *credp)
-{
- int err;
-
- if (credp->fc_uid != -1) {
- uint32_t tmp_uid = cpu_to_le32(credp->fc_uid);
- err = setxattr(path, "user.virtfs.uid", &tmp_uid, sizeof(uid_t), 0);
- if (err) {
- return err;
- }
- }
- if (credp->fc_gid != -1) {
- uint32_t tmp_gid = cpu_to_le32(credp->fc_gid);
- err = setxattr(path, "user.virtfs.gid", &tmp_gid, sizeof(gid_t), 0);
- if (err) {
- return err;
- }
- }
- if (credp->fc_mode != -1) {
- uint32_t tmp_mode = cpu_to_le32(credp->fc_mode);
- err = setxattr(path, "user.virtfs.mode", &tmp_mode, sizeof(mode_t), 0);
- if (err) {
- return err;
- }
- }
- if (credp->fc_rdev != -1) {
- uint64_t tmp_rdev = cpu_to_le64(credp->fc_rdev);
- err = setxattr(path, "user.virtfs.rdev", &tmp_rdev, sizeof(dev_t), 0);
- if (err) {
- return err;
- }
- }
- return 0;
-}
-
static int local_set_mapped_file_attrat(int dirfd, const char *name,
FsCred *credp)
{
@@ -516,33 +345,6 @@ static int local_set_xattrat(int dirfd, const char *path, FsCred *credp)
return 0;
}
-static int local_post_create_passthrough(FsContext *fs_ctx, const char *path,
- FsCred *credp)
-{
- char *buffer;
-
- buffer = rpath(fs_ctx, path);
- if (lchown(buffer, credp->fc_uid, credp->fc_gid) < 0) {
- /*
- * If we fail to change ownership and if we are
- * using security model none. Ignore the error
- */
- if ((fs_ctx->export_flags & V9FS_SEC_MASK) != V9FS_SM_NONE) {
- goto err;
- }
- }
-
- if (chmod(buffer, credp->fc_mode & 07777) < 0) {
- goto err;
- }
-
- g_free(buffer);
- return 0;
-err:
- g_free(buffer);
- return -1;
-}
-
static int local_set_cred_passthrough(FsContext *fs_ctx, int dirfd,
const char *name, FsCred *credp)
{
^ permalink raw reply related [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 00/28] 9pfs: local: fix vulnerability to symlink attacks
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (27 preceding siblings ...)
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 28/28] 9pfs: local: drop unused code Greg Kurz
@ 2017-02-26 23:45 ` Greg Kurz
2017-02-27 13:24 ` [Qemu-devel] [PATCH v2 00/28] Series short description Stefan Hajnoczi
2017-02-27 15:33 ` Stefan Hajnoczi
30 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-26 23:45 UTC (permalink / raw)
To: qemu-devel
Cc: Jann Horn, Prasad J Pandit, Aneesh Kumar K.V, Stefan Hajnoczi,
Eric Blake
[-- Attachment #1: Type: text/plain, Size: 4179 bytes --]
On Sun, 26 Feb 2017 23:41:32 +0100
Greg Kurz <groug@kaod.org> wrote:
> This series tries to fix CVE-2016-9602 reported by Jann Horn of Google
> Project Zero:
>
> https://bugzilla.redhat.com/show_bug.cgi?id=1413929
>
> This vulnerability affects all accesses to the underlying filesystem in
> the "local" backend code.
>
> If QEMU is started with:
>
> -fsdev local,security_model=<passthrough|none>,path=/foo/bar
>
> then the guest can cause QEMU to create symlinks in /foo/bar.
>
> This causes accesses to any path /foo/bar/some/path to be unsafe, since
> untrusted code within the guest (or in another guest sharing the same
> virtfs folder) could change some/path to point to a random path of the
> host filesystem.
>
> The core problem is that the "local" backend relies on path-based syscalls
> to access the underlying filesystem. All path-based syscalls are vulnerable
> to this issue, even open(O_NOFOLLOW) or syscalls that explicitly don't
> dereference symlinks, since the kernel only checks the rightmost element of
> the path. Depending on the privilege level of the QEMU process, a guest can
> end up opening, renaming, changing ACLs, unlinking... files on the host
> filesystem.
>
> The right way to address this is to use "at" variants of all syscalls in
> the "local" backend code. This requires to open directories without
> traversing any symlink in the intermediate path elements. There was a
> tentative to introduce an O_BENEATH flag for openat() that would address
> this:
>
> https://patchwork.kernel.org/patch/7007181/
>
> Unfortunately this never got merged. An alternative is to walk through all
> path elements manually with openat(O_NOFOLLOW).
>
> v2:
> - /proc based implementation for xattr code (fixes metadata perf drop
> observed with v1)
> - some code refactoring
>
> Stefan.
>
> I had to rework some patches you had already reviewed, please consider
> giving your Reviewed-by again if the changes are ok.
>
> Thanks.
>
> --
> Greg
>
> ---
>
> Greg Kurz (28):
> 9pfs: local: move xattr security ops to 9p-xattr.c
> 9pfs: remove side-effects in local_init()
> 9pfs: remove side-effects in local_open() and local_opendir()
> 9pfs: introduce openat_nofollow() helper
> 9pfs: local: keep a file descriptor on the shared folder
> 9pfs: local: open/opendir: don't follow symlinks
> 9pfs: local: lgetxattr: don't follow symlinks
> 9pfs: local: llistxattr: don't follow symlinks
> 9pfs: local: lsetxattr: don't follow symlinks
> 9pfs: local: lremovexattr: don't follow symlinks
> 9pfs: local: unlinkat: don't follow symlinks
> 9pfs: local: remove: don't follow symlinks
> 9pfs: local: utimensat: don't follow symlinks
> 9pfs: local: statfs: don't follow symlinks
> 9pfs: local: truncate: don't follow symlinks
> 9pfs: local: readlink: don't follow symlinks
> 9pfs: local: lstat: don't follow symlinks
> 9pfs: local: renameat: don't follow symlinks
> 9pfs: local: rename: use renameat
> 9pfs: local: improve error handling in link op
> 9pfs: local: link: don't follow symlinks
> 9pfs: local: chmod: don't follow symlinks
> 9pfs: local: chown: don't follow symlinks
> 9pfs: local: symlink: don't follow symlinks
> 9pfs: local: mknod: don't follow symlinks
> 9pfs: local: mkdir: don't follow symlinks
> 9pfs: local: open2: don't follow symlinks
> 9pfs: local: drop unused code
>
>
> hw/9pfs/9p-local.c | 1023 ++++++++++++++++++++++++++---------------------
> hw/9pfs/9p-local.h | 20 +
> hw/9pfs/9p-posix-acl.c | 44 --
> hw/9pfs/9p-util.c | 65 +++
> hw/9pfs/9p-util.h | 52 ++
> hw/9pfs/9p-xattr-user.c | 24 -
> hw/9pfs/9p-xattr.c | 166 +++++++-
> hw/9pfs/9p-xattr.h | 87 +---
> hw/9pfs/Makefile.objs | 2
> 9 files changed, 887 insertions(+), 596 deletions(-)
> create mode 100644 hw/9pfs/9p-local.h
> create mode 100644 hw/9pfs/9p-util.c
> create mode 100644 hw/9pfs/9p-util.h
>
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper Greg Kurz
@ 2017-02-27 12:44 ` Stefan Hajnoczi
2017-02-27 14:31 ` Greg Kurz
2017-02-27 23:28 ` Eric Blake
1 sibling, 1 reply; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 12:44 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1360 bytes --]
On Sun, Feb 26, 2017 at 11:42:03PM +0100, Greg Kurz wrote:
> +int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
> +{
> + int fd;
> +
> + fd = dup(dirfd);
> + if (fd == -1) {
> + return -1;
> + }
> +
> + while (*path) {
> + const char *c;
> + int next_fd;
> + char *head;
> +
> + head = g_strdup(path);
> + c = strchr(path, '/');
> + if (c) {
> + head[c - path] = 0;
> + next_fd = openat_dir(fd, head);
> + } else {
> + next_fd = openat_file(fd, head, flags, mode);
> + }
> + g_free(head);
> + if (next_fd == -1) {
> + close_preserve_errno(fd);
> + return -1;
> + }
> + close(fd);
> + fd = next_fd;
> +
> + if (!c) {
> + break;
> + }
> + path = c + 1;
> + }
> +
> + return fd;
> +}
If I understand the Linux openat(2) implementation correctly this
function fails with ENOENT if:
1. An absolute path is given
2. A path contains consecutive slashes ("a///b")
Both of these behaviors are problematic. If the function doesn't
support absolute paths it should be called relative_openat_nofollow()
and have an error if path[0] == '/'.
I believe guests can pass in paths with consecutive slashes, so the
function must cope with them.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 06/28] 9pfs: local: open/opendir: don't follow symlinks
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 06/28] 9pfs: local: open/opendir: don't follow symlinks Greg Kurz
@ 2017-02-27 12:49 ` Stefan Hajnoczi
2017-02-27 14:35 ` Greg Kurz
0 siblings, 1 reply; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 12:49 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 893 bytes --]
On Sun, Feb 26, 2017 at 11:42:18PM +0100, Greg Kurz wrote:
> @@ -48,6 +49,24 @@ typedef struct {
> int mountfd;
> } LocalData;
>
> +int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
> + mode_t mode)
> +{
> + LocalData *data = fs_ctx->private;
> +
> + /* All paths are relative to the path data->mountfd points to */
> + while (*path == '/') {
> + path++;
> + }
> +
> + return openat_nofollow(data->mountfd, path, flags, mode);
What about all the other openat_nofollow() users? They don't explicitly
strip leading slashes. Perhaps this should be part of a renamed
relative_openat_nofollow() function.
> +}
> +
> +int local_opendir_nofollow(FsContext *fs_ctx, const char *path)
> +{
> + return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0);
Why not strip slashes here?
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 07/28] 9pfs: local: lgetxattr: don't follow symlinks
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 07/28] 9pfs: local: lgetxattr: " Greg Kurz
@ 2017-02-27 12:58 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 12:58 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1074 bytes --]
On Sun, Feb 26, 2017 at 11:42:26PM +0100, Greg Kurz wrote:
> The local_lgetxattr() callback is vulnerable to symlink attacks because
> it calls lgetxattr() which follows symbolic links in all path elements but
> the rightmost one.
>
> This patch introduces a helper to emulate the non-existing fgetxattrat()
> function: it is implemented with /proc/self/fd which provides a trusted
> path that can be safely passed to lgetxattr().
>
> local_lgetxattr() is converted to use this helper and opendir_nofollow().
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> ---
> v2: - introduce /proc based fgetxattrat_nofollow()
> ---
> hw/9pfs/9p-posix-acl.c | 16 ++--------------
> hw/9pfs/9p-util.c | 12 ++++++++++++
> hw/9pfs/9p-util.h | 2 ++
> hw/9pfs/9p-xattr-user.c | 8 +-------
> hw/9pfs/9p-xattr.c | 31 ++++++++++++++++++++++++-------
> hw/9pfs/9p-xattr.h | 2 ++
> 6 files changed, 43 insertions(+), 28 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 08/28] 9pfs: local: llistxattr: don't follow symlinks
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 08/28] 9pfs: local: llistxattr: " Greg Kurz
@ 2017-02-27 13:08 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:08 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 857 bytes --]
On Sun, Feb 26, 2017 at 11:42:34PM +0100, Greg Kurz wrote:
> The local_llistxattr() callback is vulnerable to symlink attacks because
> it calls llistxattr() which follows symbolic links in all path elements but
> the rightmost one.
>
> This patch introduces a helper to emulate the non-existing flistxattrat()
> function: it is implemented with /proc/self/fd which provides a trusted
> path that can be safely passed to llistxattr().
>
> local_llistxattr() is converted to use this helper and opendir_nofollow().
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> ---
> v2: - introduce /proc based flistxattrat_nofollow()
> ---
> hw/9pfs/9p-xattr.c | 35 +++++++++++++++++++++++++++++------
> 1 file changed, 29 insertions(+), 6 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 09/28] 9pfs: local: lsetxattr: don't follow symlinks
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 09/28] 9pfs: local: lsetxattr: " Greg Kurz
@ 2017-02-27 13:10 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:10 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1037 bytes --]
On Sun, Feb 26, 2017 at 11:42:43PM +0100, Greg Kurz wrote:
> The local_lsetxattr() callback is vulnerable to symlink attacks because
> it calls lsetxattr() which follows symbolic links in all path elements but
> the rightmost one.
>
> This patch introduces a helper to emulate the non-existing fsetxattrat()
> function: it is implemented with /proc/self/fd which provides a trusted
> path that can be safely passed to lsetxattr().
>
> local_lsetxattr() is converted to use this helper and opendir_nofollow().
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> ---
> v2: - introduce /proc based fsetxattrat_nofollow()
> ---
> hw/9pfs/9p-posix-acl.c | 18 ++++--------------
> hw/9pfs/9p-util.h | 2 ++
> hw/9pfs/9p-xattr-user.c | 8 +-------
> hw/9pfs/9p-xattr.c | 39 +++++++++++++++++++++++++++++++++------
> hw/9pfs/9p-xattr.h | 3 +++
> 5 files changed, 43 insertions(+), 27 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 10/28] 9pfs: local: lremovexattr: don't follow symlinks
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 10/28] 9pfs: local: lremovexattr: " Greg Kurz
@ 2017-02-27 13:12 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:12 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1067 bytes --]
On Sun, Feb 26, 2017 at 11:42:51PM +0100, Greg Kurz wrote:
> The local_lremovexattr() callback is vulnerable to symlink attacks because
> it calls lremovexattr() which follows symbolic links in all path elements
> but the rightmost one.
>
> This patch introduces a helper to emulate the non-existing fremovexattrat()
> function: it is implemented with /proc/self/fd which provides a trusted
> path that can be safely passed to lremovexattr().
>
> local_lremovexattr() is converted to use this helper and opendir_nofollow().
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> ---
> v2: - introduce /proc based fremovexattrat_nofollow()
> - fix arguments passed to local_removexattr_nofollow()
> ---
> hw/9pfs/9p-posix-acl.c | 10 ++--------
> hw/9pfs/9p-xattr-user.c | 8 +-------
> hw/9pfs/9p-xattr.c | 36 +++++++++++++++++++++++++++++++-----
> hw/9pfs/9p-xattr.h | 2 ++
> 4 files changed, 36 insertions(+), 20 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 11/28] 9pfs: local: unlinkat: don't follow symlinks
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 11/28] 9pfs: local: unlinkat: " Greg Kurz
@ 2017-02-27 13:14 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:14 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 874 bytes --]
On Sun, Feb 26, 2017 at 11:43:00PM +0100, Greg Kurz wrote:
> The local_unlinkat() callback is vulnerable to symlink attacks because it
> calls remove() which follows symbolic links in all path elements but the
> rightmost one.
>
> This patch converts local_unlinkat() to rely on opendir_nofollow() and
> unlinkat() instead.
>
> Most of the code is moved to a separate local_unlinkat_common() helper
> which will be reused in a subsequent patch to fix the same issue in
> local_remove().
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
> ---
> v2: - use openat_dir()
> ---
> hw/9pfs/9p-local.c | 99 +++++++++++++++++++++++++++++-----------------------
> 1 file changed, 56 insertions(+), 43 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 22/28] 9pfs: local: chmod: don't follow symlinks
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 22/28] 9pfs: local: chmod: " Greg Kurz
@ 2017-02-27 13:17 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:17 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1800 bytes --]
On Sun, Feb 26, 2017 at 11:44:28PM +0100, Greg Kurz wrote:
> The local_chmod() callback is vulnerable to symlink attacks because it
> calls:
>
> (1) chmod() which follows symbolic links for all path elements
> (2) local_set_xattr()->setxattr() which follows symbolic links for all
> path elements
> (3) local_set_mapped_file_attr() which calls in turn local_fopen() and
> mkdir(), both functions following symbolic links for all path
> elements but the rightmost one
>
> We would need fchmodat() to implement AT_SYMLINK_NOFOLLOW to fix (1). This
> isn't the case on linux unfortunately: the kernel doesn't even have a flags
> argument to the syscall :-\ It is impossible to fix it in userspace in
> a race-free manner. This patch hence converts local_chmod() to rely on
> open_nofollow() and fchmod(). This fixes the vulnerability but introduces
> a limitation: the target file must readable and/or writable for the call
> to openat() to succeed.
>
> It introduces a local_set_xattrat() replacement to local_set_xattr()
> based on fsetxattrat() to fix (2), and a local_set_mapped_file_attrat()
> replacement to local_set_mapped_file_attr() based on local_fopenat()
> and mkdirat() to fix (3). No effort is made to factor out code because
> both local_set_xattr() and local_set_mapped_file_attr() will be dropped
> when all users have been converted to use the "at" versions.
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> ---
> v2: - use openat_dir()
> - updated the changelog and added a comment for fchmod()
> ---
> hw/9pfs/9p-local.c | 178 +++++++++++++++++++++++++++++++++++++++++++++++++---
> 1 file changed, 167 insertions(+), 11 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 25/28] 9pfs: local: mknod: don't follow symlinks
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 25/28] 9pfs: local: mknod: " Greg Kurz
@ 2017-02-27 13:18 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:18 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1582 bytes --]
On Sun, Feb 26, 2017 at 11:44:54PM +0100, Greg Kurz wrote:
> The local_mknod() callback is vulnerable to symlink attacks because it
> calls:
>
> (1) mknod() which follows symbolic links for all path elements but the
> rightmost one
> (2) local_set_xattr()->setxattr() which follows symbolic links for all
> path elements
> (3) local_set_mapped_file_attr() which calls in turn local_fopen() and
> mkdir(), both functions following symbolic links for all path
> elements but the rightmost one
> (4) local_post_create_passthrough() which calls in turn lchown() and
> chmod(), both functions also following symbolic links
>
> This patch converts local_mknod() to rely on opendir_nofollow() and
> mknodat() to fix (1), as well as local_set_xattrat() and
> local_set_mapped_file_attrat() to fix (2) and (3) respectively.
>
> A new local_set_cred_passthrough() helper based on fchownat() and
> fchmodat_nofollow() is introduced as a replacement to
> local_post_create_passthrough() to fix (4).
>
> The mapped and mapped-file security modes are supposed to be identical,
> except for the place where credentials and file modes are stored. While
> here, we also make that explicit by sharing the call to mknodat().
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> ---
> v2: - use fchmodat_nofollow()
> ---
> hw/9pfs/9p-local.c | 68 +++++++++++++++++++++++++++-------------------------
> 1 file changed, 35 insertions(+), 33 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 27/28] 9pfs: local: open2: don't follow symlinks
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 27/28] 9pfs: local: open2: " Greg Kurz
@ 2017-02-27 13:18 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:18 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1652 bytes --]
On Sun, Feb 26, 2017 at 11:45:09PM +0100, Greg Kurz wrote:
> The local_open2() callback is vulnerable to symlink attacks because it
> calls:
>
> (1) open() which follows symbolic links for all path elements but the
> rightmost one
> (2) local_set_xattr()->setxattr() which follows symbolic links for all
> path elements
> (3) local_set_mapped_file_attr() which calls in turn local_fopen() and
> mkdir(), both functions following symbolic links for all path
> elements but the rightmost one
> (4) local_post_create_passthrough() which calls in turn lchown() and
> chmod(), both functions also following symbolic links
>
> This patch converts local_open2() to rely on opendir_nofollow() and
> mkdirat() to fix (1), as well as local_set_xattrat(),
> local_set_mapped_file_attrat() and local_set_cred_passthrough() to
> fix (2), (3) and (4) respectively. Since local_open2() already opens
> a descriptor to the target file, local_set_cred_passthrough() is
> modified to reuse it instead of opening a new one.
>
> The mapped and mapped-file security modes are supposed to be identical,
> except for the place where credentials and file modes are stored. While
> here, we also make that explicit by sharing the call to openat().
>
> This partly fixes CVE-2016-9602.
>
> Signed-off-by: Greg Kurz <groug@kaod.org>
> ---
> v2: - use openat_file()
> - pass dirfd and name to local_set_cred_passthrough()
> ---
> hw/9pfs/9p-local.c | 56 ++++++++++++++++++----------------------------------
> 1 file changed, 19 insertions(+), 37 deletions(-)
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 00/28] Series short description
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (28 preceding siblings ...)
2017-02-26 23:45 ` [Qemu-devel] [PATCH v2 00/28] 9pfs: local: fix vulnerability to symlink attacks Greg Kurz
@ 2017-02-27 13:24 ` Stefan Hajnoczi
2017-02-27 15:33 ` Stefan Hajnoczi
30 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 13:24 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 2122 bytes --]
On Sun, Feb 26, 2017 at 11:41:32PM +0100, Greg Kurz wrote:
> This series tries to fix CVE-2016-9602 reported by Jann Horn of Google
> Project Zero:
>
> https://bugzilla.redhat.com/show_bug.cgi?id=1413929
>
> This vulnerability affects all accesses to the underlying filesystem in
> the "local" backend code.
>
> If QEMU is started with:
>
> -fsdev local,security_model=<passthrough|none>,path=/foo/bar
>
> then the guest can cause QEMU to create symlinks in /foo/bar.
>
> This causes accesses to any path /foo/bar/some/path to be unsafe, since
> untrusted code within the guest (or in another guest sharing the same
> virtfs folder) could change some/path to point to a random path of the
> host filesystem.
>
> The core problem is that the "local" backend relies on path-based syscalls
> to access the underlying filesystem. All path-based syscalls are vulnerable
> to this issue, even open(O_NOFOLLOW) or syscalls that explicitly don't
> dereference symlinks, since the kernel only checks the rightmost element of
> the path. Depending on the privilege level of the QEMU process, a guest can
> end up opening, renaming, changing ACLs, unlinking... files on the host
> filesystem.
>
> The right way to address this is to use "at" variants of all syscalls in
> the "local" backend code. This requires to open directories without
> traversing any symlink in the intermediate path elements. There was a
> tentative to introduce an O_BENEATH flag for openat() that would address
> this:
>
> https://patchwork.kernel.org/patch/7007181/
>
> Unfortunately this never got merged. An alternative is to walk through all
> path elements manually with openat(O_NOFOLLOW).
>
> v2:
> - /proc based implementation for xattr code (fixes metadata perf drop
> observed with v1)
> - some code refactoring
>
> Stefan.
>
> I had to rework some patches you had already reviewed, please consider
> giving your Reviewed-by again if the changes are ok.
I have reviewed patches that didn't have R-b from me. Please see
comments on individual patches.
Stefan
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper
2017-02-27 12:44 ` Stefan Hajnoczi
@ 2017-02-27 14:31 ` Greg Kurz
2017-02-27 15:32 ` Stefan Hajnoczi
0 siblings, 1 reply; 47+ messages in thread
From: Greg Kurz @ 2017-02-27 14:31 UTC (permalink / raw)
To: Stefan Hajnoczi
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 3343 bytes --]
On Mon, 27 Feb 2017 12:44:30 +0000
Stefan Hajnoczi <stefanha@gmail.com> wrote:
> On Sun, Feb 26, 2017 at 11:42:03PM +0100, Greg Kurz wrote:
> > +int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
> > +{
> > + int fd;
> > +
> > + fd = dup(dirfd);
> > + if (fd == -1) {
> > + return -1;
> > + }
> > +
> > + while (*path) {
> > + const char *c;
> > + int next_fd;
> > + char *head;
> > +
> > + head = g_strdup(path);
> > + c = strchr(path, '/');
> > + if (c) {
> > + head[c - path] = 0;
> > + next_fd = openat_dir(fd, head);
> > + } else {
> > + next_fd = openat_file(fd, head, flags, mode);
> > + }
> > + g_free(head);
> > + if (next_fd == -1) {
> > + close_preserve_errno(fd);
> > + return -1;
> > + }
> > + close(fd);
> > + fd = next_fd;
> > +
> > + if (!c) {
> > + break;
> > + }
> > + path = c + 1;
> > + }
> > +
> > + return fd;
> > +}
>
> If I understand the Linux openat(2) implementation correctly this
> function fails with ENOENT if:
>
> 1. An absolute path is given
> 2. A path contains consecutive slashes ("a///b")
>
> Both of these behaviors are problematic. If the function doesn't
> support absolute paths it should be called relative_openat_nofollow()
> and have an error if path[0] == '/'.
>
I agree with the name change since we don't want this function to deal
with absolute names, per-design. It shouldn't even return an error but
rather assert (see below for more details).
> I believe guests can pass in paths with consecutive slashes, so the
> function must cope with them.
Actually no. The 9P protocol implements paths as an array of path
elements, and the frontend prohibits '/' characters in individual
path elements (look for name_is_illegal in hw/9pfs/9p.c).
We cannot have consecutive '/' characters in the intermediate part or at
the end of the path. But we do have '//' at the beginning.
This lies lies in the way the frontend internally forges paths to access
files.
A typical path is in the form:
${root}/${elem1}/${elem2}/${elem3}/ ... etc... /${elemN}
where ${root} is set to '/' when the client mounts the 9pfs share (see
v9fs_attach() in hw/9pfs/9p.c). Since all paths are supposed to be
relative to the path of shared directory on the host, I believe ${root}
should rather be '.' than '/'. Unfortunately, this contradicts this
snippet of code in the handle backend code (hw/9pfs/9p-handle.c):
static int handle_name_to_path(FsContext *ctx, V9fsPath *dir_path,
const char *name, V9fsPath *target)
{
[...]
/* "." and ".." are not allowed */
if (!strcmp(name, ".") || !strcmp(name, "..")) {
errno = EINVAL;
return -1;
}
I should probably dig some more to see why it is coded that way. But since
I couldn't reproduce the vulnerability with the handle backend and this series
is already huge, I've opted to keep the hardcoded '/' in v9fs_attach().
The consequence is that I have to strip these leading '/' when calling openat().
But then the code can safely assume that paths don't have consecutive '/'.
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 06/28] 9pfs: local: open/opendir: don't follow symlinks
2017-02-27 12:49 ` Stefan Hajnoczi
@ 2017-02-27 14:35 ` Greg Kurz
0 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-27 14:35 UTC (permalink / raw)
To: Stefan Hajnoczi
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 1382 bytes --]
On Mon, 27 Feb 2017 12:49:01 +0000
Stefan Hajnoczi <stefanha@gmail.com> wrote:
> On Sun, Feb 26, 2017 at 11:42:18PM +0100, Greg Kurz wrote:
> > @@ -48,6 +49,24 @@ typedef struct {
> > int mountfd;
> > } LocalData;
> >
> > +int local_open_nofollow(FsContext *fs_ctx, const char *path, int flags,
> > + mode_t mode)
> > +{
> > + LocalData *data = fs_ctx->private;
> > +
> > + /* All paths are relative to the path data->mountfd points to */
> > + while (*path == '/') {
> > + path++;
> > + }
> > +
> > + return openat_nofollow(data->mountfd, path, flags, mode);
>
> What about all the other openat_nofollow() users? They don't explicitly
$ git grep openat_nofollow
hw/9pfs/9p-local.c: return openat_nofollow(data->mountfd, path, flags, mode);
hw/9pfs/9p-util.c:int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
hw/9pfs/9p-util.h:int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode);
> strip leading slashes. Perhaps this should be part of a renamed
> relative_openat_nofollow() function.
>
> > +}
> > +
> > +int local_opendir_nofollow(FsContext *fs_ctx, const char *path)
> > +{
> > + return local_open_nofollow(fs_ctx, path, O_DIRECTORY | O_RDONLY, 0);
>
> Why not strip slashes here?
Because this one calls the other one above.
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper
2017-02-27 14:31 ` Greg Kurz
@ 2017-02-27 15:32 ` Stefan Hajnoczi
0 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 15:32 UTC (permalink / raw)
To: Greg Kurz
Cc: Stefan Hajnoczi, qemu-devel, Jann Horn, Prasad J Pandit,
Aneesh Kumar K.V
[-- Attachment #1: Type: text/plain, Size: 3644 bytes --]
On Mon, Feb 27, 2017 at 03:31:47PM +0100, Greg Kurz wrote:
> On Mon, 27 Feb 2017 12:44:30 +0000
> Stefan Hajnoczi <stefanha@gmail.com> wrote:
>
> > On Sun, Feb 26, 2017 at 11:42:03PM +0100, Greg Kurz wrote:
> > > +int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
> > > +{
> > > + int fd;
> > > +
> > > + fd = dup(dirfd);
> > > + if (fd == -1) {
> > > + return -1;
> > > + }
> > > +
> > > + while (*path) {
> > > + const char *c;
> > > + int next_fd;
> > > + char *head;
> > > +
> > > + head = g_strdup(path);
> > > + c = strchr(path, '/');
> > > + if (c) {
> > > + head[c - path] = 0;
> > > + next_fd = openat_dir(fd, head);
> > > + } else {
> > > + next_fd = openat_file(fd, head, flags, mode);
> > > + }
> > > + g_free(head);
> > > + if (next_fd == -1) {
> > > + close_preserve_errno(fd);
> > > + return -1;
> > > + }
> > > + close(fd);
> > > + fd = next_fd;
> > > +
> > > + if (!c) {
> > > + break;
> > > + }
> > > + path = c + 1;
> > > + }
> > > +
> > > + return fd;
> > > +}
> >
> > If I understand the Linux openat(2) implementation correctly this
> > function fails with ENOENT if:
> >
> > 1. An absolute path is given
> > 2. A path contains consecutive slashes ("a///b")
> >
> > Both of these behaviors are problematic. If the function doesn't
> > support absolute paths it should be called relative_openat_nofollow()
> > and have an error if path[0] == '/'.
> >
>
> I agree with the name change since we don't want this function to deal
> with absolute names, per-design. It shouldn't even return an error but
> rather assert (see below for more details).
>
> > I believe guests can pass in paths with consecutive slashes, so the
> > function must cope with them.
>
> Actually no. The 9P protocol implements paths as an array of path
> elements, and the frontend prohibits '/' characters in individual
> path elements (look for name_is_illegal in hw/9pfs/9p.c).
>
> We cannot have consecutive '/' characters in the intermediate part or at
> the end of the path. But we do have '//' at the beginning.
>
> This lies lies in the way the frontend internally forges paths to access
> files.
>
> A typical path is in the form:
>
> ${root}/${elem1}/${elem2}/${elem3}/ ... etc... /${elemN}
>
> where ${root} is set to '/' when the client mounts the 9pfs share (see
> v9fs_attach() in hw/9pfs/9p.c). Since all paths are supposed to be
> relative to the path of shared directory on the host, I believe ${root}
> should rather be '.' than '/'. Unfortunately, this contradicts this
> snippet of code in the handle backend code (hw/9pfs/9p-handle.c):
>
> static int handle_name_to_path(FsContext *ctx, V9fsPath *dir_path,
> const char *name, V9fsPath *target)
> {
> [...]
> /* "." and ".." are not allowed */
> if (!strcmp(name, ".") || !strcmp(name, "..")) {
> errno = EINVAL;
> return -1;
>
> }
>
> I should probably dig some more to see why it is coded that way. But since
> I couldn't reproduce the vulnerability with the handle backend and this series
> is already huge, I've opted to keep the hardcoded '/' in v9fs_attach().
>
> The consequence is that I have to strip these leading '/' when calling openat().
>
> But then the code can safely assume that paths don't have consecutive '/'.
I see, thanks for explaining.
Stefan
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 00/28] Series short description
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
` (29 preceding siblings ...)
2017-02-27 13:24 ` [Qemu-devel] [PATCH v2 00/28] Series short description Stefan Hajnoczi
@ 2017-02-27 15:33 ` Stefan Hajnoczi
30 siblings, 0 replies; 47+ messages in thread
From: Stefan Hajnoczi @ 2017-02-27 15:33 UTC (permalink / raw)
To: Greg Kurz
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Eric Blake
[-- Attachment #1: Type: text/plain, Size: 4218 bytes --]
On Sun, Feb 26, 2017 at 11:41:32PM +0100, Greg Kurz wrote:
> This series tries to fix CVE-2016-9602 reported by Jann Horn of Google
> Project Zero:
>
> https://bugzilla.redhat.com/show_bug.cgi?id=1413929
>
> This vulnerability affects all accesses to the underlying filesystem in
> the "local" backend code.
>
> If QEMU is started with:
>
> -fsdev local,security_model=<passthrough|none>,path=/foo/bar
>
> then the guest can cause QEMU to create symlinks in /foo/bar.
>
> This causes accesses to any path /foo/bar/some/path to be unsafe, since
> untrusted code within the guest (or in another guest sharing the same
> virtfs folder) could change some/path to point to a random path of the
> host filesystem.
>
> The core problem is that the "local" backend relies on path-based syscalls
> to access the underlying filesystem. All path-based syscalls are vulnerable
> to this issue, even open(O_NOFOLLOW) or syscalls that explicitly don't
> dereference symlinks, since the kernel only checks the rightmost element of
> the path. Depending on the privilege level of the QEMU process, a guest can
> end up opening, renaming, changing ACLs, unlinking... files on the host
> filesystem.
>
> The right way to address this is to use "at" variants of all syscalls in
> the "local" backend code. This requires to open directories without
> traversing any symlink in the intermediate path elements. There was a
> tentative to introduce an O_BENEATH flag for openat() that would address
> this:
>
> https://patchwork.kernel.org/patch/7007181/
>
> Unfortunately this never got merged. An alternative is to walk through all
> path elements manually with openat(O_NOFOLLOW).
>
> v2:
> - /proc based implementation for xattr code (fixes metadata perf drop
> observed with v1)
> - some code refactoring
>
> Stefan.
>
> I had to rework some patches you had already reviewed, please consider
> giving your Reviewed-by again if the changes are ok.
>
> Thanks.
>
> --
> Greg
>
> ---
>
> Greg Kurz (28):
> 9pfs: local: move xattr security ops to 9p-xattr.c
> 9pfs: remove side-effects in local_init()
> 9pfs: remove side-effects in local_open() and local_opendir()
> 9pfs: introduce openat_nofollow() helper
> 9pfs: local: keep a file descriptor on the shared folder
> 9pfs: local: open/opendir: don't follow symlinks
> 9pfs: local: lgetxattr: don't follow symlinks
> 9pfs: local: llistxattr: don't follow symlinks
> 9pfs: local: lsetxattr: don't follow symlinks
> 9pfs: local: lremovexattr: don't follow symlinks
> 9pfs: local: unlinkat: don't follow symlinks
> 9pfs: local: remove: don't follow symlinks
> 9pfs: local: utimensat: don't follow symlinks
> 9pfs: local: statfs: don't follow symlinks
> 9pfs: local: truncate: don't follow symlinks
> 9pfs: local: readlink: don't follow symlinks
> 9pfs: local: lstat: don't follow symlinks
> 9pfs: local: renameat: don't follow symlinks
> 9pfs: local: rename: use renameat
> 9pfs: local: improve error handling in link op
> 9pfs: local: link: don't follow symlinks
> 9pfs: local: chmod: don't follow symlinks
> 9pfs: local: chown: don't follow symlinks
> 9pfs: local: symlink: don't follow symlinks
> 9pfs: local: mknod: don't follow symlinks
> 9pfs: local: mkdir: don't follow symlinks
> 9pfs: local: open2: don't follow symlinks
> 9pfs: local: drop unused code
>
>
> hw/9pfs/9p-local.c | 1023 ++++++++++++++++++++++++++---------------------
> hw/9pfs/9p-local.h | 20 +
> hw/9pfs/9p-posix-acl.c | 44 --
> hw/9pfs/9p-util.c | 65 +++
> hw/9pfs/9p-util.h | 52 ++
> hw/9pfs/9p-xattr-user.c | 24 -
> hw/9pfs/9p-xattr.c | 166 +++++++-
> hw/9pfs/9p-xattr.h | 87 +---
> hw/9pfs/Makefile.objs | 2
> 9 files changed, 887 insertions(+), 596 deletions(-)
> create mode 100644 hw/9pfs/9p-local.h
> create mode 100644 hw/9pfs/9p-util.c
> create mode 100644 hw/9pfs/9p-util.h
>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 455 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper Greg Kurz
2017-02-27 12:44 ` Stefan Hajnoczi
@ 2017-02-27 23:28 ` Eric Blake
2017-02-28 0:32 ` Greg Kurz
1 sibling, 1 reply; 47+ messages in thread
From: Eric Blake @ 2017-02-27 23:28 UTC (permalink / raw)
To: Greg Kurz, qemu-devel
Cc: Jann Horn, Prasad J Pandit, Aneesh Kumar K.V, Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 2448 bytes --]
On 02/26/2017 04:42 PM, Greg Kurz wrote:
> When using the passthrough security mode, symbolic links created by the
> guest are actual symbolic links on the host file system.
>
>
> diff --git a/hw/9pfs/9p-util.c b/hw/9pfs/9p-util.c
> new file mode 100644
> index 000000000000..62fd7a76212a
> --- /dev/null
> +int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
> +{
> + int fd;
> +
> + fd = dup(dirfd);
> + if (fd == -1) {
> + return -1;
> + }
> +
Do you want to assert that the caller's path does not start with '/'?
This function ignores dirfd in that case, which may not be what you want.
> + while (*path) {
> + const char *c;
> + int next_fd;
> + char *head;
> +
> + head = g_strdup(path);
> + c = strchr(path, '/');
So if the caller passes path="a//b", then the first iteration sets
head="a", but the second iteration sets head="".
> + if (c) {
> + head[c - path] = 0;
> + next_fd = openat_dir(fd, head);
The second iteration will then fail (openat_dir on "" should fail with
ENOENT, right?). Oops.
> + } else {
> + next_fd = openat_file(fd, head, flags, mode);
> + }
> + g_free(head);
> + if (next_fd == -1) {
> + close_preserve_errno(fd);
> + return -1;
> + }
> + close(fd);
> + fd = next_fd;
> +
> + if (!c) {
> + break;
> + }
> + path = c + 1;
I think the fix is that you should skip past all consecutive '/' here,
rather than assuming there is just one. Or can you assert that all
callers are well-behaved, and that *path is not '/' at this point?
> + }
> +static inline int openat_file(int dirfd, const char *name, int flags,
> + mode_t mode)
> +{
> + int fd, serrno;
> +
> + fd = openat(dirfd, name, flags | O_NOFOLLOW | O_NOCTTY | O_NONBLOCK,
> + mode);
> + if (fd == -1) {
> + return -1;
> + }
> +
> + serrno = errno;
> + /* O_NONBLOCK was only needed to open the file. Let's drop it. */
> + assert(!fcntl(fd, F_SETFL, flags));
Ouch - side effect inside an assertion. We don't support use of NDEBUG,
but this is poor practice.
--
Eric Blake eblake redhat com +1-919-301-3266
Libvirt virtualization library http://libvirt.org
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 604 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
* Re: [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper
2017-02-27 23:28 ` Eric Blake
@ 2017-02-28 0:32 ` Greg Kurz
0 siblings, 0 replies; 47+ messages in thread
From: Greg Kurz @ 2017-02-28 0:32 UTC (permalink / raw)
To: Eric Blake
Cc: qemu-devel, Jann Horn, Prasad J Pandit, Aneesh Kumar K.V,
Stefan Hajnoczi
[-- Attachment #1: Type: text/plain, Size: 2962 bytes --]
On Mon, 27 Feb 2017 17:28:33 -0600
Eric Blake <eblake@redhat.com> wrote:
> On 02/26/2017 04:42 PM, Greg Kurz wrote:
> > When using the passthrough security mode, symbolic links created by the
> > guest are actual symbolic links on the host file system.
> >
>
> >
> > diff --git a/hw/9pfs/9p-util.c b/hw/9pfs/9p-util.c
> > new file mode 100644
> > index 000000000000..62fd7a76212a
> > --- /dev/null
>
> > +int openat_nofollow(int dirfd, const char *path, int flags, mode_t mode)
> > +{
> > + int fd;
> > +
> > + fd = dup(dirfd);
> > + if (fd == -1) {
> > + return -1;
> > + }
> > +
>
> Do you want to assert that the caller's path does not start with '/'?
Yes, I've added this for the pull request.
> This function ignores dirfd in that case, which may not be what you want.
>
Indeed, it really needs the path to be relative.
> > + while (*path) {
> > + const char *c;
> > + int next_fd;
> > + char *head;
> > +
> > + head = g_strdup(path);
> > + c = strchr(path, '/');
>
> So if the caller passes path="a//b", then the first iteration sets
> head="a", but the second iteration sets head="".
>
This doesn't happen with the current code, but you're right, we should
assert here also. We only wany a/b/c/d
>
> > + if (c) {
> > + head[c - path] = 0;
> > + next_fd = openat_dir(fd, head);
>
> The second iteration will then fail (openat_dir on "" should fail with
> ENOENT, right?). Oops.
>
> > + } else {
> > + next_fd = openat_file(fd, head, flags, mode);
> > + }
> > + g_free(head);
> > + if (next_fd == -1) {
> > + close_preserve_errno(fd);
> > + return -1;
> > + }
> > + close(fd);
> > + fd = next_fd;
> > +
> > + if (!c) {
> > + break;
> > + }
> > + path = c + 1;
>
> I think the fix is that you should skip past all consecutive '/' here,
> rather than assuming there is just one. Or can you assert that all
> callers are well-behaved, and that *path is not '/' at this point?
>
Again you're right :-\
> > + }
>
> > +static inline int openat_file(int dirfd, const char *name, int flags,
> > + mode_t mode)
> > +{
> > + int fd, serrno;
> > +
> > + fd = openat(dirfd, name, flags | O_NOFOLLOW | O_NOCTTY | O_NONBLOCK,
> > + mode);
> > + if (fd == -1) {
> > + return -1;
> > + }
> > +
> > + serrno = errno;
> > + /* O_NONBLOCK was only needed to open the file. Let's drop it. */
> > + assert(!fcntl(fd, F_SETFL, flags));
>
> Ouch - side effect inside an assertion. We don't support use of NDEBUG,
> but this is poor practice.
>
And I now remember you already made a similar comment in the past... I hope
I will remember this time.
Thanks!
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]
^ permalink raw reply [flat|nested] 47+ messages in thread
end of thread, other threads:[~2017-02-28 0:32 UTC | newest]
Thread overview: 47+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2017-02-26 22:41 [Qemu-devel] [PATCH v2 00/28] Series short description Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 01/28] 9pfs: local: move xattr security ops to 9p-xattr.c Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 02/28] 9pfs: remove side-effects in local_init() Greg Kurz
2017-02-26 22:41 ` [Qemu-devel] [PATCH v2 03/28] 9pfs: remove side-effects in local_open() and local_opendir() Greg Kurz
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 04/28] 9pfs: introduce openat_nofollow() helper Greg Kurz
2017-02-27 12:44 ` Stefan Hajnoczi
2017-02-27 14:31 ` Greg Kurz
2017-02-27 15:32 ` Stefan Hajnoczi
2017-02-27 23:28 ` Eric Blake
2017-02-28 0:32 ` Greg Kurz
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 05/28] 9pfs: local: keep a file descriptor on the shared folder Greg Kurz
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 06/28] 9pfs: local: open/opendir: don't follow symlinks Greg Kurz
2017-02-27 12:49 ` Stefan Hajnoczi
2017-02-27 14:35 ` Greg Kurz
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 07/28] 9pfs: local: lgetxattr: " Greg Kurz
2017-02-27 12:58 ` Stefan Hajnoczi
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 08/28] 9pfs: local: llistxattr: " Greg Kurz
2017-02-27 13:08 ` Stefan Hajnoczi
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 09/28] 9pfs: local: lsetxattr: " Greg Kurz
2017-02-27 13:10 ` Stefan Hajnoczi
2017-02-26 22:42 ` [Qemu-devel] [PATCH v2 10/28] 9pfs: local: lremovexattr: " Greg Kurz
2017-02-27 13:12 ` Stefan Hajnoczi
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 11/28] 9pfs: local: unlinkat: " Greg Kurz
2017-02-27 13:14 ` Stefan Hajnoczi
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 12/28] 9pfs: local: remove: " Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 13/28] 9pfs: local: utimensat: " Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 14/28] 9pfs: local: statfs: " Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 15/28] 9pfs: local: truncate: " Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 16/28] 9pfs: local: readlink: " Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 17/28] 9pfs: local: lstat: " Greg Kurz
2017-02-26 22:43 ` [Qemu-devel] [PATCH v2 18/28] 9pfs: local: renameat: " Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 19/28] 9pfs: local: rename: use renameat Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 20/28] 9pfs: local: improve error handling in link op Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 21/28] 9pfs: local: link: don't follow symlinks Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 22/28] 9pfs: local: chmod: " Greg Kurz
2017-02-27 13:17 ` Stefan Hajnoczi
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 23/28] 9pfs: local: chown: " Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 24/28] 9pfs: local: symlink: " Greg Kurz
2017-02-26 22:44 ` [Qemu-devel] [PATCH v2 25/28] 9pfs: local: mknod: " Greg Kurz
2017-02-27 13:18 ` Stefan Hajnoczi
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 26/28] 9pfs: local: mkdir: " Greg Kurz
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 27/28] 9pfs: local: open2: " Greg Kurz
2017-02-27 13:18 ` Stefan Hajnoczi
2017-02-26 22:45 ` [Qemu-devel] [PATCH v2 28/28] 9pfs: local: drop unused code Greg Kurz
2017-02-26 23:45 ` [Qemu-devel] [PATCH v2 00/28] 9pfs: local: fix vulnerability to symlink attacks Greg Kurz
2017-02-27 13:24 ` [Qemu-devel] [PATCH v2 00/28] Series short description Stefan Hajnoczi
2017-02-27 15:33 ` Stefan Hajnoczi
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).