qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
From: Greg Kurz <groug@kaod.org>
To: Stefan Hajnoczi <stefanha@gmail.com>
Cc: qemu-devel@nongnu.org, Jann Horn <jannh@google.com>,
	Prasad J Pandit <prasad@redhat.com>,
	"Aneesh Kumar K.V" <aneesh.kumar@linux.vnet.ibm.com>,
	Stefan Hajnoczi <stefanha@redhat.com>,
	Eric Blake <eblake@redhat.com>
Subject: Re: [Qemu-devel] [PATCH 23/29] 9pfs: local: chmod: don't follow symlinks
Date: Fri, 24 Feb 2017 11:34:02 +0100	[thread overview]
Message-ID: <20170224113402.07a0fb44@bahia.lan> (raw)
In-Reply-To: <20170223151042.GW30636@stefanha-x1.localdomain>

[-- Attachment #1: Type: text/plain, Size: 8390 bytes --]

On Thu, 23 Feb 2017 15:10:42 +0000
Stefan Hajnoczi <stefanha@gmail.com> wrote:

> On Mon, Feb 20, 2017 at 03:42:19PM +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
> > 
> > This patch converts local_chmod() to rely on open_nofollow() and
> > fchmod() to fix (1).
> > 
> > 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>
> > ---
> >  hw/9pfs/9p-local.c |  163 ++++++++++++++++++++++++++++++++++++++++++++++++----
> >  1 file changed, 150 insertions(+), 13 deletions(-)
> > 
> > diff --git a/hw/9pfs/9p-local.c b/hw/9pfs/9p-local.c
> > index fb536fdb3082..56d7731f7ce1 100644
> > --- a/hw/9pfs/9p-local.c
> > +++ b/hw/9pfs/9p-local.c
> > @@ -327,6 +327,87 @@ err_out:
> >      return ret;
> >  }
> >  
> > +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(dirfd, VIRTFS_META_DIR,
> > +                       O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
> > +    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 local_set_xattr(const char *path, FsCred *credp)
> >  {
> >      int err;
> > @@ -362,6 +443,45 @@ static int local_set_xattr(const char *path, FsCred *credp)
> >      return 0;
> >  }
> >  
> > +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)
> >  {
> > @@ -553,21 +673,38 @@ static ssize_t local_pwritev(FsContext *ctx, V9fsFidOpenState *fs,
> >  
> >  static int local_chmod(FsContext *fs_ctx, V9fsPath *fs_path, FsCred *credp)
> >  {
> > -    char *buffer;
> >      int ret = -1;
> > -    char *path = fs_path->data;
> > +    int dirfd;
> >  
> > -    if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
> > -        buffer = rpath(fs_ctx, path);
> > -        ret = local_set_xattr(buffer, credp);
> > -        g_free(buffer);
> > -    } 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);
> > +    if (fs_ctx->export_flags & V9FS_SM_MAPPED ||
> > +        fs_ctx->export_flags & V9FS_SM_MAPPED_FILE) {
> > +        char *dirpath, *name;
> > +
> > +        dirpath = g_path_get_dirname(fs_path->data);
> > +        dirfd = local_opendir_nofollow(fs_ctx, dirpath);
> > +        g_free(dirpath);
> > +        if (dirfd == -1) {
> > +            return -1;
> > +        }
> > +
> > +        name = g_path_get_basename(fs_path->data);
> > +        if (fs_ctx->export_flags & V9FS_SM_MAPPED) {
> > +            ret = local_set_xattrat(dirfd, name, credp);
> > +        } else {
> > +            ret = local_set_mapped_file_attrat(dirfd, name, credp);
> > +        }
> > +        g_free(name);
> > +        close_preserve_errno(dirfd);
> > +    } else if (fs_ctx->export_flags & V9FS_SM_PASSTHROUGH ||
> > +               fs_ctx->export_flags & V9FS_SM_NONE) {
> > +        int fd;
> > +
> > +        fd = local_open_nofollow(fs_ctx, fs_path->data, O_RDONLY, 0);
> > +        if (fd == -1) {
> > +            return -1;
> > +        }
> > +        ret = fchmod(fd, credp->fc_mode);
> > +        close_preserve_errno(fd);  
> 
> As mentioned on IRC, not sure this is workable since chmod(2) must not
> require read permission on the file.  This patch introduces failures
> when the file isn't readable.
> 

Yeah :-\ This one is the more painful issue to address. I need a
chmod() variant that would fail on symlinks instead of following
them... and I'm kinda suffering to implement this in a race-free
manner.

Cc'ing Eric for insights.

> Stefan


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 181 bytes --]

  reply	other threads:[~2017-02-24 10:34 UTC|newest]

Thread overview: 75+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2017-02-20 14:39 [Qemu-devel] [PATCH 00/29] 9pfs: local: fix vulnerability to symlink attacks Greg Kurz
2017-02-20 14:39 ` [Qemu-devel] [PATCH 01/29] 9pfs: local: move xattr security ops to 9p-xattr.c Greg Kurz
2017-02-23 10:57   ` Stefan Hajnoczi
2017-02-20 14:39 ` [Qemu-devel] [PATCH 02/29] 9pfs: remove side-effects in local_init() Greg Kurz
2017-02-23 11:00   ` Stefan Hajnoczi
2017-02-20 14:39 ` [Qemu-devel] [PATCH 03/29] 9pfs: remove side-effects in local_open() and local_opendir() Greg Kurz
2017-02-23 11:01   ` Stefan Hajnoczi
2017-02-20 14:39 ` [Qemu-devel] [PATCH 04/29] 9pfs: introduce openat_nofollow() helper Greg Kurz
2017-02-23 11:16   ` Stefan Hajnoczi
2017-02-23 11:56     ` Greg Kurz
2017-02-24 17:17       ` Stefan Hajnoczi
2017-02-24 22:17         ` Greg Kurz
2017-02-27 10:20           ` Stefan Hajnoczi
2017-02-27 10:37             ` Greg Kurz
2017-02-20 14:39 ` [Qemu-devel] [PATCH 05/29] 9pfs: local: keep a file descriptor on the shared folder Greg Kurz
2017-02-23 11:23   ` Stefan Hajnoczi
2017-02-20 14:40 ` [Qemu-devel] [PATCH 06/29] 9pfs: local: open/opendir: don't follow symlinks Greg Kurz
2017-02-23 13:18   ` Stefan Hajnoczi
2017-02-20 14:40 ` [Qemu-devel] [PATCH 07/29] 9pfs: local: introduce symlink-attack safe xattr helpers Greg Kurz
2017-02-23 13:44   ` Stefan Hajnoczi
2017-02-23 20:54     ` Greg Kurz
2017-02-23 15:02   ` Eric Blake
2017-02-23 15:05     ` Jann Horn
2017-02-23 20:31       ` Greg Kurz
2017-02-23 21:01     ` Greg Kurz
2017-02-20 14:40 ` [Qemu-devel] [PATCH 08/29] 9pfs: local: lgetxattr: don't follow symlinks Greg Kurz
2017-02-23 13:45   ` Stefan Hajnoczi
2017-02-20 14:40 ` [Qemu-devel] [PATCH 09/29] 9pfs: local: llistxattr: " Greg Kurz
2017-02-23 14:07   ` Stefan Hajnoczi
2017-02-20 14:40 ` [Qemu-devel] [PATCH 10/29] 9pfs: local: lsetxattr: " Greg Kurz
2017-02-23 14:08   ` Stefan Hajnoczi
2017-02-20 14:40 ` [Qemu-devel] [PATCH 11/29] 9pfs: local: lremovexattr: " Greg Kurz
2017-02-23 14:09   ` Stefan Hajnoczi
2017-02-24 21:58   ` Greg Kurz
2017-02-20 14:40 ` [Qemu-devel] [PATCH 12/29] 9pfs: local: unlinkat: " Greg Kurz
2017-02-23 14:17   ` Stefan Hajnoczi
2017-02-20 14:41 ` [Qemu-devel] [PATCH 13/29] 9pfs: local: remove: " Greg Kurz
2017-02-23 14:23   ` Stefan Hajnoczi
2017-02-24  0:21     ` Greg Kurz
2017-02-20 14:41 ` [Qemu-devel] [PATCH 14/29] 9pfs: local: utimensat: " Greg Kurz
2017-02-23 14:48   ` Stefan Hajnoczi
2017-02-20 14:41 ` [Qemu-devel] [PATCH 15/29] 9pfs: local: statfs: " Greg Kurz
2017-02-23 14:48   ` Stefan Hajnoczi
2017-02-20 14:41 ` [Qemu-devel] [PATCH 16/29] 9pfs: local: truncate: " Greg Kurz
2017-02-23 14:50   ` Stefan Hajnoczi
2017-02-20 14:41 ` [Qemu-devel] [PATCH 17/29] 9pfs: local: readlink: " Greg Kurz
2017-02-23 14:52   ` Stefan Hajnoczi
2017-02-20 14:41 ` [Qemu-devel] [PATCH 18/29] 9pfs: local: lstat: " Greg Kurz
2017-02-23 14:55   ` Stefan Hajnoczi
2017-02-20 14:41 ` [Qemu-devel] [PATCH 19/29] 9pfs: local: renameat: " Greg Kurz
2017-02-23 14:57   ` Stefan Hajnoczi
2017-02-20 14:41 ` [Qemu-devel] [PATCH 20/29] 9pfs: local: rename: use renameat Greg Kurz
2017-02-23 14:57   ` Stefan Hajnoczi
2017-02-20 14:42 ` [Qemu-devel] [PATCH 21/29] 9pfs: local: improve error handling in link op Greg Kurz
2017-02-23 15:00   ` Stefan Hajnoczi
2017-02-20 14:42 ` [Qemu-devel] [PATCH 22/29] 9pfs: local: link: don't follow symlinks Greg Kurz
2017-02-23 15:01   ` Stefan Hajnoczi
2017-02-20 14:42 ` [Qemu-devel] [PATCH 23/29] 9pfs: local: chmod: " Greg Kurz
2017-02-23 15:10   ` Stefan Hajnoczi
2017-02-24 10:34     ` Greg Kurz [this message]
2017-02-24 15:23       ` Eric Blake
2017-02-24 16:22         ` Jann Horn
2017-02-24 19:25           ` Greg Kurz
2017-02-20 14:42 ` [Qemu-devel] [PATCH 24/29] 9pfs: local: chown: " Greg Kurz
2017-02-23 15:10   ` Stefan Hajnoczi
2017-02-20 14:42 ` [Qemu-devel] [PATCH 25/29] 9pfs: local: symlink: " Greg Kurz
2017-02-23 15:15   ` Stefan Hajnoczi
2017-02-20 14:42 ` [Qemu-devel] [PATCH 26/29] 9pfs: local: mknod: " Greg Kurz
2017-02-23 15:16   ` Stefan Hajnoczi
2017-02-20 14:42 ` [Qemu-devel] [PATCH 27/29] 9pfs: local: mkdir: " Greg Kurz
2017-02-23 15:16   ` Stefan Hajnoczi
2017-02-20 14:42 ` [Qemu-devel] [PATCH 28/29] 9pfs: local: open2: " Greg Kurz
2017-02-23 15:22   ` Stefan Hajnoczi
2017-02-20 14:43 ` [Qemu-devel] [PATCH 29/29] 9pfs: local: drop unused code Greg Kurz
2017-02-23 15:22   ` Stefan Hajnoczi

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20170224113402.07a0fb44@bahia.lan \
    --to=groug@kaod.org \
    --cc=aneesh.kumar@linux.vnet.ibm.com \
    --cc=eblake@redhat.com \
    --cc=jannh@google.com \
    --cc=prasad@redhat.com \
    --cc=qemu-devel@nongnu.org \
    --cc=stefanha@gmail.com \
    --cc=stefanha@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).