Received: by 10.223.185.116 with SMTP id b49csp85080wrg; Fri, 2 Mar 2018 14:09:06 -0800 (PST) X-Google-Smtp-Source: AG47ELvRFCX0KW4TNsNGkIKJwo98bkbwy0m8ouaX3uRjIJ7pLzTicWfo2e6Cjg6FpjqxvDnuZNFn X-Received: by 2002:a17:902:6e0f:: with SMTP id u15-v6mr6417022plk.78.1520028546107; Fri, 02 Mar 2018 14:09:06 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1520028546; cv=none; d=google.com; s=arc-20160816; b=XJW1k62TSCFs1o1NhYQE1IM8rq17vKlfQoAjo2VRkglDTrd5kzl3dcc+Z4ZBYDikH8 O/CynepH6YsyEcGfEr1V44fEcbqGeIwruRUK77gFxKGVB17pQsIhXog6qo+vw6LXaOCd Ec9mLL9NBL1X0X+l13V9MXZ9XCprqLMmfBSSRXnu2IUNSQrhXW5RvHDDSzx68lhukoSD 2+pMJCV+445K2IPATljTSmBym+oV5h2Mzfxv4ZZdjw6FPQ2srS2IGAkIxGPoT4WlScgW U8BH+H9lbz+POINH639ShLGuLRSoVvQopwQdPJjHljmjI/U+hlXmXGSWCtPCx7BRQvqn KiPA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:subject:references:in-reply-to:message-id :date:cc:to:from:arc-authentication-results; bh=dBfYYuKMb7cDdoK+bpjqzLtUWTOPdefPPyRmDGuJD3Q=; b=sAd7E8K2RTzF+EnEC+rNlIsK/JQzvvTufXxsP+64o+zjAMbE5qbQFOCvpfa2rPHjC+ 6LCfPrgqROKzBpTmKoBjQd50zh+MpUJusNd7wyXGPu8fZ3NZFgV7ue5r+Sntoi60+lwN 3fRV3/zL76yvefnXb/h2/+KfRCzsUq5KHvDEQTWSmUcDIra6G7SCCMDyHi4dzL0oxOtH VGuY5IAdOihXiuztZLDH9wb4AkR7bYSyi7+RRTTfxz0IoJpqqccG6XBvRliK8Pl3tQiz djpxSts869zXJyQ+H5jFNN/8d3z9j2NZHBiQy6mAhEwx9Rv7yCHj7WmPpK5Y3KjkYLGS Eueg== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id g3-v6si5326348plp.38.2018.03.02.14.08.51; Fri, 02 Mar 2018 14:09:06 -0800 (PST) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S932886AbeCBWBC (ORCPT + 99 others); Fri, 2 Mar 2018 17:01:02 -0500 Received: from out02.mta.xmission.com ([166.70.13.232]:50270 "EHLO out02.mta.xmission.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932750AbeCBWA4 (ORCPT ); Fri, 2 Mar 2018 17:00:56 -0500 Received: from in02.mta.xmission.com ([166.70.13.52]) by out02.mta.xmission.com with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.87) (envelope-from ) id 1ersjX-0005Db-VN; Fri, 02 Mar 2018 15:00:56 -0700 Received: from 174-19-85-160.omah.qwest.net ([174.19.85.160] helo=x220.int.ebiederm.org) by in02.mta.xmission.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.87) (envelope-from ) id 1ersjI-0000ts-HU; Fri, 02 Mar 2018 15:00:55 -0700 From: "Eric W. Biederman" To: Miklos Szeredi Cc: linux-kernel@vger.kernel.org, containers@lists.linux-foundation.org, linux-fsdevel@vger.kernel.org, Alban Crequy , Seth Forshee , Sargun Dhillon , Dongsu Park , "Serge E. Hallyn" , Linus Torvalds , "Eric W. Biederman" Date: Fri, 2 Mar 2018 15:59:18 -0600 Message-Id: <20180302215919.27207-5-ebiederm@xmission.com> X-Mailer: git-send-email 2.14.1 In-Reply-To: <87r2p287i8.fsf_-_@xmission.com> References: <87r2p287i8.fsf_-_@xmission.com> X-XM-SPF: eid=1ersjI-0000ts-HU;;;mid=<20180302215919.27207-5-ebiederm@xmission.com>;;;hst=in02.mta.xmission.com;;;ip=174.19.85.160;;;frm=ebiederm@xmission.com;;;spf=neutral X-XM-AID: U2FsdGVkX1/Y10fR85btk6lePIhSZu7VGjVv4F5vyWA= X-SA-Exim-Connect-IP: 174.19.85.160 X-SA-Exim-Mail-From: ebiederm@xmission.com X-Spam-Checker-Version: SpamAssassin 3.4.1 (2015-04-28) on sa07.xmission.com X-Spam-Level: X-Spam-Status: No, score=0.5 required=8.0 tests=ALL_TRUSTED,BAYES_50, DCC_CHECK_NEGATIVE,TVD_RCVD_IP,T_TM2_M_HEADER_IN_MSG,T_TooManySym_01, XMSubLong autolearn=disabled version=3.4.1 X-Spam-Report: * -1.0 ALL_TRUSTED Passed through trusted hosts only via SMTP * 0.0 TVD_RCVD_IP Message was received from an IP address * 0.7 XMSubLong Long Subject * 0.0 T_TM2_M_HEADER_IN_MSG BODY: No description available. * 0.8 BAYES_50 BODY: Bayes spam probability is 40 to 60% * [score: 0.5000] * -0.0 DCC_CHECK_NEGATIVE Not listed in DCC * [sa07 1397; Body=1 Fuz1=1 Fuz2=168] * 0.0 T_TooManySym_01 4+ unique symbols in subject X-Spam-DCC: XMission; sa07 1397; Body=1 Fuz1=1 Fuz2=168 X-Spam-Combo: ;Miklos Szeredi X-Spam-Relay-Country: X-Spam-Timing: total 15031 ms - load_scoreonly_sql: 0.05 (0.0%), signal_user_changed: 2.3 (0.0%), b_tie_ro: 1.53 (0.0%), parse: 1.14 (0.0%), extract_message_metadata: 20 (0.1%), get_uri_detail_list: 6 (0.0%), tests_pri_-1000: 3.0 (0.0%), tests_pri_-950: 1.18 (0.0%), tests_pri_-900: 0.98 (0.0%), tests_pri_-400: 47 (0.3%), check_bayes: 46 (0.3%), b_tokenize: 20 (0.1%), b_tok_get_all: 14 (0.1%), b_comp_prob: 4.1 (0.0%), b_tok_touch_all: 6 (0.0%), b_finish: 0.55 (0.0%), tests_pri_0: 1540 (10.2%), check_dkim_signature: 0.68 (0.0%), check_dkim_adsp: 11 (0.1%), tests_pri_500: 13412 (89.2%), poll_dns_idle: 13403 (89.2%), rewrite_mail: 0.00 (0.0%) Subject: [PATCH v8 5/6] fuse: Support fuse filesystems outside of init_user_ns X-Spam-Flag: No X-SA-Exim-Version: 4.2.1 (built Thu, 05 May 2016 13:38:54 -0600) X-SA-Exim-Scanned: Yes (on in02.mta.xmission.com) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org In order to support mounts from namespaces other than init_user_ns, fuse must translate uids and gids to/from the userns of the process servicing requests on /dev/fuse. This patch does that, with a couple of restrictions on the namespace: - The userns for the fuse connection is fixed to the namespace from which /dev/fuse is opened. - The namespace must be the same as s_user_ns. These restrictions simplify the implementation by avoiding the need to pass around userns references and by allowing fuse to rely on the checks in setattr_prepare for ownership changes. Either restriction could be relaxed in the future if needed. For cuse the userns used is the opener of /dev/cuse. Semantically the cuse support does not appear safe for unprivileged users. Practically the permissions on /dev/cuse only make it accessible to the global root user. If something slips through the cracks in a user namespace the only users who will be able to use the cuse device are those users mapped into the user namespace. Translation in the posix acl is updated to use the uuser namespace of the filesystem. Avoiding cases which might bypass this translation is handled in a following change. This change is stronlgy based on a similar change from Seth Forshee and Dongsu Park. Cc: linux-fsdevel@vger.kernel.org Cc: linux-kernel@vger.kernel.org Cc: Miklos Szeredi Cc: Cc: Dongsu Park Signed-off-by: Eric W. Biederman --- fs/fuse/acl.c | 4 ++-- fs/fuse/cuse.c | 7 ++++++- fs/fuse/dev.c | 4 ++-- fs/fuse/dir.c | 14 +++++++------- fs/fuse/fuse_i.h | 6 +++++- fs/fuse/inode.c | 31 +++++++++++++++++++------------ 6 files changed, 41 insertions(+), 25 deletions(-) diff --git a/fs/fuse/acl.c b/fs/fuse/acl.c index cfa58ee0c10b..0472735a89c3 100644 --- a/fs/fuse/acl.c +++ b/fs/fuse/acl.c @@ -34,7 +34,7 @@ struct posix_acl *fuse_get_acl(struct inode *inode, int type) return ERR_PTR(-ENOMEM); size = fuse_getxattr(inode, name, value, PAGE_SIZE); if (size > 0) - acl = posix_acl_from_xattr(&init_user_ns, value, size); + acl = posix_acl_from_xattr(fc->user_ns, value, size); else if ((size == 0) || (size == -ENODATA) || (size == -EOPNOTSUPP && fc->no_getxattr)) acl = NULL; @@ -83,7 +83,7 @@ int fuse_set_acl(struct inode *inode, struct posix_acl *acl, int type) if (!value) return -ENOMEM; - ret = posix_acl_to_xattr(&init_user_ns, acl, value, size); + ret = posix_acl_to_xattr(fc->user_ns, acl, value, size); if (ret < 0) { kfree(value); return ret; diff --git a/fs/fuse/cuse.c b/fs/fuse/cuse.c index e9e97803442a..036ee477669e 100644 --- a/fs/fuse/cuse.c +++ b/fs/fuse/cuse.c @@ -48,6 +48,7 @@ #include #include #include +#include #include "fuse_i.h" @@ -498,7 +499,11 @@ static int cuse_channel_open(struct inode *inode, struct file *file) if (!cc) return -ENOMEM; - fuse_conn_init(&cc->fc); + /* + * Limit the cuse channel to requests that can + * be represented in file->f_cred->user_ns. + */ + fuse_conn_init(&cc->fc, file->f_cred->user_ns); fud = fuse_dev_alloc(&cc->fc); if (!fud) { diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c index 2886a56d5f61..fce7915aea13 100644 --- a/fs/fuse/dev.c +++ b/fs/fuse/dev.c @@ -114,8 +114,8 @@ static void __fuse_put_request(struct fuse_req *req) static bool fuse_req_init_context(struct fuse_conn *fc, struct fuse_req *req) { - req->in.h.uid = from_kuid(&init_user_ns, current_fsuid()); - req->in.h.gid = from_kgid(&init_user_ns, current_fsgid()); + req->in.h.uid = from_kuid(fc->user_ns, current_fsuid()); + req->in.h.gid = from_kgid(fc->user_ns, current_fsgid()); req->in.h.pid = pid_nr_ns(task_pid(current), fc->pid_ns); return (req->in.h.uid != ((uid_t)-1)) && (req->in.h.gid != ((gid_t)-1)); diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 43a45e83d313..c749a4bd4ea3 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -858,8 +858,8 @@ static void fuse_fillattr(struct inode *inode, struct fuse_attr *attr, stat->ino = attr->ino; stat->mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777); stat->nlink = attr->nlink; - stat->uid = make_kuid(&init_user_ns, attr->uid); - stat->gid = make_kgid(&init_user_ns, attr->gid); + stat->uid = make_kuid(fc->user_ns, attr->uid); + stat->gid = make_kgid(fc->user_ns, attr->gid); stat->rdev = inode->i_rdev; stat->atime.tv_sec = attr->atime; stat->atime.tv_nsec = attr->atimensec; @@ -1475,17 +1475,17 @@ static bool update_mtime(unsigned ivalid, bool trust_local_mtime) return true; } -static void iattr_to_fattr(struct iattr *iattr, struct fuse_setattr_in *arg, - bool trust_local_cmtime) +static void iattr_to_fattr(struct fuse_conn *fc, struct iattr *iattr, + struct fuse_setattr_in *arg, bool trust_local_cmtime) { unsigned ivalid = iattr->ia_valid; if (ivalid & ATTR_MODE) arg->valid |= FATTR_MODE, arg->mode = iattr->ia_mode; if (ivalid & ATTR_UID) - arg->valid |= FATTR_UID, arg->uid = from_kuid(&init_user_ns, iattr->ia_uid); + arg->valid |= FATTR_UID, arg->uid = from_kuid(fc->user_ns, iattr->ia_uid); if (ivalid & ATTR_GID) - arg->valid |= FATTR_GID, arg->gid = from_kgid(&init_user_ns, iattr->ia_gid); + arg->valid |= FATTR_GID, arg->gid = from_kgid(fc->user_ns, iattr->ia_gid); if (ivalid & ATTR_SIZE) arg->valid |= FATTR_SIZE, arg->size = iattr->ia_size; if (ivalid & ATTR_ATIME) { @@ -1646,7 +1646,7 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr, memset(&inarg, 0, sizeof(inarg)); memset(&outarg, 0, sizeof(outarg)); - iattr_to_fattr(attr, &inarg, trust_local_cmtime); + iattr_to_fattr(fc, attr, &inarg, trust_local_cmtime); if (file) { struct fuse_file *ff = file->private_data; inarg.valid |= FATTR_FH; diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h index 74ce02fb16d6..dbb1d4ef1a0b 100644 --- a/fs/fuse/fuse_i.h +++ b/fs/fuse/fuse_i.h @@ -26,6 +26,7 @@ #include #include #include +#include /** Max number of pages that can be used in a single read request */ #define FUSE_MAX_PAGES_PER_REQ 32 @@ -466,6 +467,9 @@ struct fuse_conn { /** The pid namespace for this mount */ struct pid_namespace *pid_ns; + /** The user namespace for this mount */ + struct user_namespace *user_ns; + /** Maximum read size */ unsigned max_read; @@ -870,7 +874,7 @@ struct fuse_conn *fuse_conn_get(struct fuse_conn *fc); /** * Initialize fuse_conn */ -void fuse_conn_init(struct fuse_conn *fc); +void fuse_conn_init(struct fuse_conn *fc, struct user_namespace *user_ns); /** * Release reference to fuse_conn diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c index 507f780046c5..b5b2e1fc5bfd 100644 --- a/fs/fuse/inode.c +++ b/fs/fuse/inode.c @@ -171,8 +171,8 @@ void fuse_change_attributes_common(struct inode *inode, struct fuse_attr *attr, inode->i_ino = fuse_squash_ino(attr->ino); inode->i_mode = (inode->i_mode & S_IFMT) | (attr->mode & 07777); set_nlink(inode, attr->nlink); - inode->i_uid = make_kuid(&init_user_ns, attr->uid); - inode->i_gid = make_kgid(&init_user_ns, attr->gid); + inode->i_uid = make_kuid(fc->user_ns, attr->uid); + inode->i_gid = make_kgid(fc->user_ns, attr->gid); inode->i_blocks = attr->blocks; inode->i_atime.tv_sec = attr->atime; inode->i_atime.tv_nsec = attr->atimensec; @@ -477,7 +477,8 @@ static int fuse_match_uint(substring_t *s, unsigned int *res) return err; } -static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev) +static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev, + struct user_namespace *user_ns) { char *p; memset(d, 0, sizeof(struct fuse_mount_data)); @@ -513,7 +514,7 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev) case OPT_USER_ID: if (fuse_match_uint(&args[0], &uv)) return 0; - d->user_id = make_kuid(current_user_ns(), uv); + d->user_id = make_kuid(user_ns, uv); if (!uid_valid(d->user_id)) return 0; d->user_id_present = 1; @@ -522,7 +523,7 @@ static int parse_fuse_opt(char *opt, struct fuse_mount_data *d, int is_bdev) case OPT_GROUP_ID: if (fuse_match_uint(&args[0], &uv)) return 0; - d->group_id = make_kgid(current_user_ns(), uv); + d->group_id = make_kgid(user_ns, uv); if (!gid_valid(d->group_id)) return 0; d->group_id_present = 1; @@ -565,8 +566,8 @@ static int fuse_show_options(struct seq_file *m, struct dentry *root) struct super_block *sb = root->d_sb; struct fuse_conn *fc = get_fuse_conn_super(sb); - seq_printf(m, ",user_id=%u", from_kuid_munged(&init_user_ns, fc->user_id)); - seq_printf(m, ",group_id=%u", from_kgid_munged(&init_user_ns, fc->group_id)); + seq_printf(m, ",user_id=%u", from_kuid_munged(fc->user_ns, fc->user_id)); + seq_printf(m, ",group_id=%u", from_kgid_munged(fc->user_ns, fc->group_id)); if (fc->default_permissions) seq_puts(m, ",default_permissions"); if (fc->allow_other) @@ -597,7 +598,7 @@ static void fuse_pqueue_init(struct fuse_pqueue *fpq) fpq->connected = 1; } -void fuse_conn_init(struct fuse_conn *fc) +void fuse_conn_init(struct fuse_conn *fc, struct user_namespace *user_ns) { memset(fc, 0, sizeof(*fc)); spin_lock_init(&fc->lock); @@ -621,6 +622,7 @@ void fuse_conn_init(struct fuse_conn *fc) fc->attr_version = 1; get_random_bytes(&fc->scramble_key, sizeof(fc->scramble_key)); fc->pid_ns = get_pid_ns(task_active_pid_ns(current)); + fc->user_ns = get_user_ns(user_ns); } EXPORT_SYMBOL_GPL(fuse_conn_init); @@ -630,6 +632,7 @@ void fuse_conn_put(struct fuse_conn *fc) if (fc->destroy_req) fuse_request_free(fc->destroy_req); put_pid_ns(fc->pid_ns); + put_user_ns(fc->user_ns); fc->release(fc); } } @@ -1060,7 +1063,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) sb->s_flags &= ~(SB_NOSEC | SB_I_VERSION); - if (!parse_fuse_opt(data, &d, is_bdev)) + if (!parse_fuse_opt(data, &d, is_bdev, sb->s_user_ns)) goto err; if (is_bdev) { @@ -1085,8 +1088,12 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) if (!file) goto err; - if ((file->f_op != &fuse_dev_operations) || - (file->f_cred->user_ns != &init_user_ns)) + /* + * Require mount to happen from the same user namespace which + * opened /dev/fuse to prevent potential attacks. + */ + if (file->f_op != &fuse_dev_operations || + file->f_cred->user_ns != sb->s_user_ns) goto err_fput; fc = kmalloc(sizeof(*fc), GFP_KERNEL); @@ -1094,7 +1101,7 @@ static int fuse_fill_super(struct super_block *sb, void *data, int silent) if (!fc) goto err_fput; - fuse_conn_init(fc); + fuse_conn_init(fc, sb->s_user_ns); fc->release = fuse_free_conn; fud = fuse_dev_alloc(fc); -- 2.14.1