From patchwork Thu Apr 4 13:39:13 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 1077442 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=lists.ubuntu.com (client-ip=91.189.94.19; helo=huckleberry.canonical.com; envelope-from=kernel-team-bounces@lists.ubuntu.com; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=brauner.io Authentication-Results: ozlabs.org; dkim=fail reason="signature verification failed" (2048-bit key; secure) header.d=brauner.io header.i=@brauner.io header.b="P/ZpvKNb"; dkim-atps=neutral Received: from huckleberry.canonical.com (huckleberry.canonical.com [91.189.94.19]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 44ZnYp5Z11z9sR1; Fri, 5 Apr 2019 02:54:06 +1100 (AEDT) Received: from localhost ([127.0.0.1] helo=huckleberry.canonical.com) by huckleberry.canonical.com with esmtp (Exim 4.86_2) (envelope-from ) id 1hC4gi-0002bm-4C; Thu, 04 Apr 2019 15:54:00 +0000 Received: from mail-pf1-f195.google.com ([209.85.210.195]) by huckleberry.canonical.com with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1hC2ay-0007jq-0x for kernel-team@lists.ubuntu.com; Thu, 04 Apr 2019 13:39:56 +0000 Received: by mail-pf1-f195.google.com with SMTP id z5so1404424pfn.3 for ; Thu, 04 Apr 2019 06:39:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=brauner.io; s=google; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=Spr6wVK4HXiTsamuNGUVOF0lLHrisQG8sAvo/SmJrBE=; b=P/ZpvKNbJq6LxP0S5mgkvEbbkY7SYQHEpvxLie3B2dJEc/9qXzZplvN8g/ND5aciRL +xbpPra/X29Ua68Ec3Rso2uUXmxK17LO9GiXsmTNJINkwPobyA3LTEKw9hp/wEKt1L2O T+GyXlSpD9KKZiIRZD/cHoeYfkncIJApHoP75+op/i1ANhCnGVpzXhHPMVzmkvVAX5q+ xuz9K8A4a4DAAs/P+aQHXKKM9wQU0miOCS5ajOmWDBCe6aidZypuB5ZttmMxLIgVQoYp EGQm4c3MiYDZi9ilhOw98y0krPzYzMRFB1g+/y8jKXKOkauwIkOj1UxuLSxD+pf02eUV j5dA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=Spr6wVK4HXiTsamuNGUVOF0lLHrisQG8sAvo/SmJrBE=; b=NtTlveb87ZXbYIZH6yVJabS09c+k78CFl5WOdDiqyOY+G4palNh/vnFxs6yc0iv1eL nHgd7lFLdSQvk8bYAs5IV7iN/35dQAr9Q0cTDZOqBWNESXqrSxOCnGkFmE8NjfEocMHb uC9zNXAhKBtGaJTfu2OgSrDZ2kuHKoRP85VLymkBzQZdj/kVFpBPhYx+FRWq5/XY/TWq t++VIurFeZhJZ6vv/LdyC5a8Qa0E6kcL2AFqRY/+Ky/BeGUKJGt5w9apHhWq+xequv1z p3MowB4XRCb4B3n/tHWqgTNjdhnf1IH16Z+uL3XWuA2iUg1+v3DC/SsjXqOChpnhq/g8 hZbg== X-Gm-Message-State: APjAAAXfnLDL5socz9NBYbVqPij03a/qEwgQdRoFUBpsDimUhb8PIpU2 VRGpi3BU4O3rNuVtIN4y1b0AEQ== X-Google-Smtp-Source: APXvYqx++00nStD2n1IiVP8HTvb6+F6QMdhlgaSp4QOIwCA2MGHNN7CRpeH3syS71Vbkn5dAGJhJMA== X-Received: by 2002:a65:6645:: with SMTP id z5mr5963322pgv.251.1554385194372; Thu, 04 Apr 2019 06:39:54 -0700 (PDT) Received: from localhost.localdomain ([172.56.31.243]) by smtp.gmail.com with ESMTPSA id t12sm44109704pfl.59.2019.04.04.06.39.49 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Thu, 04 Apr 2019 06:39:53 -0700 (PDT) From: Christian Brauner X-Google-Original-From: Christian Brauner To: seth.forshee@canonical.com, tyhicks@canonical.com, kernel-team@lists.ubuntu.com Subject: [PATCH v1 3/4][DISCO] shiftfs: support some btrfs ioctls Date: Thu, 4 Apr 2019 15:39:13 +0200 Message-Id: <20190404133914.29244-4-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.21.0 In-Reply-To: <20190404133914.29244-1-christian.brauner@ubuntu.com> References: <20190404133914.29244-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 X-Mailman-Approved-At: Thu, 04 Apr 2019 15:53:53 +0000 X-BeenThere: kernel-team@lists.ubuntu.com X-Mailman-Version: 2.1.20 Precedence: list List-Id: Kernel team discussions List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: stgraber@ubuntu.com Errors-To: kernel-team-bounces@lists.ubuntu.com Sender: "kernel-team" From: Christian Brauner Shiftfs currently only passes through a few ioctl()s to the underlay. These are ioctl()s that are generally considered safe. Doing it for random ioctl()s would be a security issue. Permissions for ioctl()s are not checked before the filesystem gets involved so if we were to override credentials we e.g. could do a btrfs tree search in the underlay which we normally wouldn't be allowed to do. However, the btrfs filesystem allows unprivileged users to perform various operations through its ioctl() interface. With shiftfs these ioctl() are currently not working. To not regress users that expect btrfs ioctl()s to work in unprivileged containers we can create a whitelist of ioctl()s that we allow to go through to the underlay and for which we also switch credentials. The main problem is how we switch credentials. Since permissions checks for ioctl()s are done by the actual file system and not by the vfs this would mean that any additional capable()-based checks done by the filesystem would unconditonally pass after we switch credentials. So to make credential switching safe we drop *all* capabilities when switching credentials. This means that only inode-based permission checks will pass. Btrfs also allows unprivileged users to delete snapshots when the filesystem is mounted with user_subvol_rm_allowed mount option or if the the callers is capable(CAP_SYS_ADMIN). The latter should never be the case with unprivileged users. To make sure we only allow removal of snapshots in the former case we drop all capabilities (see above) when switching credentials. Additonally, btrfs allows the creation of snapshots. To make this work we need to be (too) clever. When doing snapshots btrfs requires that an fd to the directory the snapshot is supposed to be created in be passed along. This fd obviously references a shiftfs file and as such a shiftfs dentry and inode. This will cause btrfs to yell EXDEV. To circumnavigate this problem we need to silently temporarily replace the passed in fd with an fd that refers to a file that references a btrfs dentry and inode. Signed-off-by: Christian Brauner Acked-by: Tyler Hicks --- fs/shiftfs.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 151 insertions(+), 5 deletions(-) diff --git a/fs/shiftfs.c b/fs/shiftfs.c index ad1ae5bce6c1..678cad30f4a5 100644 --- a/fs/shiftfs.c +++ b/fs/shiftfs.c @@ -1,6 +1,8 @@ +#include #include #include #include +#include #include #include #include @@ -41,7 +43,21 @@ static void shiftfs_fill_inode(struct inode *inode, unsigned long ino, #define SHIFTFS_PASSTHROUGH_NONE 0 #define SHIFTFS_PASSTHROUGH_STAT 1 -#define SHIFTFS_PASSTHROUGH_ALL (SHIFTFS_PASSTHROUGH_STAT) +#define SHIFTFS_PASSTHROUGH_IOCTL 2 +#define SHIFTFS_PASSTHROUGH_ALL \ + (SHIFTFS_PASSTHROUGH_STAT | SHIFTFS_PASSTHROUGH_IOCTL) + +static inline bool shiftfs_passthrough_ioctls(struct shiftfs_super_info *info) +{ + if (!(info->passthrough & SHIFTFS_PASSTHROUGH_IOCTL)) + return false; + + if (info->info_mark && + !(info->info_mark->passthrough & SHIFTFS_PASSTHROUGH_IOCTL)) + return false; + + return true; +} static inline bool shiftfs_passthrough_statfs(struct shiftfs_super_info *info) { @@ -1345,18 +1361,120 @@ static inline void shiftfs_revert_ioctl_creds(const struct cred *oldcred, return shiftfs_revert_object_creds(oldcred, newcred); } +static inline bool is_btrfs_snap_ioctl(int cmd) +{ + if ((cmd == BTRFS_IOC_SNAP_CREATE) || (cmd == BTRFS_IOC_SNAP_CREATE_V2)) + return true; + + return false; +} + +static int shiftfs_btrfs_ioctl_fd_restore(int cmd, struct fd lfd, int fd, + void __user *arg, + struct btrfs_ioctl_vol_args *v1, + struct btrfs_ioctl_vol_args_v2 *v2) +{ + int ret; + + if (!is_btrfs_snap_ioctl(cmd)) + return 0; + + if (cmd == BTRFS_IOC_SNAP_CREATE) + ret = copy_to_user(arg, v1, sizeof(*v1)); + else + ret = copy_to_user(arg, v2, sizeof(*v2)); + + fdput(lfd); + __close_fd(current->files, fd); + kfree(v1); + kfree(v2); + + return ret; +} + +static int shiftfs_btrfs_ioctl_fd_replace(int cmd, void __user *arg, + struct btrfs_ioctl_vol_args **b1, + struct btrfs_ioctl_vol_args_v2 **b2, + struct fd *lfd, + int *newfd) +{ + int oldfd, ret; + struct fd src; + struct btrfs_ioctl_vol_args *v1 = NULL; + struct btrfs_ioctl_vol_args_v2 *v2 = NULL; + + if (!is_btrfs_snap_ioctl(cmd)) + return 0; + + if (cmd == BTRFS_IOC_SNAP_CREATE) { + v1 = memdup_user(arg, sizeof(*v1)); + if (IS_ERR(v1)) + return PTR_ERR(v1); + oldfd = v1->fd; + *b1 = v1; + } else { + v2 = memdup_user(arg, sizeof(*v2)); + if (IS_ERR(v2)) + return PTR_ERR(v2); + oldfd = v2->fd; + *b2 = v2; + } + + src = fdget(oldfd); + if (!src.file) + return -EINVAL; + + ret = shiftfs_real_fdget(src.file, lfd); + fdput(src); + if (ret) + return ret; + + *newfd = get_unused_fd_flags(lfd->file->f_flags); + if (*newfd < 0) { + fdput(*lfd); + return *newfd; + } + + fd_install(*newfd, lfd->file); + + if (cmd == BTRFS_IOC_SNAP_CREATE) { + v1->fd = *newfd; + ret = copy_to_user(arg, v1, sizeof(*v1)); + v1->fd = oldfd; + } else { + v2->fd = *newfd; + ret = copy_to_user(arg, v2, sizeof(*v2)); + v2->fd = oldfd; + } + + if (ret) + shiftfs_btrfs_ioctl_fd_restore(cmd, *lfd, *newfd, arg, v1, v2); + + return ret; +} + static long shiftfs_real_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - long ret = 0; struct fd lowerfd; struct cred *newcred; const struct cred *oldcred; + int newfd = -EBADF; + long err = 0, ret = 0; + void __user *argp = (void __user *)arg; + struct fd btrfs_lfd = {}; struct super_block *sb = file->f_path.dentry->d_sb; + struct btrfs_ioctl_vol_args *btrfs_v1 = NULL; + struct btrfs_ioctl_vol_args_v2 *btrfs_v2 = NULL; + + ret = shiftfs_btrfs_ioctl_fd_replace(cmd, argp, &btrfs_v1, &btrfs_v2, + &btrfs_lfd, &newfd); + if (ret < 0) + return ret; ret = shiftfs_real_fdget(file, &lowerfd); if (ret) - return ret; + goto out_restore; ret = shiftfs_override_ioctl_creds(sb, &oldcred, &newcred); if (ret) @@ -1372,9 +1490,33 @@ static long shiftfs_real_ioctl(struct file *file, unsigned int cmd, out_fdput: fdput(lowerfd); +out_restore: + err = shiftfs_btrfs_ioctl_fd_restore(cmd, btrfs_lfd, newfd, argp, + btrfs_v1, btrfs_v2); + if (!ret) + ret = err; + return ret; } +static bool in_ioctl_whitelist(int flag) +{ + switch (flag) { + case BTRFS_IOC_SNAP_CREATE: + return true; + case BTRFS_IOC_SNAP_CREATE_V2: + return true; + case BTRFS_IOC_SUBVOL_CREATE: + return true; + case BTRFS_IOC_SUBVOL_CREATE_V2: + return true; + case BTRFS_IOC_SNAP_DESTROY: + return true; + } + + return false; +} + static long shiftfs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { @@ -1386,7 +1528,9 @@ static long shiftfs_ioctl(struct file *file, unsigned int cmd, case FS_IOC_SETFLAGS: break; default: - return -ENOTTY; + if (!in_ioctl_whitelist(cmd) || + !shiftfs_passthrough_ioctls(file->f_path.dentry->d_sb->s_fs_info)) + return -ENOTTY; } return shiftfs_real_ioctl(file, cmd, arg); @@ -1403,7 +1547,9 @@ static long shiftfs_compat_ioctl(struct file *file, unsigned int cmd, case FS_IOC32_SETFLAGS: break; default: - return -ENOIOCTLCMD; + if (!in_ioctl_whitelist(cmd) || + !shiftfs_passthrough_ioctls(file->f_path.dentry->d_sb->s_fs_info)) + return -ENOIOCTLCMD; } return shiftfs_real_ioctl(file, cmd, arg);