From patchwork Thu Dec 3 23:56:57 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950097 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E208DC4743C for ; Fri, 4 Dec 2020 00:02:35 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id BB0A922224 for ; Fri, 4 Dec 2020 00:02:35 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388622AbgLDAC0 (ORCPT ); Thu, 3 Dec 2020 19:02:26 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41699 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731385AbgLDACK (ORCPT ); Thu, 3 Dec 2020 19:02:10 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXE-0007ka-37; Fri, 04 Dec 2020 00:01:16 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 01/40] namespace: take lock_mount_hash() directly when changing flags Date: Fri, 4 Dec 2020 00:56:57 +0100 Message-Id: <20201203235736.3528991-2-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Changing mount options always ends up taking lock_mount_hash() but when MNT_READONLY is requested and neither the mount nor the superblock are MNT_READONLY we end up taking the lock, dropping it, and retaking it to change the other mount attributes. Instead, let's acquire the lock once when changing the mount attributes. This simplifies the locking in these codepath, makes them easier to reason about and avoids having to reacquire the lock right after dropping it. Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ - Christoph Hellwig : - Remove pointless __mnt_unmake_readonly() helper. - Even though Christoph suggested to lockdep_assert_held() into places that require {lock,unlock}_mount_hash() it seems that seqlock's don't support it. /* v3 */ unchanged /* v4 */ unchanged --- fs/namespace.c | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index cebaa3e81794..f183161833ad 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -463,7 +463,6 @@ static int mnt_make_readonly(struct mount *mnt) { int ret = 0; - lock_mount_hash(); mnt->mnt.mnt_flags |= MNT_WRITE_HOLD; /* * After storing MNT_WRITE_HOLD, we'll read the counters. This store @@ -497,18 +496,9 @@ static int mnt_make_readonly(struct mount *mnt) */ smp_wmb(); mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD; - unlock_mount_hash(); return ret; } -static int __mnt_unmake_readonly(struct mount *mnt) -{ - lock_mount_hash(); - mnt->mnt.mnt_flags &= ~MNT_READONLY; - unlock_mount_hash(); - return 0; -} - int sb_prepare_remount_readonly(struct super_block *sb) { struct mount *mnt; @@ -2508,7 +2498,8 @@ static int change_mount_ro_state(struct mount *mnt, unsigned int mnt_flags) if (readonly_request) return mnt_make_readonly(mnt); - return __mnt_unmake_readonly(mnt); + mnt->mnt.mnt_flags &= ~MNT_READONLY; + return 0; } /* @@ -2517,11 +2508,9 @@ static int change_mount_ro_state(struct mount *mnt, unsigned int mnt_flags) */ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags) { - lock_mount_hash(); mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK; mnt->mnt.mnt_flags = mnt_flags; touch_mnt_namespace(mnt->mnt_ns); - unlock_mount_hash(); } static void mnt_warn_timestamp_expiry(struct path *mountpoint, struct vfsmount *mnt) @@ -2567,9 +2556,11 @@ static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags) return -EPERM; down_write(&sb->s_umount); + lock_mount_hash(); ret = change_mount_ro_state(mnt, mnt_flags); if (ret == 0) set_mount_attributes(mnt, mnt_flags); + unlock_mount_hash(); up_write(&sb->s_umount); mnt_warn_timestamp_expiry(path, &mnt->mnt); @@ -2610,8 +2601,11 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags, err = -EPERM; if (ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) { err = reconfigure_super(fc); - if (!err) + if (!err) { + lock_mount_hash(); set_mount_attributes(mnt, mnt_flags); + unlock_mount_hash(); + } } up_write(&sb->s_umount); } From patchwork Thu Dec 3 23:56:58 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950083 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 945FFC1B087 for ; Fri, 4 Dec 2020 00:02:26 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 44762224D1 for ; Fri, 4 Dec 2020 00:02:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388136AbgLDACM (ORCPT ); Thu, 3 Dec 2020 19:02:12 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41697 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728063AbgLDACK (ORCPT ); Thu, 3 Dec 2020 19:02:10 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXF-0007ka-Su; Fri, 04 Dec 2020 00:01:18 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 02/40] mount: make {lock,unlock}_mount_hash() static Date: Fri, 4 Dec 2020 00:56:58 +0100 Message-Id: <20201203235736.3528991-3-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The lock_mount_hash() and unlock_mount_hash() helpers are never called outside a single file. Remove them from the header and make them static to reflect this fact. There's no need to have them callable from other places right now, as Christoph observed. Suggested-by: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ - Christoph Hellwig : - Add a patch to make {lock,unlock)_mount_hash() static. /* v3 */ unchanged /* v4 */ unchanged --- fs/mount.h | 10 ---------- fs/namespace.c | 10 ++++++++++ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/fs/mount.h b/fs/mount.h index c7abb7b394d8..562d96d57bad 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -125,16 +125,6 @@ static inline void get_mnt_ns(struct mnt_namespace *ns) extern seqlock_t mount_lock; -static inline void lock_mount_hash(void) -{ - write_seqlock(&mount_lock); -} - -static inline void unlock_mount_hash(void) -{ - write_sequnlock(&mount_lock); -} - struct proc_mounts { struct mnt_namespace *ns; struct path root; diff --git a/fs/namespace.c b/fs/namespace.c index f183161833ad..798bbf4f48ad 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -87,6 +87,16 @@ EXPORT_SYMBOL_GPL(fs_kobj); */ __cacheline_aligned_in_smp DEFINE_SEQLOCK(mount_lock); +static inline void lock_mount_hash(void) +{ + write_seqlock(&mount_lock); +} + +static inline void unlock_mount_hash(void) +{ + write_sequnlock(&mount_lock); +} + static inline struct hlist_head *m_hash(struct vfsmount *mnt, struct dentry *dentry) { unsigned long tmp = ((unsigned long)mnt / L1_CACHE_BYTES); From patchwork Thu Dec 3 23:56:59 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950081 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 60DD3C4167B for ; Fri, 4 Dec 2020 00:02:25 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2C807224DE for ; Fri, 4 Dec 2020 00:02:25 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387751AbgLDACK (ORCPT ); Thu, 3 Dec 2020 19:02:10 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41694 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731372AbgLDACJ (ORCPT ); Thu, 3 Dec 2020 19:02:09 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXH-0007ka-Mh; Fri, 04 Dec 2020 00:01:19 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 03/40] namespace: only take read lock in do_reconfigure_mnt() Date: Fri, 4 Dec 2020 00:56:59 +0100 Message-Id: <20201203235736.3528991-4-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org do_reconfigure_mnt() used to take the down_write(&sb->s_umount) lock which seems unnecessary since we're not changing the superblock. We're only checking whether it is already read-only. Setting other mount attributes is protected by lock_mount_hash() afaict and not by s_umount. So I think the history of down_write(&sb->s_umount) lock being taken when setting mount attributes dates back to the introduction of MNT_READONLY in [2]. Afaict, this introduced the concept of having read-only mounts in contrast to just having a read-only superblock. When it got introduced it was simply plumbed into do_remount() which already took down_write(&sb->s_umount) because it was only used to actually change the superblock before [2]. Afaict, it would've already been possible back then to only use down_read(&sb->s_umount) for MS_BIND | MS_REMOUNT since actual mount options were protected by the vfsmount lock already. But that would've meant special casing the locking for MS_BIND | MS_REMOUNT in do_remount() which people might not have considered worth it. Then in [1] MS_BIND | MS_REMOUNT mount option changes were split out of do_remount() into do_reconfigure_mnt() but the down_write(&sb->s_umount) lock was simply copied over. Now that we have this be a separate helper only take the down_read(&sb->s_umount) lock since we're only interested in checking whether the super block is currently read-only and blocking any writers from changing it. Essentially, checking that the super block is read-only has the advantage that we can avoid having to go into the slowpath and through MNT_WRITE_HOLD and can simply set the read-only flag on the mount in set_mount_attributes(). [1]: commit 43f5e655eff7 ("vfs: Separate changing mount flags full remount") [2]: commit 2e4b7fcd9260 ("[PATCH] r/o bind mounts: honor mount writer counts at remount") Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ unchanged --- fs/namespace.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 798bbf4f48ad..8497d149ecaa 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -2512,10 +2512,6 @@ static int change_mount_ro_state(struct mount *mnt, unsigned int mnt_flags) return 0; } -/* - * Update the user-settable attributes on a mount. The caller must hold - * sb->s_umount for writing. - */ static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags) { mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK; @@ -2565,13 +2561,17 @@ static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags) if (!can_change_locked_flags(mnt, mnt_flags)) return -EPERM; - down_write(&sb->s_umount); + /* + * We're only checking whether the superblock is read-only not changing + * it, so only take down_read(&sb->s_umount). + */ + down_read(&sb->s_umount); lock_mount_hash(); ret = change_mount_ro_state(mnt, mnt_flags); if (ret == 0) set_mount_attributes(mnt, mnt_flags); unlock_mount_hash(); - up_write(&sb->s_umount); + up_read(&sb->s_umount); mnt_warn_timestamp_expiry(path, &mnt->mnt); From patchwork Thu Dec 3 23:57:00 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950079 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EF6A8C433FE for ; Fri, 4 Dec 2020 00:02:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id A5C07224BD for ; Fri, 4 Dec 2020 00:02:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2387401AbgLDACJ (ORCPT ); Thu, 3 Dec 2020 19:02:09 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41693 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1731338AbgLDACI (ORCPT ); Thu, 3 Dec 2020 19:02:08 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXJ-0007ka-ED; Fri, 04 Dec 2020 00:01:21 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 04/40] fs: split out functions to hold writers Date: Fri, 4 Dec 2020 00:57:00 +0100 Message-Id: <20201203235736.3528991-5-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org When a mount is marked read-only we set MNT_WRITE_HOLD on it if there aren't currently any active writers. Split this logic out into simple helpers that we can use in follow-up patches. Suggested-by: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ patch not present /* v3 */ patch not present /* v4 */ patch introduced --- fs/namespace.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 8497d149ecaa..1a756d33c91a 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -469,10 +469,8 @@ void mnt_drop_write_file(struct file *file) } EXPORT_SYMBOL(mnt_drop_write_file); -static int mnt_make_readonly(struct mount *mnt) +static inline int mnt_hold_writers(struct mount *mnt) { - int ret = 0; - mnt->mnt.mnt_flags |= MNT_WRITE_HOLD; /* * After storing MNT_WRITE_HOLD, we'll read the counters. This store @@ -497,15 +495,29 @@ static int mnt_make_readonly(struct mount *mnt) * we're counting up here. */ if (mnt_get_writers(mnt) > 0) - ret = -EBUSY; - else - mnt->mnt.mnt_flags |= MNT_READONLY; + return -EBUSY; + + return 0; +} + +static inline void mnt_unhold_writers(struct mount *mnt) +{ /* * MNT_READONLY must become visible before ~MNT_WRITE_HOLD, so writers * that become unheld will see MNT_READONLY. */ smp_wmb(); mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD; +} + +static int mnt_make_readonly(struct mount *mnt) +{ + int ret; + + ret = mnt_hold_writers(mnt); + if (!ret) + mnt->mnt.mnt_flags |= MNT_READONLY; + mnt_unhold_writers(mnt); return ret; } From patchwork Thu Dec 3 23:57:01 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950085 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1C1E8C2BB3F for ; Fri, 4 Dec 2020 00:02:27 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C799F224B1 for ; Fri, 4 Dec 2020 00:02:26 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388218AbgLDACN (ORCPT ); Thu, 3 Dec 2020 19:02:13 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41698 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1728138AbgLDACL (ORCPT ); Thu, 3 Dec 2020 19:02:11 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXL-0007ka-79; Fri, 04 Dec 2020 00:01:23 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 05/40] fs: add attr_flags_to_mnt_flags helper Date: Fri, 4 Dec 2020 00:57:01 +0100 Message-Id: <20201203235736.3528991-6-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Add a simple helper to translate uapi MOUNT_ATTR_* flags to MNT_* flags which we will use in follow-up patches too. Suggested-by: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ patch not present /* v3 */ patch not present /* v4 */ patch introduced --- fs/namespace.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index 1a756d33c91a..b2a9eb7b43d3 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -3450,6 +3450,28 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, return ret; } +#define FSMOUNT_VALID_FLAGS \ + (MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV | \ + MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME | MOUNT_ATTR_NODIRATIME) + +static unsigned int attr_flags_to_mnt_flags(u64 attr_flags) +{ + unsigned int mnt_flags = 0; + + if (attr_flags & MOUNT_ATTR_RDONLY) + mnt_flags |= MNT_READONLY; + if (attr_flags & MOUNT_ATTR_NOSUID) + mnt_flags |= MNT_NOSUID; + if (attr_flags & MOUNT_ATTR_NODEV) + mnt_flags |= MNT_NODEV; + if (attr_flags & MOUNT_ATTR_NOEXEC) + mnt_flags |= MNT_NOEXEC; + if (attr_flags & MOUNT_ATTR_NODIRATIME) + mnt_flags |= MNT_NODIRATIME; + + return mnt_flags; +} + /* * Create a kernel mount representation for a new, prepared superblock * (specified by fs_fd) and attach to an open_tree-like file descriptor. @@ -3472,24 +3494,10 @@ SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags, if ((flags & ~(FSMOUNT_CLOEXEC)) != 0) return -EINVAL; - if (attr_flags & ~(MOUNT_ATTR_RDONLY | - MOUNT_ATTR_NOSUID | - MOUNT_ATTR_NODEV | - MOUNT_ATTR_NOEXEC | - MOUNT_ATTR__ATIME | - MOUNT_ATTR_NODIRATIME)) + if (attr_flags & ~FSMOUNT_VALID_FLAGS) return -EINVAL; - if (attr_flags & MOUNT_ATTR_RDONLY) - mnt_flags |= MNT_READONLY; - if (attr_flags & MOUNT_ATTR_NOSUID) - mnt_flags |= MNT_NOSUID; - if (attr_flags & MOUNT_ATTR_NODEV) - mnt_flags |= MNT_NODEV; - if (attr_flags & MOUNT_ATTR_NOEXEC) - mnt_flags |= MNT_NOEXEC; - if (attr_flags & MOUNT_ATTR_NODIRATIME) - mnt_flags |= MNT_NODIRATIME; + mnt_flags = attr_flags_to_mnt_flags(attr_flags); switch (attr_flags & MOUNT_ATTR__ATIME) { case MOUNT_ATTR_STRICTATIME: From patchwork Thu Dec 3 23:57:02 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950091 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6AF3EC2BBD4 for ; Fri, 4 Dec 2020 00:02:28 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1DCA622224 for ; Fri, 4 Dec 2020 00:02:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388321AbgLDACQ (ORCPT ); Thu, 3 Dec 2020 19:02:16 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41702 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2387439AbgLDACO (ORCPT ); Thu, 3 Dec 2020 19:02:14 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXM-0007ka-VU; Fri, 04 Dec 2020 00:01:25 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 06/40] fs: add mount_setattr() Date: Fri, 4 Dec 2020 00:57:02 +0100 Message-Id: <20201203235736.3528991-7-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org This implements the missing mount_setattr() syscall. While the new mount api allows to change the properties of a superblock there is currently no way to change the properties of a mount or a mount tree using file descriptors which the new mount api is based on. In addition the old mount api has the restriction that mount options cannot be applied recursively. This hasn't changed since changing mount options on a per-mount basis was implemented in [1] and has been a frequent request not just for convenience but also for security reasons. The legacy mount syscall is unable to accommodate this behavior without introducing a whole new set of flags because MS_REC | MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOEXEC | [...] only apply the mount option to the topmost mount. Changing MS_REC to apply to the whole mount tree would mean introducing a significant uapi change and would likely cause significant regressions. The new mount_setattr() syscall allows to recursively clear and set mount options in one shot. Multiple calls to change mount options requesting the same changes are idempotent: int mount_setattr(int dfd, const char *path, unsigned flags, struct mount_attr *uattr, size_t usize); Flags to modify path resolution behavior are specified in the @flags argument. Currently, AT_EMPTY_PATH, AT_RECURSIVE, AT_SYMLINK_NOFOLLOW, and AT_NO_AUTOMOUNT are supported. If useful, additional lookup flags to restrict path resolution as introduced with openat2() might be supported in the future. The mount_setattr() syscall can be expected to grow over time and is designed with extensibility in mind. It follows the extensible syscall pattern we have used with other syscalls such as openat2(), clone3(), sched_{set,get}attr(), and others. The set of mount options is passed in the uapi struct mount_attr which currently has the following layout: struct mount_attr { __u64 attr_set; __u64 attr_clr; __u32 propagation; }; The @attr_set and @attr_clr members are used to clear and set mount options. This way a user can e.g. request that a set of flags is to be raised such as turning mounts readonly by raising MOUNT_ATTR_RDONLY in @attr_set while at the same time requesting that another set of flags is to be lowered such as removing noexec from a mount tree by specifying MOUNT_ATTR_NOEXEC in @attr_clr. Note, since the MOUNT_ATTR_ values are an enum starting from 0, not a bitmap, users wanting to transition to a different atime setting cannot simply specify the atime setting in @attr_set, but must also specify MOUNT_ATTR__ATIME in the @attr_clr field. So we ensure that MOUNT_ATTR__ATIME can't be partially set in @attr_clr and that @attr_set can't have any atime bits set if MOUNT_ATTR__ATIME isn't set in @attr_clr. The @propagation field lets callers specify the propagation type of a mount tree. Propagation is a single property that has four different settings and as such is not really a flag argument but an enum. Specifically, it would be unclear what setting and clearing propagation settings in combination would amount to. The legacy mount() syscall thus forbids the combination of multiple propagation settings too. The goal is to keep the semantics of mount propagation somewhat simple as they are overly complex as it is. [1]: commit 2e4b7fcd9260 ("[PATCH] r/o bind mounts: honor mount writer counts at remount") Cc: Christoph Hellwig Cc: David Howells Cc: Aleksa Sarai Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Cc: linux-api@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ - Christoph Hellwig : - Split into multiple helpers. /* v3 */ - kernel test robot : - Fix unknown __u64 type by including linux/types.h in linux/mount.h. /* v4 */ - Christoph Hellwig : - Make sure lines wrap at 80 chars. - Move struct mount_kattr out of the internal.h header and completely into fs/namespace.c as it's not used outside of that file. - Add missing space between ( and { when initializing mount_kattr. - Split flag validation and calculation into separate preparatory patch. - Simplify flag validation in build_mount_kattr() to avoid upper_32_bits() and lower_32_bits() calls. This will also lead to better code generation. - Remove new propagation enums and simply use the old flags. - Strictly adhere to 80 char limit. - Restructure the time setting code in build_mount_kattr(). --- arch/alpha/kernel/syscalls/syscall.tbl | 1 + arch/arm/tools/syscall.tbl | 1 + arch/arm64/include/asm/unistd32.h | 2 + arch/ia64/kernel/syscalls/syscall.tbl | 1 + arch/m68k/kernel/syscalls/syscall.tbl | 1 + arch/microblaze/kernel/syscalls/syscall.tbl | 1 + arch/mips/kernel/syscalls/syscall_n32.tbl | 1 + arch/mips/kernel/syscalls/syscall_n64.tbl | 1 + arch/mips/kernel/syscalls/syscall_o32.tbl | 1 + arch/parisc/kernel/syscalls/syscall.tbl | 1 + arch/powerpc/kernel/syscalls/syscall.tbl | 1 + arch/s390/kernel/syscalls/syscall.tbl | 1 + arch/sh/kernel/syscalls/syscall.tbl | 1 + arch/sparc/kernel/syscalls/syscall.tbl | 1 + arch/x86/entry/syscalls/syscall_32.tbl | 1 + arch/x86/entry/syscalls/syscall_64.tbl | 1 + arch/xtensa/kernel/syscalls/syscall.tbl | 1 + fs/namespace.c | 271 ++++++++++++++++++++ include/linux/syscalls.h | 3 + include/uapi/asm-generic/unistd.h | 4 +- include/uapi/linux/mount.h | 14 + tools/include/uapi/asm-generic/unistd.h | 4 +- 22 files changed, 312 insertions(+), 2 deletions(-) diff --git a/arch/alpha/kernel/syscalls/syscall.tbl b/arch/alpha/kernel/syscalls/syscall.tbl index ee7b01bb7346..24d8709624b8 100644 --- a/arch/alpha/kernel/syscalls/syscall.tbl +++ b/arch/alpha/kernel/syscalls/syscall.tbl @@ -480,3 +480,4 @@ 548 common pidfd_getfd sys_pidfd_getfd 549 common faccessat2 sys_faccessat2 550 common process_madvise sys_process_madvise +551 common mount_setattr sys_mount_setattr diff --git a/arch/arm/tools/syscall.tbl b/arch/arm/tools/syscall.tbl index d056a548358e..e3785513d445 100644 --- a/arch/arm/tools/syscall.tbl +++ b/arch/arm/tools/syscall.tbl @@ -454,3 +454,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/arm64/include/asm/unistd32.h b/arch/arm64/include/asm/unistd32.h index 107f08e03b9f..78af754e070a 100644 --- a/arch/arm64/include/asm/unistd32.h +++ b/arch/arm64/include/asm/unistd32.h @@ -889,6 +889,8 @@ __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd) __SYSCALL(__NR_faccessat2, sys_faccessat2) #define __NR_process_madvise 440 __SYSCALL(__NR_process_madvise, sys_process_madvise) +#define __NR_mount_setattr 441 +__SYSCALL(__NR_mount_setattr, sys_mount_setattr) /* * Please add new compat syscalls above this comment and update diff --git a/arch/ia64/kernel/syscalls/syscall.tbl b/arch/ia64/kernel/syscalls/syscall.tbl index b96ed8b8a508..f7d4b1f55be0 100644 --- a/arch/ia64/kernel/syscalls/syscall.tbl +++ b/arch/ia64/kernel/syscalls/syscall.tbl @@ -361,3 +361,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/m68k/kernel/syscalls/syscall.tbl b/arch/m68k/kernel/syscalls/syscall.tbl index 625fb6d32842..e96e9c6a6ffa 100644 --- a/arch/m68k/kernel/syscalls/syscall.tbl +++ b/arch/m68k/kernel/syscalls/syscall.tbl @@ -440,3 +440,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/microblaze/kernel/syscalls/syscall.tbl b/arch/microblaze/kernel/syscalls/syscall.tbl index aae729c95cf9..6538f075a18e 100644 --- a/arch/microblaze/kernel/syscalls/syscall.tbl +++ b/arch/microblaze/kernel/syscalls/syscall.tbl @@ -446,3 +446,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/mips/kernel/syscalls/syscall_n32.tbl b/arch/mips/kernel/syscalls/syscall_n32.tbl index 32817c954435..64d129db1aa7 100644 --- a/arch/mips/kernel/syscalls/syscall_n32.tbl +++ b/arch/mips/kernel/syscalls/syscall_n32.tbl @@ -379,3 +379,4 @@ 438 n32 pidfd_getfd sys_pidfd_getfd 439 n32 faccessat2 sys_faccessat2 440 n32 process_madvise sys_process_madvise +441 n32 mount_setattr sys_mount_setattr diff --git a/arch/mips/kernel/syscalls/syscall_n64.tbl b/arch/mips/kernel/syscalls/syscall_n64.tbl index 9e4ea3c31b1c..94b24e6b2608 100644 --- a/arch/mips/kernel/syscalls/syscall_n64.tbl +++ b/arch/mips/kernel/syscalls/syscall_n64.tbl @@ -355,3 +355,4 @@ 438 n64 pidfd_getfd sys_pidfd_getfd 439 n64 faccessat2 sys_faccessat2 440 n64 process_madvise sys_process_madvise +441 n64 mount_setattr sys_mount_setattr diff --git a/arch/mips/kernel/syscalls/syscall_o32.tbl b/arch/mips/kernel/syscalls/syscall_o32.tbl index 29f5f28cf5ce..eae522306767 100644 --- a/arch/mips/kernel/syscalls/syscall_o32.tbl +++ b/arch/mips/kernel/syscalls/syscall_o32.tbl @@ -428,3 +428,4 @@ 438 o32 pidfd_getfd sys_pidfd_getfd 439 o32 faccessat2 sys_faccessat2 440 o32 process_madvise sys_process_madvise +441 o32 mount_setattr sys_mount_setattr diff --git a/arch/parisc/kernel/syscalls/syscall.tbl b/arch/parisc/kernel/syscalls/syscall.tbl index f375ea528e59..c7e25f1d219f 100644 --- a/arch/parisc/kernel/syscalls/syscall.tbl +++ b/arch/parisc/kernel/syscalls/syscall.tbl @@ -438,3 +438,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/powerpc/kernel/syscalls/syscall.tbl b/arch/powerpc/kernel/syscalls/syscall.tbl index 1275daec7fec..0b309ef64e91 100644 --- a/arch/powerpc/kernel/syscalls/syscall.tbl +++ b/arch/powerpc/kernel/syscalls/syscall.tbl @@ -530,3 +530,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/s390/kernel/syscalls/syscall.tbl b/arch/s390/kernel/syscalls/syscall.tbl index 28c168000483..0b30398fee42 100644 --- a/arch/s390/kernel/syscalls/syscall.tbl +++ b/arch/s390/kernel/syscalls/syscall.tbl @@ -443,3 +443,4 @@ 438 common pidfd_getfd sys_pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr sys_mount_setattr diff --git a/arch/sh/kernel/syscalls/syscall.tbl b/arch/sh/kernel/syscalls/syscall.tbl index 783738448ff5..8e4949c5b740 100644 --- a/arch/sh/kernel/syscalls/syscall.tbl +++ b/arch/sh/kernel/syscalls/syscall.tbl @@ -443,3 +443,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/sparc/kernel/syscalls/syscall.tbl b/arch/sparc/kernel/syscalls/syscall.tbl index 78160260991b..409f21a650b8 100644 --- a/arch/sparc/kernel/syscalls/syscall.tbl +++ b/arch/sparc/kernel/syscalls/syscall.tbl @@ -486,3 +486,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/arch/x86/entry/syscalls/syscall_32.tbl b/arch/x86/entry/syscalls/syscall_32.tbl index 0d0667a9fbd7..2a694420f6cd 100644 --- a/arch/x86/entry/syscalls/syscall_32.tbl +++ b/arch/x86/entry/syscalls/syscall_32.tbl @@ -445,3 +445,4 @@ 438 i386 pidfd_getfd sys_pidfd_getfd 439 i386 faccessat2 sys_faccessat2 440 i386 process_madvise sys_process_madvise +441 i386 mount_setattr sys_mount_setattr diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl index 379819244b91..4d594d0246c1 100644 --- a/arch/x86/entry/syscalls/syscall_64.tbl +++ b/arch/x86/entry/syscalls/syscall_64.tbl @@ -362,6 +362,7 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr # # Due to a historical design error, certain syscalls are numbered differently diff --git a/arch/xtensa/kernel/syscalls/syscall.tbl b/arch/xtensa/kernel/syscalls/syscall.tbl index b070f272995d..a650dc05593d 100644 --- a/arch/xtensa/kernel/syscalls/syscall.tbl +++ b/arch/xtensa/kernel/syscalls/syscall.tbl @@ -411,3 +411,4 @@ 438 common pidfd_getfd sys_pidfd_getfd 439 common faccessat2 sys_faccessat2 440 common process_madvise sys_process_madvise +441 common mount_setattr sys_mount_setattr diff --git a/fs/namespace.c b/fs/namespace.c index b2a9eb7b43d3..51cfacfb3f82 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -73,6 +73,14 @@ static DECLARE_RWSEM(namespace_sem); static HLIST_HEAD(unmounted); /* protected by namespace_sem */ static LIST_HEAD(ex_mountpoints); /* protected by namespace_sem */ +struct mount_kattr { + unsigned int attr_set; + unsigned int attr_clr; + unsigned int propagation; + unsigned int lookup_flags; + bool recurse; +}; + /* /sys/fs */ struct kobject *fs_kobj; EXPORT_SYMBOL_GPL(fs_kobj); @@ -3454,6 +3462,8 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, (MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV | \ MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME | MOUNT_ATTR_NODIRATIME) +#define MOUNT_SETATTR_VALID_FLAGS FSMOUNT_VALID_FLAGS + static unsigned int attr_flags_to_mnt_flags(u64 attr_flags) { unsigned int mnt_flags = 0; @@ -3805,6 +3815,267 @@ SYSCALL_DEFINE2(pivot_root, const char __user *, new_root, return error; } +static unsigned int recalc_flags(struct mount_kattr *kattr, struct mount *mnt) +{ + unsigned int flags = mnt->mnt.mnt_flags; + + /* flags to clear */ + flags &= ~kattr->attr_clr; + /* flags to raise */ + flags |= kattr->attr_set; + + return flags; +} + +static struct mount *mount_setattr_prepare(struct mount_kattr *kattr, + struct mount *mnt, int *err) +{ + struct mount *m = mnt, *last = NULL; + + if (!is_mounted(&m->mnt)) { + *err = -EINVAL; + goto out; + } + + if (!(mnt_has_parent(m) ? check_mnt(m) : is_anon_ns(m->mnt_ns))) { + *err = -EINVAL; + goto out; + } + + do { + unsigned int flags; + + flags = recalc_flags(kattr, m); + if (!can_change_locked_flags(m, flags)) { + *err = -EPERM; + goto out; + } + + last = m; + + if ((kattr->attr_set & MNT_READONLY) && + !(m->mnt.mnt_flags & MNT_READONLY)) { + *err = mnt_hold_writers(m); + if (*err) + goto out; + } + } while (kattr->recurse && (m = next_mnt(m, mnt))); + +out: + return last; +} + +static void mount_setattr_commit(struct mount_kattr *kattr, + struct mount *mnt, struct mount *last, + int err) +{ + struct mount *m = mnt; + + do { + if (!err) { + unsigned int flags; + + flags = recalc_flags(kattr, m); + WRITE_ONCE(m->mnt.mnt_flags, flags); + } + + /* + * We either set MNT_READONLY above so make it visible + * before ~MNT_WRITE_HOLD or we failed to recursively + * apply mount options. + */ + if ((kattr->attr_set & MNT_READONLY) && + (m->mnt.mnt_flags & MNT_WRITE_HOLD)) + mnt_unhold_writers(m); + + if (!err && kattr->propagation) + change_mnt_propagation(m, kattr->propagation); + + /* + * On failure, only cleanup until we found the first mount + * we failed to handle. + */ + if (err && m == last) + break; + } while (kattr->recurse && (m = next_mnt(m, mnt))); + + if (!err) + touch_mnt_namespace(mnt->mnt_ns); +} + +static int do_mount_setattr(struct path *path, struct mount_kattr *kattr) +{ + struct mount *mnt = real_mount(path->mnt), *last = NULL; + int err = 0; + + if (path->dentry != mnt->mnt.mnt_root) + return -EINVAL; + + if (kattr->propagation) { + /* + * Only take namespace_lock() if we're actually changing + * propagation. + */ + namespace_lock(); + if (kattr->propagation == MS_SHARED) { + err = invent_group_ids(mnt, kattr->recurse); + if (err) { + namespace_unlock(); + return err; + } + } + } + + lock_mount_hash(); + + /* + * Get the mount tree in a shape where we can change mount + * properties without failure. + */ + last = mount_setattr_prepare(kattr, mnt, &err); + if (last) /* Commit all changes or revert to the old state. */ + mount_setattr_commit(kattr, mnt, last, err); + + unlock_mount_hash(); + + if (kattr->propagation) { + namespace_unlock(); + if (err) + cleanup_group_ids(mnt, NULL); + } + + return err; +} + +static int build_mount_kattr(const struct mount_attr *attr, + struct mount_kattr *kattr, unsigned int flags) +{ + unsigned int lookup_flags = LOOKUP_AUTOMOUNT | LOOKUP_FOLLOW; + + if (flags & AT_NO_AUTOMOUNT) + lookup_flags &= ~LOOKUP_AUTOMOUNT; + if (flags & AT_SYMLINK_NOFOLLOW) + lookup_flags &= ~LOOKUP_FOLLOW; + if (flags & AT_EMPTY_PATH) + lookup_flags |= LOOKUP_EMPTY; + + *kattr = (struct mount_kattr) { + .lookup_flags = lookup_flags, + .recurse = !!(flags & AT_RECURSIVE), + }; + + switch (attr->propagation) { + case 0: + kattr->propagation = 0; + break; + case MS_UNBINDABLE: + kattr->propagation = MS_UNBINDABLE; + break; + case MS_PRIVATE: + kattr->propagation = MS_PRIVATE; + break; + case MS_SLAVE: + kattr->propagation = MS_SLAVE; + break; + case MS_SHARED: + kattr->propagation = MS_SHARED; + break; + default: + return -EINVAL; + } + + if ((attr->attr_set | attr->attr_clr) & ~MOUNT_SETATTR_VALID_FLAGS) + return -EINVAL; + + kattr->attr_set = attr_flags_to_mnt_flags(attr->attr_set); + kattr->attr_clr = attr_flags_to_mnt_flags(attr->attr_clr); + + /* + * Since the MOUNT_ATTR_ values are an enum, not a bitmap, + * users wanting to transition to a different atime setting cannot + * simply specify the atime setting in @attr_set, but must also + * specify MOUNT_ATTR__ATIME in the @attr_clr field. + * So ensure that MOUNT_ATTR__ATIME can't be partially set in + * @attr_clr and that @attr_set can't have any atime bits set if + * MOUNT_ATTR__ATIME isn't set in @attr_clr. + */ + if (attr->attr_clr & MOUNT_ATTR__ATIME) { + if ((attr->attr_clr & MOUNT_ATTR__ATIME) != MOUNT_ATTR__ATIME) + return -EINVAL; + + /* + * Clear all previous time settings as they are mutually + * exclusive. + */ + kattr->attr_clr |= MNT_RELATIME | MNT_NOATIME; + switch (attr->attr_set & MOUNT_ATTR__ATIME) { + case MOUNT_ATTR_RELATIME: + kattr->attr_set |= MNT_RELATIME; + break; + case MOUNT_ATTR_NOATIME: + kattr->attr_set |= MNT_NOATIME; + break; + case MOUNT_ATTR_STRICTATIME: + break; + default: + return -EINVAL; + } + } else { + if (attr->attr_set & MOUNT_ATTR__ATIME) + return -EINVAL; + } + + return 0; +} + +SYSCALL_DEFINE5(mount_setattr, int, dfd, const char __user *, path, + unsigned int, flags, struct mount_attr __user *, uattr, + size_t, usize) +{ + int err; + struct path target; + struct mount_attr attr; + struct mount_kattr kattr; + + BUILD_BUG_ON(sizeof(struct mount_attr) != MOUNT_ATTR_SIZE_VER0); + + if (flags & ~(AT_EMPTY_PATH | + AT_RECURSIVE | + AT_SYMLINK_NOFOLLOW | + AT_NO_AUTOMOUNT)) + return -EINVAL; + + if (unlikely(usize > PAGE_SIZE)) + return -E2BIG; + if (unlikely(usize < MOUNT_ATTR_SIZE_VER0)) + return -EINVAL; + + if (!may_mount()) + return -EPERM; + + err = copy_struct_from_user(&attr, sizeof(attr), uattr, usize); + if (err) + return err; + + /* Don't bother walking through the mounts if this is a nop. */ + if (attr.attr_set == 0 && + attr.attr_clr == 0 && + attr.propagation == 0) + return 0; + + err = build_mount_kattr(&attr, &kattr, flags); + if (err) + return err; + + err = user_path_at(dfd, path, kattr.lookup_flags, &target); + if (err) + return err; + + err = do_mount_setattr(&target, &kattr); + path_put(&target); + return err; +} + static void __init init_mount_tree(void) { struct vfsmount *mnt; diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 37bea07c12f2..a62d5904fb6a 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h @@ -68,6 +68,7 @@ union bpf_attr; struct io_uring_params; struct clone_args; struct open_how; +struct mount_attr; #include #include @@ -999,6 +1000,8 @@ asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags); asmlinkage long sys_move_mount(int from_dfd, const char __user *from_path, int to_dfd, const char __user *to_path, unsigned int ms_flags); +asmlinkage long sys_mount_setattr(int dfd, const char __user *path, unsigned int flags, + struct mount_attr __user *uattr, size_t usize); asmlinkage long sys_fsopen(const char __user *fs_name, unsigned int flags); asmlinkage long sys_fsconfig(int fs_fd, unsigned int cmd, const char __user *key, const void __user *value, int aux); diff --git a/include/uapi/asm-generic/unistd.h b/include/uapi/asm-generic/unistd.h index 2056318988f7..0517f36fe783 100644 --- a/include/uapi/asm-generic/unistd.h +++ b/include/uapi/asm-generic/unistd.h @@ -859,9 +859,11 @@ __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd) __SYSCALL(__NR_faccessat2, sys_faccessat2) #define __NR_process_madvise 440 __SYSCALL(__NR_process_madvise, sys_process_madvise) +#define __NR_mount_setattr 441 +__SYSCALL(__NR_mount_setattr, sys_mount_setattr) #undef __NR_syscalls -#define __NR_syscalls 441 +#define __NR_syscalls 442 /* * 32 bit systems traditionally used different diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index dd8306ea336c..2255624e91c8 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h @@ -1,6 +1,8 @@ #ifndef _UAPI_LINUX_MOUNT_H #define _UAPI_LINUX_MOUNT_H +#include + /* * These are the fs-independent mount-flags: up to 32 flags are supported * @@ -118,4 +120,16 @@ enum fsconfig_command { #define MOUNT_ATTR_STRICTATIME 0x00000020 /* - Always perform atime updates */ #define MOUNT_ATTR_NODIRATIME 0x00000080 /* Do not update directory access times */ +/* + * mount_setattr() + */ +struct mount_attr { + __u64 attr_set; + __u64 attr_clr; + __u64 propagation; +}; + +/* List of all mount_attr versions. */ +#define MOUNT_ATTR_SIZE_VER0 24 /* sizeof first published struct */ + #endif /* _UAPI_LINUX_MOUNT_H */ diff --git a/tools/include/uapi/asm-generic/unistd.h b/tools/include/uapi/asm-generic/unistd.h index 2056318988f7..0517f36fe783 100644 --- a/tools/include/uapi/asm-generic/unistd.h +++ b/tools/include/uapi/asm-generic/unistd.h @@ -859,9 +859,11 @@ __SYSCALL(__NR_pidfd_getfd, sys_pidfd_getfd) __SYSCALL(__NR_faccessat2, sys_faccessat2) #define __NR_process_madvise 440 __SYSCALL(__NR_process_madvise, sys_process_madvise) +#define __NR_mount_setattr 441 +__SYSCALL(__NR_mount_setattr, sys_mount_setattr) #undef __NR_syscalls -#define __NR_syscalls 441 +#define __NR_syscalls 442 /* * 32 bit systems traditionally used different From patchwork Thu Dec 3 23:57:03 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950095 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 33AF4C19437 for ; Fri, 4 Dec 2020 00:02:34 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 0B6D622224 for ; Fri, 4 Dec 2020 00:02:34 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388482AbgLDACW (ORCPT ); Thu, 3 Dec 2020 19:02:22 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41707 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388087AbgLDACU (ORCPT ); Thu, 3 Dec 2020 19:02:20 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXO-0007ka-Py; Fri, 04 Dec 2020 00:01:26 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 07/40] tests: add mount_setattr() selftests Date: Fri, 4 Dec 2020 00:57:03 +0100 Message-Id: <20201203235736.3528991-8-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Add a range of selftests for the new mount_setattr() syscall to verify that it works as expected. This tests that: - no invalid flags can be specified - changing properties of a single mount works and leaves other mounts in the mount tree unchanged - changing a mount tre to read-only when one of the mounts has writers fails and leaves the whole mount tree unchanged - changing mount properties from multiple threads works - changing atime settings works - changing mount propagation works - changing the mount options of a mount tree where the individual mounts in the tree have different mount options only changes the flags that were requested to change - changing mount options from another mount namespace fails - changing mount options from another user namespace fails [==========] Running 9 tests from 2 test cases. [ RUN ] mount_setattr.invalid_attributes [ OK ] mount_setattr.invalid_attributes [ RUN ] mount_setattr.basic [ OK ] mount_setattr.basic [ RUN ] mount_setattr.basic_recursive [ OK ] mount_setattr.basic_recursive [ RUN ] mount_setattr.mount_has_writers [ OK ] mount_setattr.mount_has_writers [ RUN ] mount_setattr.mixed_mount_options [ OK ] mount_setattr.mixed_mount_options [ RUN ] mount_setattr.time_changes [ OK ] mount_setattr.time_changes [ RUN ] mount_setattr.multi_threaded [ OK ] mount_setattr.multi_threaded [ RUN ] mount_setattr.wrong_user_namespace [ OK ] mount_setattr.wrong_user_namespace [ RUN ] mount_setattr.wrong_mount_namespace [ OK ] mount_setattr.wrong_mount_namespace [==========] 9 / 9 tests passed. [ PASSED ] Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ unchanged --- tools/testing/selftests/Makefile | 1 + .../selftests/mount_setattr/.gitignore | 1 + .../testing/selftests/mount_setattr/Makefile | 7 + tools/testing/selftests/mount_setattr/config | 1 + .../mount_setattr/mount_setattr_test.c | 941 ++++++++++++++++++ 5 files changed, 951 insertions(+) create mode 100644 tools/testing/selftests/mount_setattr/.gitignore create mode 100644 tools/testing/selftests/mount_setattr/Makefile create mode 100644 tools/testing/selftests/mount_setattr/config create mode 100644 tools/testing/selftests/mount_setattr/mount_setattr_test.c diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile index d9c283503159..87b7107dd9a6 100644 --- a/tools/testing/selftests/Makefile +++ b/tools/testing/selftests/Makefile @@ -34,6 +34,7 @@ TARGETS += memfd TARGETS += memory-hotplug TARGETS += mincore TARGETS += mount +TARGETS += mount_setattr TARGETS += mqueue TARGETS += net TARGETS += net/forwarding diff --git a/tools/testing/selftests/mount_setattr/.gitignore b/tools/testing/selftests/mount_setattr/.gitignore new file mode 100644 index 000000000000..5f74d8488472 --- /dev/null +++ b/tools/testing/selftests/mount_setattr/.gitignore @@ -0,0 +1 @@ +mount_setattr_test diff --git a/tools/testing/selftests/mount_setattr/Makefile b/tools/testing/selftests/mount_setattr/Makefile new file mode 100644 index 000000000000..2250f7dcb81e --- /dev/null +++ b/tools/testing/selftests/mount_setattr/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for mount selftests. +CFLAGS = -g -I../../../../usr/include/ -Wall -O2 -pthread + +TEST_GEN_FILES += mount_setattr_test + +include ../lib.mk diff --git a/tools/testing/selftests/mount_setattr/config b/tools/testing/selftests/mount_setattr/config new file mode 100644 index 000000000000..416bd53ce982 --- /dev/null +++ b/tools/testing/selftests/mount_setattr/config @@ -0,0 +1 @@ +CONFIG_USER_NS=y diff --git a/tools/testing/selftests/mount_setattr/mount_setattr_test.c b/tools/testing/selftests/mount_setattr/mount_setattr_test.c new file mode 100644 index 000000000000..32fdb192629c --- /dev/null +++ b/tools/testing/selftests/mount_setattr/mount_setattr_test.c @@ -0,0 +1,941 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../kselftest_harness.h" + +#ifndef CLONE_NEWNS +#define CLONE_NEWNS 0x00020000 +#endif + +#ifndef CLONE_NEWUSER +#define CLONE_NEWUSER 0x10000000 +#endif + +#ifndef MS_REC +#define MS_REC 16384 +#endif + +#ifndef MS_RELATIME +#define MS_RELATIME (1 << 21) +#endif + +#ifndef MS_STRICTATIME +#define MS_STRICTATIME (1 << 24) +#endif + +#ifndef MOUNT_ATTR_RDONLY +#define MOUNT_ATTR_RDONLY 0x00000001 +#endif + +#ifndef MOUNT_ATTR_NOSUID +#define MOUNT_ATTR_NOSUID 0x00000002 +#endif + +#ifndef MOUNT_ATTR_NOEXEC +#define MOUNT_ATTR_NOEXEC 0x00000008 +#endif + +#ifndef MOUNT_ATTR_NODIRATIME +#define MOUNT_ATTR_NODIRATIME 0x00000080 +#endif + +#ifndef MOUNT_ATTR__ATIME +#define MOUNT_ATTR__ATIME 0x00000070 +#endif + +#ifndef MOUNT_ATTR_RELATIME +#define MOUNT_ATTR_RELATIME 0x00000000 +#endif + +#ifndef MOUNT_ATTR_NOATIME +#define MOUNT_ATTR_NOATIME 0x00000010 +#endif + +#ifndef MOUNT_ATTR_STRICTATIME +#define MOUNT_ATTR_STRICTATIME 0x00000020 +#endif + +#ifndef AT_RECURSIVE +#define AT_RECURSIVE 0x8000 +#endif + +#ifndef MS_SHARED +#define MS_SHARED (1 << 20) +#endif + +#define DEFAULT_THREADS 4 +#define ptr_to_int(p) ((int)((intptr_t)(p))) +#define int_to_ptr(u) ((void *)((intptr_t)(u))) + +#ifndef __NR_mount_setattr + #if defined __alpha__ + #define __NR_mount_setattr 551 + #elif defined _MIPS_SIM + #if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */ + #define __NR_mount_setattr 4441 + #endif + #if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */ + #define __NR_mount_setattr 6441 + #endif + #if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */ + #define __NR_mount_setattr 5441 + #endif + #elif defined __ia64__ + #define __NR_mount_setattr (441 + 1024) + #else + #define __NR_mount_setattr 441 + #endif + +struct mount_attr { + __u64 attr_set; + __u64 attr_clr; + __u64 propagation; +}; +#endif + +static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags, + struct mount_attr *attr, size_t size) +{ + return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); +} + +static ssize_t write_nointr(int fd, const void *buf, size_t count) +{ + ssize_t ret; + + do { + ret = write(fd, buf, count); + } while (ret < 0 && errno == EINTR); + + return ret; +} + +static int write_file(const char *path, const void *buf, size_t count) +{ + int fd; + ssize_t ret; + + fd = open(path, O_WRONLY | O_CLOEXEC | O_NOCTTY | O_NOFOLLOW); + if (fd < 0) + return -1; + + ret = write_nointr(fd, buf, count); + close(fd); + if (ret < 0 || (size_t)ret != count) + return -1; + + return 0; +} + +static int create_and_enter_userns(void) +{ + uid_t uid; + gid_t gid; + char map[100]; + + uid = getuid(); + gid = getgid(); + + if (unshare(CLONE_NEWUSER)) + return -1; + + if (write_file("/proc/self/setgroups", "deny", sizeof("deny") - 1) && + errno != ENOENT) + return -1; + + snprintf(map, sizeof(map), "0 %d 1", uid); + if (write_file("/proc/self/uid_map", map, strlen(map))) + return -1; + + + snprintf(map, sizeof(map), "0 %d 1", gid); + if (write_file("/proc/self/gid_map", map, strlen(map))) + return -1; + + if (setgid(0)) + return -1; + + if (setuid(0)) + return -1; + + return 0; +} + +static int prepare_unpriv_mountns(void) +{ + if (create_and_enter_userns()) + return -1; + + if (unshare(CLONE_NEWNS)) + return -1; + + if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0)) + return -1; + + return 0; +} + +static int read_mnt_flags(const char *path) +{ + int ret; + struct statvfs stat; + unsigned int mnt_flags; + + ret = statvfs(path, &stat); + if (ret != 0) + return -EINVAL; + + if (stat.f_flag & + ~(ST_RDONLY | ST_NOSUID | ST_NODEV | ST_NOEXEC | ST_NOATIME | + ST_NODIRATIME | ST_RELATIME | ST_SYNCHRONOUS | ST_MANDLOCK)) + return -EINVAL; + + mnt_flags = 0; + if (stat.f_flag & ST_RDONLY) + mnt_flags |= MS_RDONLY; + if (stat.f_flag & ST_NOSUID) + mnt_flags |= MS_NOSUID; + if (stat.f_flag & ST_NODEV) + mnt_flags |= MS_NODEV; + if (stat.f_flag & ST_NOEXEC) + mnt_flags |= MS_NOEXEC; + if (stat.f_flag & ST_NOATIME) + mnt_flags |= MS_NOATIME; + if (stat.f_flag & ST_NODIRATIME) + mnt_flags |= MS_NODIRATIME; + if (stat.f_flag & ST_RELATIME) + mnt_flags |= MS_RELATIME; + if (stat.f_flag & ST_SYNCHRONOUS) + mnt_flags |= MS_SYNCHRONOUS; + if (stat.f_flag & ST_MANDLOCK) + mnt_flags |= ST_MANDLOCK; + + return mnt_flags; +} + +static char *get_field(char *src, int nfields) +{ + int i; + char *p = src; + + for (i = 0; i < nfields; i++) { + while (*p && *p != ' ' && *p != '\t') + p++; + + if (!*p) + break; + + p++; + } + + return p; +} + +static void null_endofword(char *word) +{ + while (*word && *word != ' ' && *word != '\t') + word++; + *word = '\0'; +} + +static bool is_shared_mount(const char *path) +{ + size_t len = 0; + char *line = NULL; + FILE *f = NULL; + + f = fopen("/proc/self/mountinfo", "re"); + if (!f) + return false; + + while (getline(&line, &len, f) != -1) { + char *opts, *target; + + target = get_field(line, 4); + if (!target) + continue; + + opts = get_field(target, 2); + if (!opts) + continue; + + null_endofword(target); + + if (strcmp(target, path) != 0) + continue; + + null_endofword(opts); + if (strstr(opts, "shared:")) + return true; + } + + free(line); + fclose(f); + + return false; +} + +static void *mount_setattr_thread(void *data) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID, + .attr_clr = 0, + .propagation = MS_SHARED, + }; + + if (sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr))) + pthread_exit(int_to_ptr(-1)); + + pthread_exit(int_to_ptr(0)); +} + +/* Attempt to de-conflict with the selftests tree. */ +#ifndef SKIP +#define SKIP(s, ...) XFAIL(s, ##__VA_ARGS__) +#endif + +static bool mount_setattr_supported(void) +{ + int ret; + + ret = sys_mount_setattr(-EBADF, "", AT_EMPTY_PATH, NULL, 0); + if (ret < 0 && errno == ENOSYS) + return false; + + return true; +} + +FIXTURE(mount_setattr) { +}; + +FIXTURE_SETUP(mount_setattr) +{ + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + ASSERT_EQ(prepare_unpriv_mountns(), 0); + + (void)umount2("/mnt", MNT_DETACH); + (void)umount2("/tmp", MNT_DETACH); + + ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/tmp/B", 0777), 0); + + ASSERT_EQ(mount("testing", "/tmp/B", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0); + + ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mount("testing", "/mnt", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/mnt/A", 0777), 0); + + ASSERT_EQ(mount("testing", "/mnt/A", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/mnt/A/AA", 0777), 0); + + ASSERT_EQ(mount("/tmp", "/mnt/A/AA", NULL, MS_BIND | MS_REC, NULL), 0); + + ASSERT_EQ(mkdir("/mnt/B", 0777), 0); + + ASSERT_EQ(mount("testing", "/mnt/B", "ramfs", + MS_NOATIME | MS_NODEV | MS_NOSUID, 0), 0); + + ASSERT_EQ(mkdir("/mnt/B/BB", 0777), 0); + + ASSERT_EQ(mount("testing", "/tmp/B/BB", "devpts", + MS_RELATIME | MS_NOEXEC | MS_RDONLY, 0), 0); +} + +FIXTURE_TEARDOWN(mount_setattr) +{ + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + (void)umount2("/mnt/A", MNT_DETACH); + (void)umount2("/tmp", MNT_DETACH); +} + +TEST_F(mount_setattr, invalid_attributes) +{ + struct mount_attr invalid_attr = { + .attr_set = (1U << 31), + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, + sizeof(invalid_attr)), 0); + + invalid_attr.attr_set = 0; + invalid_attr.attr_clr = (1U << 31); + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, + sizeof(invalid_attr)), 0); + + invalid_attr.attr_clr = 0; + invalid_attr.propagation = (1U << 31); + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, + sizeof(invalid_attr)), 0); + + invalid_attr.attr_set = (1U << 31); + invalid_attr.attr_clr = (1U << 31); + invalid_attr.propagation = (1U << 31); + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, + sizeof(invalid_attr)), 0); + + ASSERT_NE(sys_mount_setattr(-1, "mnt/A", AT_RECURSIVE, &invalid_attr, + sizeof(invalid_attr)), 0); +} + +TEST_F(mount_setattr, extensibility) +{ + unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; + char *s = "dummy"; + struct mount_attr invalid_attr = {}; + struct mount_attr_large { + struct mount_attr attr1; + struct mount_attr attr2; + struct mount_attr attr3; + } large_attr = {}; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + old_flags = read_mnt_flags("/mnt/A"); + ASSERT_GT(old_flags, 0); + + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, NULL, + sizeof(invalid_attr)), 0); + ASSERT_EQ(errno, EFAULT); + + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, (void *)s, + sizeof(invalid_attr)), 0); + ASSERT_EQ(errno, EINVAL); + + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, 0), 0); + ASSERT_EQ(errno, EINVAL); + + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, + sizeof(invalid_attr) / 2), 0); + ASSERT_EQ(errno, EINVAL); + + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &invalid_attr, + sizeof(invalid_attr) / 2), 0); + ASSERT_EQ(errno, EINVAL); + + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, + (void *)&large_attr, sizeof(large_attr)), 0); + + large_attr.attr3.attr_set = MOUNT_ATTR_RDONLY; + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, + (void *)&large_attr, sizeof(large_attr)), 0); + + large_attr.attr3.attr_set = 0; + large_attr.attr1.attr_set = MOUNT_ATTR_RDONLY; + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, + (void *)&large_attr, sizeof(large_attr)), 0); + + expected_flags = old_flags; + expected_flags |= MS_RDONLY; + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); +} + +TEST_F(mount_setattr, basic) +{ + unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME, + .attr_clr = MOUNT_ATTR__ATIME, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + old_flags = read_mnt_flags("/mnt/A"); + ASSERT_GT(old_flags, 0); + + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", 0, &attr, sizeof(attr)), 0); + + expected_flags = old_flags; + expected_flags |= MS_RDONLY; + expected_flags |= MS_NOEXEC; + expected_flags &= ~MS_NOATIME; + expected_flags |= MS_RELATIME; + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, old_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, old_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, old_flags); +} + +TEST_F(mount_setattr, basic_recursive) +{ + int fd; + unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME, + .attr_clr = MOUNT_ATTR__ATIME, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + old_flags = read_mnt_flags("/mnt/A"); + ASSERT_GT(old_flags, 0); + + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags = old_flags; + expected_flags |= MS_RDONLY; + expected_flags |= MS_NOEXEC; + expected_flags &= ~MS_NOATIME; + expected_flags |= MS_RELATIME; + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + memset(&attr, 0, sizeof(attr)); + attr.attr_clr = MOUNT_ATTR_RDONLY; + attr.propagation = MS_SHARED; + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags &= ~MS_RDONLY; + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A"), true); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true); + + fd = open("/mnt/A/AA/B/b", O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 0777); + ASSERT_GE(fd, 0); + + /* + * We're holding a fd open for writing so this needs to fail somewhere + * in the middle and the mount options need to be unchanged. + */ + attr.attr_set = MOUNT_ATTR_RDONLY; + ASSERT_LT(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A"), true); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true); + + EXPECT_EQ(close(fd), 0); +} + +TEST_F(mount_setattr, mount_has_writers) +{ + int fd, dfd; + unsigned int old_flags = 0, new_flags = 0; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOEXEC | MOUNT_ATTR_RELATIME, + .attr_clr = MOUNT_ATTR__ATIME, + .propagation = MS_SHARED, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + old_flags = read_mnt_flags("/mnt/A"); + ASSERT_GT(old_flags, 0); + + fd = open("/mnt/A/AA/B/b", O_RDWR | O_CLOEXEC | O_CREAT | O_EXCL, 0777); + ASSERT_GE(fd, 0); + + /* + * We're holding a fd open to a mount somwhere in the middle so this + * needs to fail somewhere in the middle. After this the mount options + * need to be unchanged. + */ + ASSERT_LT(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, old_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A"), false); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, old_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA"), false); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, old_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), false); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, old_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), false); + + dfd = open("/mnt/A/AA/B", O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(dfd, 0); + EXPECT_EQ(fsync(dfd), 0); + EXPECT_EQ(close(dfd), 0); + + EXPECT_EQ(fsync(fd), 0); + EXPECT_EQ(close(fd), 0); + + /* All writers are gone so this should succeed. */ + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); +} + +TEST_F(mount_setattr, mixed_mount_options) +{ + unsigned int old_flags1 = 0, old_flags2 = 0, new_flags = 0, expected_flags = 0; + struct mount_attr attr = { + .attr_clr = MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME, + .attr_set = MOUNT_ATTR_RELATIME, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + old_flags1 = read_mnt_flags("/mnt/B"); + ASSERT_GT(old_flags1, 0); + + old_flags2 = read_mnt_flags("/mnt/B/BB"); + ASSERT_GT(old_flags2, 0); + + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/B", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags = old_flags2; + expected_flags &= ~(MS_RDONLY | MS_NOEXEC | MS_NOATIME | MS_NOSUID); + expected_flags |= MS_RELATIME; + + new_flags = read_mnt_flags("/mnt/B"); + ASSERT_EQ(new_flags, expected_flags); + + expected_flags = old_flags2; + expected_flags &= ~(MS_RDONLY | MS_NOEXEC | MS_NOATIME | MS_NOSUID); + expected_flags |= MS_RELATIME; + + new_flags = read_mnt_flags("/mnt/B/BB"); + ASSERT_EQ(new_flags, expected_flags); +} + +TEST_F(mount_setattr, time_changes) +{ + unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_NODIRATIME | MOUNT_ATTR_NOATIME, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + attr.attr_set = MOUNT_ATTR_STRICTATIME; + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + attr.attr_set = MOUNT_ATTR_STRICTATIME | MOUNT_ATTR_NOATIME; + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + attr.attr_set = MOUNT_ATTR_STRICTATIME | MOUNT_ATTR_NOATIME; + attr.attr_clr = MOUNT_ATTR__ATIME; + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + attr.attr_set = 0; + attr.attr_clr = MOUNT_ATTR_STRICTATIME; + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + attr.attr_clr = MOUNT_ATTR_NOATIME; + ASSERT_NE(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + old_flags = read_mnt_flags("/mnt/A"); + ASSERT_GT(old_flags, 0); + + attr.attr_set = MOUNT_ATTR_NODIRATIME | MOUNT_ATTR_NOATIME; + attr.attr_clr = MOUNT_ATTR__ATIME; + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags = old_flags; + expected_flags |= MS_NOATIME; + expected_flags |= MS_NODIRATIME; + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + memset(&attr, 0, sizeof(attr)); + attr.attr_set &= ~MOUNT_ATTR_NOATIME; + attr.attr_set |= MOUNT_ATTR_RELATIME; + attr.attr_clr |= MOUNT_ATTR__ATIME; + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags &= ~MS_NOATIME; + expected_flags |= MS_RELATIME; + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + memset(&attr, 0, sizeof(attr)); + attr.attr_set &= ~MOUNT_ATTR_RELATIME; + attr.attr_set |= MOUNT_ATTR_STRICTATIME; + attr.attr_clr |= MOUNT_ATTR__ATIME; + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags &= ~MS_RELATIME; + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + memset(&attr, 0, sizeof(attr)); + attr.attr_set &= ~MOUNT_ATTR_STRICTATIME; + attr.attr_set |= MOUNT_ATTR_NOATIME; + attr.attr_clr |= MOUNT_ATTR__ATIME; + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags |= MS_NOATIME; + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + memset(&attr, 0, sizeof(attr)); + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + memset(&attr, 0, sizeof(attr)); + attr.attr_clr = MOUNT_ATTR_NODIRATIME; + ASSERT_EQ(sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)), 0); + + expected_flags &= ~MS_NODIRATIME; + + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); +} + +TEST_F(mount_setattr, multi_threaded) +{ + int i, j, nthreads, ret = 0; + unsigned int old_flags = 0, new_flags = 0, expected_flags = 0; + pthread_attr_t pattr; + pthread_t threads[DEFAULT_THREADS]; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + old_flags = read_mnt_flags("/mnt/A"); + ASSERT_GT(old_flags, 0); + + /* Try to change mount options from multiple threads. */ + nthreads = get_nprocs_conf(); + if (nthreads > DEFAULT_THREADS) + nthreads = DEFAULT_THREADS; + + pthread_attr_init(&pattr); + for (i = 0; i < nthreads; i++) + ASSERT_EQ(pthread_create(&threads[i], &pattr, mount_setattr_thread, NULL), 0); + + for (j = 0; j < i; j++) { + void *retptr = NULL; + + EXPECT_EQ(pthread_join(threads[j], &retptr), 0); + + ret += ptr_to_int(retptr); + EXPECT_EQ(ret, 0); + } + pthread_attr_destroy(&pattr); + + ASSERT_EQ(ret, 0); + + expected_flags = old_flags; + expected_flags |= MS_RDONLY; + expected_flags |= MS_NOSUID; + new_flags = read_mnt_flags("/mnt/A"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A"), true); + + new_flags = read_mnt_flags("/mnt/A/AA"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA"), true); + + new_flags = read_mnt_flags("/mnt/A/AA/B"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B"), true); + + new_flags = read_mnt_flags("/mnt/A/AA/B/BB"); + ASSERT_EQ(new_flags, expected_flags); + + ASSERT_EQ(is_shared_mount("/mnt/A/AA/B/BB"), true); +} + +TEST_F(mount_setattr, wrong_user_namespace) +{ + int ret; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_RDONLY, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + EXPECT_EQ(create_and_enter_userns(), 0); + ret = sys_mount_setattr(-1, "/mnt/A", AT_RECURSIVE, &attr, sizeof(attr)); + ASSERT_LT(ret, 0); + ASSERT_EQ(errno, EPERM); +} + +TEST_F(mount_setattr, wrong_mount_namespace) +{ + int fd, ret; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_RDONLY, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + fd = open("/mnt/A", O_DIRECTORY | O_CLOEXEC); + ASSERT_GE(fd, 0); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + + ret = sys_mount_setattr(fd, "", AT_EMPTY_PATH | AT_RECURSIVE, &attr, sizeof(attr)); + ASSERT_LT(ret, 0); + ASSERT_EQ(errno, EINVAL); +} + +TEST_HARNESS_MAIN From patchwork Thu Dec 3 23:57:04 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950087 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id C4BDBC4361B for ; Fri, 4 Dec 2020 00:02:30 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 79724224F4 for ; Fri, 4 Dec 2020 00:02:28 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388332AbgLDACQ (ORCPT ); Thu, 3 Dec 2020 19:02:16 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41706 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388003AbgLDACO (ORCPT ); Thu, 3 Dec 2020 19:02:14 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXQ-0007ka-Jn; Fri, 04 Dec 2020 00:01:28 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 08/40] fs: add id translation helpers Date: Fri, 4 Dec 2020 00:57:04 +0100 Message-Id: <20201203235736.3528991-9-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Add simple helpers to make it easy to map kuids into and from idmapped mounts. We provide simple wrappers that filesystems can use to e.g. initialize inodes similar to i_{uid,gid}_read() and i_{uid,gid}_write(). Accessing an inode through an idmapped mount will require the inode to be mapped according to the mount's user namespace. If the fsids are used to compare against inodes or to initialize inodes they are required to be shifted from the mount's user namespace. Passing the initial user namespace to these helpers makes them a nop and so any non-idmapped paths will not be impacted. Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Reviewed-by: Christoph Hellwig --- /* v2 */ - Christoph Hellwig : - Get rid of the ifdefs and the config option that hid idmapped mounts. /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- include/linux/fs.h | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/include/linux/fs.h b/include/linux/fs.h index 8667d0cdc71e..0c820837d240 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -1574,6 +1575,52 @@ static inline void i_gid_write(struct inode *inode, gid_t gid) inode->i_gid = make_kgid(inode->i_sb->s_user_ns, gid); } +static inline kuid_t kuid_into_mnt(struct user_namespace *mnt_userns, + kuid_t kuid) +{ + return make_kuid(mnt_userns, __kuid_val(kuid)); +} + +static inline kgid_t kgid_into_mnt(struct user_namespace *mnt_userns, + kgid_t kgid) +{ + return make_kgid(mnt_userns, __kgid_val(kgid)); +} + +static inline kuid_t i_uid_into_mnt(struct user_namespace *mnt_userns, + const struct inode *inode) +{ + return kuid_into_mnt(mnt_userns, inode->i_uid); +} + +static inline kgid_t i_gid_into_mnt(struct user_namespace *mnt_userns, + const struct inode *inode) +{ + return kgid_into_mnt(mnt_userns, inode->i_gid); +} + +static inline kuid_t kuid_from_mnt(struct user_namespace *mnt_userns, + kuid_t kuid) +{ + return KUIDT_INIT(from_kuid(mnt_userns, kuid)); +} + +static inline kgid_t kgid_from_mnt(struct user_namespace *mnt_userns, + kgid_t kgid) +{ + return KGIDT_INIT(from_kgid(mnt_userns, kgid)); +} + +static inline kuid_t fsuid_into_mnt(struct user_namespace *mnt_userns) +{ + return kuid_from_mnt(mnt_userns, current_fsuid()); +} + +static inline kgid_t fsgid_into_mnt(struct user_namespace *mnt_userns) +{ + return kgid_from_mnt(mnt_userns, current_fsgid()); +} + extern struct timespec64 current_time(struct inode *inode); /* From patchwork Thu Dec 3 23:57:05 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950089 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8A904C3526C for ; Fri, 4 Dec 2020 00:02:31 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5E84C22224 for ; Fri, 4 Dec 2020 00:02:31 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388379AbgLDACR (ORCPT ); Thu, 3 Dec 2020 19:02:17 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41711 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388205AbgLDACP (ORCPT ); Thu, 3 Dec 2020 19:02:15 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXS-0007ka-C9; Fri, 04 Dec 2020 00:01:30 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 09/40] mount: attach mappings to mounts Date: Fri, 4 Dec 2020 00:57:05 +0100 Message-Id: <20201203235736.3528991-10-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org In order to support per-mount idmappings vfsmounts will be marked with user namespaces. The idmapping associated with that user namespace will be used to map the ids of vfs objects when they are accessed through that mount. By default all vfsmounts are marked with the initial user namespace. The initial user namespace is used to indicate that a mount is not idmapped. All operations behave as before. Based on prior discussions we want to attach the whole user namespace and not just a dedicated idmapping struct. This allows us to reuse all the helpers that already exist for dealing with idmappings instead of introducing a whole new range of helpers. In addition, if we decide in the future that we are confident enough to enable unprivileged users to setup idmapped mounts we can allow the user namespace an already idmapped mounts has been marked with to be replaced with another one. The permission checking would then take into account whether the caller is privileged in the user namespace the mount is currently marked with and that is about to be replaced with another one. For now, we will enforce in later patches that once a mount has been idmapped it can't be remapped. This keeps permission checking and life-cycle management simple, especially since users can always create a new mount with a different idmapping anyway. The idea to attach user namespaces to vfsmounts has been floated around in various forms at Linux Plumbers in ~2018 with the original idea tracing back to a discussion during a conference in St. Petersburg between Christoph, Tycho, and myself. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced - Christoph Hellwig : - Split internal implementation into separate patch and move syscall implementation later. /* v3 */ - David Howells : - Remove MNT_IDMAPPED flag. We can simply check the pointer and use smp_load_acquire() in later patches. - Tycho Andersen : - Use READ_ONCE() in mnt_user_ns(). /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. - Christoph Hellwig : - Drop the READ_ONCE() from this patch. At this point in the series we don't allowing changing the vfsmount's userns. The infra to do that is only introduced as almost the last patch in the series and there we immediately use smp_load_acquire() and smp_store_release(). --- fs/namespace.c | 9 +++++++++ include/linux/fs.h | 1 + include/linux/mount.h | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/fs/namespace.c b/fs/namespace.c index 51cfacfb3f82..ac55f5c8b2de 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -228,6 +228,7 @@ static struct mount *alloc_vfsmnt(const char *name) INIT_HLIST_NODE(&mnt->mnt_mp_list); INIT_LIST_HEAD(&mnt->mnt_umounting); INIT_HLIST_HEAD(&mnt->mnt_stuck_children); + mnt->mnt.mnt_userns = &init_user_ns; } return mnt; @@ -567,6 +568,11 @@ int sb_prepare_remount_readonly(struct super_block *sb) static void free_vfsmnt(struct mount *mnt) { + struct user_namespace *mnt_userns; + + mnt_userns = mnt_user_ns(&mnt->mnt); + if (mnt_userns != &init_user_ns) + put_user_ns(mnt_userns); kfree_const(mnt->mnt_devname); #ifdef CONFIG_SMP free_percpu(mnt->mnt_pcp); @@ -1075,6 +1081,9 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root, mnt->mnt.mnt_flags &= ~(MNT_WRITE_HOLD|MNT_MARKED|MNT_INTERNAL); atomic_inc(&sb->s_active); + mnt->mnt.mnt_userns = mnt_user_ns(&old->mnt); + if (mnt->mnt.mnt_userns != &init_user_ns) + mnt->mnt.mnt_userns = get_user_ns(mnt->mnt.mnt_userns); mnt->mnt.mnt_sb = sb; mnt->mnt.mnt_root = dget(root); mnt->mnt_mountpoint = mnt->mnt.mnt_root; diff --git a/include/linux/fs.h b/include/linux/fs.h index 0c820837d240..b4935793605f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2280,6 +2280,7 @@ struct file_system_type { #define FS_HAS_SUBTYPE 4 #define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */ #define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */ +#define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */ #define FS_THP_SUPPORT 8192 /* Remove once all fs converted */ #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ int (*init_fs_context)(struct fs_context *); diff --git a/include/linux/mount.h b/include/linux/mount.h index aaf343b38671..52de25e08319 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -72,8 +72,14 @@ struct vfsmount { struct dentry *mnt_root; /* root of the mounted tree */ struct super_block *mnt_sb; /* pointer to superblock */ int mnt_flags; + struct user_namespace *mnt_userns; } __randomize_layout; +static inline struct user_namespace *mnt_user_ns(const struct vfsmount *mnt) +{ + return mnt->mnt_userns; +} + struct file; /* forward dec */ struct path; From patchwork Thu Dec 3 23:57:06 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950093 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id A4D17C4361A for ; Fri, 4 Dec 2020 00:02:32 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 713A6222B6 for ; Fri, 4 Dec 2020 00:02:32 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388420AbgLDACU (ORCPT ); Thu, 3 Dec 2020 19:02:20 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41712 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388241AbgLDACS (ORCPT ); Thu, 3 Dec 2020 19:02:18 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXU-0007ka-7Q; Fri, 04 Dec 2020 00:01:32 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 10/40] capability: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:06 +0100 Message-Id: <20201203235736.3528991-11-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org In order to determine whether a caller holds privilege over a given inode the capability framework exposes the two helpers privileged_wrt_inode_uidgid() and capable_wrt_inode_uidgid(). The former verifies that the inode has a mapping in the caller's user namespace and the latter additionally verifies that the caller has the requested capability in their current user namespace. If the inode is accessed through an idmapped mount we simply need to map it according to the mount's user namespace. Afterwards the checks are identical to non-idmapped inodes. If the initial user namespace is passed all operations are a nop so non-idmapped mounts will not see a change in behavior and will also not see any performance impact. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Acked-by: Serge Hallyn --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/attr.c | 8 ++++---- fs/exec.c | 2 +- fs/inode.c | 2 +- fs/namei.c | 13 ++++++++----- fs/overlayfs/super.c | 2 +- fs/posix_acl.c | 2 +- fs/xfs/xfs_ioctl.c | 2 +- include/linux/capability.h | 7 +++++-- kernel/capability.c | 14 +++++++++----- security/commoncap.c | 5 +++-- 10 files changed, 34 insertions(+), 23 deletions(-) diff --git a/fs/attr.c b/fs/attr.c index b4bbdbd4c8ca..d270f640a192 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -23,7 +23,7 @@ static bool chown_ok(const struct inode *inode, kuid_t uid) if (uid_eq(current_fsuid(), inode->i_uid) && uid_eq(uid, inode->i_uid)) return true; - if (capable_wrt_inode_uidgid(inode, CAP_CHOWN)) + if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN)) return true; if (uid_eq(inode->i_uid, INVALID_UID) && ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN)) @@ -36,7 +36,7 @@ static bool chgrp_ok(const struct inode *inode, kgid_t gid) if (uid_eq(current_fsuid(), inode->i_uid) && (in_group_p(gid) || gid_eq(gid, inode->i_gid))) return true; - if (capable_wrt_inode_uidgid(inode, CAP_CHOWN)) + if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN)) return true; if (gid_eq(inode->i_gid, INVALID_GID) && ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN)) @@ -92,7 +92,7 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr) /* Also check the setgid bit! */ if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid : inode->i_gid) && - !capable_wrt_inode_uidgid(inode, CAP_FSETID)) + !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) attr->ia_mode &= ~S_ISGID; } @@ -193,7 +193,7 @@ void setattr_copy(struct inode *inode, const struct iattr *attr) umode_t mode = attr->ia_mode; if (!in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(inode, CAP_FSETID)) + !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) mode &= ~S_ISGID; inode->i_mode = mode; } diff --git a/fs/exec.c b/fs/exec.c index 547a2390baf5..8e75d7a33514 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1398,7 +1398,7 @@ void would_dump(struct linux_binprm *bprm, struct file *file) /* Ensure mm->user_ns contains the executable */ user_ns = old = bprm->mm->user_ns; while ((user_ns != &init_user_ns) && - !privileged_wrt_inode_uidgid(user_ns, inode)) + !privileged_wrt_inode_uidgid(user_ns, &init_user_ns, inode)) user_ns = user_ns->parent; if (old != user_ns) { diff --git a/fs/inode.c b/fs/inode.c index 9d78c37b00b8..7a15372d9c2d 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2147,7 +2147,7 @@ void inode_init_owner(struct inode *inode, const struct inode *dir, mode |= S_ISGID; else if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP) && !in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(dir, CAP_FSETID)) + !capable_wrt_inode_uidgid(&init_user_ns, dir, CAP_FSETID)) mode &= ~S_ISGID; } else inode->i_gid = current_fsgid(); diff --git a/fs/namei.c b/fs/namei.c index d4a6dd772303..3f52730af6c5 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -357,10 +357,11 @@ int generic_permission(struct inode *inode, int mask) if (S_ISDIR(inode->i_mode)) { /* DACs are overridable for directories */ if (!(mask & MAY_WRITE)) - if (capable_wrt_inode_uidgid(inode, + if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_DAC_READ_SEARCH)) return 0; - if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE)) + if (capable_wrt_inode_uidgid(&init_user_ns, inode, + CAP_DAC_OVERRIDE)) return 0; return -EACCES; } @@ -370,7 +371,8 @@ int generic_permission(struct inode *inode, int mask) */ mask &= MAY_READ | MAY_WRITE | MAY_EXEC; if (mask == MAY_READ) - if (capable_wrt_inode_uidgid(inode, CAP_DAC_READ_SEARCH)) + if (capable_wrt_inode_uidgid(&init_user_ns, inode, + CAP_DAC_READ_SEARCH)) return 0; /* * Read/write DACs are always overridable. @@ -378,7 +380,8 @@ int generic_permission(struct inode *inode, int mask) * at least one exec bit set. */ if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO)) - if (capable_wrt_inode_uidgid(inode, CAP_DAC_OVERRIDE)) + if (capable_wrt_inode_uidgid(&init_user_ns, inode, + CAP_DAC_OVERRIDE)) return 0; return -EACCES; @@ -2657,7 +2660,7 @@ int __check_sticky(struct inode *dir, struct inode *inode) return 0; if (uid_eq(dir->i_uid, fsuid)) return 0; - return !capable_wrt_inode_uidgid(inode, CAP_FOWNER); + return !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FOWNER); } EXPORT_SYMBOL(__check_sticky); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 290983bcfbb3..196fe3e3f02b 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -972,7 +972,7 @@ ovl_posix_acl_xattr_set(const struct xattr_handler *handler, if (unlikely(inode->i_mode & S_ISGID) && handler->flags == ACL_TYPE_ACCESS && !in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(inode, CAP_FSETID)) { + !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) { struct iattr iattr = { .ia_valid = ATTR_KILL_SGID }; err = ovl_setattr(dentry, &iattr); diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 95882b3f5f62..4ca6d53c6f0a 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -656,7 +656,7 @@ int posix_acl_update_mode(struct inode *inode, umode_t *mode_p, if (error == 0) *acl = NULL; if (!in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(inode, CAP_FSETID)) + !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) mode &= ~S_ISGID; *mode_p = mode; return 0; diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 3fbd98f61ea5..97bd29fc8c43 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1502,7 +1502,7 @@ xfs_ioctl_setattr( */ if ((VFS_I(ip)->i_mode & (S_ISUID|S_ISGID)) && - !capable_wrt_inode_uidgid(VFS_I(ip), CAP_FSETID)) + !capable_wrt_inode_uidgid(&init_user_ns, VFS_I(ip), CAP_FSETID)) VFS_I(ip)->i_mode &= ~(S_ISUID|S_ISGID); /* Change the ownerships and register project quota modifications */ diff --git a/include/linux/capability.h b/include/linux/capability.h index 1e7fe311cabe..b810392a5fba 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -247,8 +247,11 @@ static inline bool ns_capable_setid(struct user_namespace *ns, int cap) return true; } #endif /* CONFIG_MULTIUSER */ -extern bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode *inode); -extern bool capable_wrt_inode_uidgid(const struct inode *inode, int cap); +extern bool privileged_wrt_inode_uidgid(struct user_namespace *ns, + struct user_namespace *mnt_userns, + const struct inode *inode); +extern bool capable_wrt_inode_uidgid(struct user_namespace *mnt_userns, + const struct inode *inode, int cap); extern bool file_ns_capable(const struct file *file, struct user_namespace *ns, int cap); extern bool ptracer_capable(struct task_struct *tsk, struct user_namespace *ns); static inline bool perfmon_capable(void) diff --git a/kernel/capability.c b/kernel/capability.c index de7eac903a2a..46a361dde042 100644 --- a/kernel/capability.c +++ b/kernel/capability.c @@ -484,10 +484,12 @@ EXPORT_SYMBOL(file_ns_capable); * * Return true if the inode uid and gid are within the namespace. */ -bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode *inode) +bool privileged_wrt_inode_uidgid(struct user_namespace *ns, + struct user_namespace *mnt_userns, + const struct inode *inode) { - return kuid_has_mapping(ns, inode->i_uid) && - kgid_has_mapping(ns, inode->i_gid); + return kuid_has_mapping(ns, i_uid_into_mnt(mnt_userns, inode)) && + kgid_has_mapping(ns, i_gid_into_mnt(mnt_userns, inode)); } /** @@ -499,11 +501,13 @@ bool privileged_wrt_inode_uidgid(struct user_namespace *ns, const struct inode * * its own user namespace and that the given inode's uid and gid are * mapped into the current user namespace. */ -bool capable_wrt_inode_uidgid(const struct inode *inode, int cap) +bool capable_wrt_inode_uidgid(struct user_namespace *mnt_userns, + const struct inode *inode, int cap) { struct user_namespace *ns = current_user_ns(); - return ns_capable(ns, cap) && privileged_wrt_inode_uidgid(ns, inode); + return ns_capable(ns, cap) && + privileged_wrt_inode_uidgid(ns, mnt_userns, inode); } EXPORT_SYMBOL(capable_wrt_inode_uidgid); diff --git a/security/commoncap.c b/security/commoncap.c index 59bf3c1674c8..4cd2bdfd0a8b 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -489,7 +489,7 @@ int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size) return -EINVAL; if (!validheader(size, cap)) return -EINVAL; - if (!capable_wrt_inode_uidgid(inode, CAP_SETFCAP)) + if (!capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_SETFCAP)) return -EPERM; if (size == XATTR_CAPS_SZ_2) if (ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP)) @@ -957,7 +957,8 @@ int cap_inode_removexattr(struct dentry *dentry, const char *name) struct inode *inode = d_backing_inode(dentry); if (!inode) return -EINVAL; - if (!capable_wrt_inode_uidgid(inode, CAP_SETFCAP)) + if (!capable_wrt_inode_uidgid(&init_user_ns, inode, + CAP_SETFCAP)) return -EPERM; return 0; } From patchwork Thu Dec 3 23:57:07 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950101 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 98BF7C433FE for ; Fri, 4 Dec 2020 00:02:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 7A65222285 for ; Fri, 4 Dec 2020 00:02:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388523AbgLDAC2 (ORCPT ); Thu, 3 Dec 2020 19:02:28 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41723 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388205AbgLDAC1 (ORCPT ); Thu, 3 Dec 2020 19:02:27 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXW-0007ka-Ef; Fri, 04 Dec 2020 00:01:34 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 11/40] namei: make permission helpers idmapped mount aware Date: Fri, 4 Dec 2020 00:57:07 +0100 Message-Id: <20201203235736.3528991-12-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The two helpers inode_permission() and generic_permission() are used by the vfs to perform basic permission checking by verifying that the caller is privileged over an inode. In order to handle idmapped mounts we extend the two helpers with an additional user namespace argument. On idmapped mounts the two helpers will make sure to map the inode according to the mount's user namespace and then peform identical permission checks to inode_permission() and generic_permission(). If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner Acked-by: Serge Hallyn --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ unchanged /* v4 */ - "Serge E. Hallyn" : - Add proper documentation for all the changed permission checking helpers and adjust terminology to avoid any potential confusion. - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. - Christoph Hellwig : - Change commit message to reflect the fact that no new permission helpers are introduced but only the existing ones changed. --- fs/attr.c | 3 +- fs/btrfs/inode.c | 2 +- fs/btrfs/ioctl.c | 10 +-- fs/ceph/inode.c | 2 +- fs/cifs/cifsfs.c | 2 +- fs/configfs/symlink.c | 2 +- fs/ecryptfs/inode.c | 2 +- fs/exec.c | 2 +- fs/fuse/dir.c | 4 +- fs/gfs2/inode.c | 2 +- fs/hostfs/hostfs_kern.c | 2 +- fs/init.c | 9 ++- fs/kernfs/inode.c | 2 +- fs/libfs.c | 7 +- fs/namei.c | 121 ++++++++++++++++++++--------- fs/nfs/dir.c | 2 +- fs/nfsd/nfsfh.c | 2 +- fs/nfsd/vfs.c | 4 +- fs/nilfs2/inode.c | 2 +- fs/notify/fanotify/fanotify_user.c | 2 +- fs/notify/inotify/inotify_user.c | 2 +- fs/ocfs2/file.c | 2 +- fs/ocfs2/refcounttree.c | 4 +- fs/open.c | 10 +-- fs/orangefs/inode.c | 2 +- fs/overlayfs/file.c | 2 +- fs/overlayfs/inode.c | 4 +- fs/overlayfs/util.c | 2 +- fs/posix_acl.c | 17 ++-- fs/proc/base.c | 4 +- fs/proc/fd.c | 2 +- fs/reiserfs/xattr.c | 2 +- fs/remap_range.c | 2 +- fs/udf/file.c | 2 +- fs/verity/enable.c | 2 +- fs/xattr.c | 2 +- include/linux/fs.h | 4 +- include/linux/posix_acl.h | 4 +- ipc/mqueue.c | 2 +- kernel/bpf/inode.c | 4 +- kernel/cgroup/cgroup.c | 2 +- kernel/sys.c | 2 +- mm/madvise.c | 2 +- mm/memcontrol.c | 2 +- mm/mincore.c | 2 +- net/unix/af_unix.c | 2 +- 46 files changed, 168 insertions(+), 103 deletions(-) diff --git a/fs/attr.c b/fs/attr.c index d270f640a192..c9e29e589cec 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -244,7 +244,8 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de return -EPERM; if (!inode_owner_or_capable(inode)) { - error = inode_permission(inode, MAY_WRITE); + error = inode_permission(&init_user_ns, inode, + MAY_WRITE); if (error) return error; } diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 7e8d8169779d..ea4a70850f5e 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -9852,7 +9852,7 @@ static int btrfs_permission(struct inode *inode, int mask) if (BTRFS_I(inode)->flags & BTRFS_INODE_READONLY) return -EACCES; } - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } static int btrfs_tmpfile(struct inode *dir, struct dentry *dentry, umode_t mode) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 69a384145dc6..74500c15bdc1 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -910,7 +910,7 @@ static int btrfs_may_delete(struct inode *dir, struct dentry *victim, int isdir) BUG_ON(d_inode(victim->d_parent) != dir); audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE); - error = inode_permission(dir, MAY_WRITE | MAY_EXEC); + error = inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); if (error) return error; if (IS_APPEND(dir)) @@ -939,7 +939,7 @@ static inline int btrfs_may_create(struct inode *dir, struct dentry *child) return -EEXIST; if (IS_DEADDIR(dir)) return -ENOENT; - return inode_permission(dir, MAY_WRITE | MAY_EXEC); + return inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); } /* @@ -2487,7 +2487,7 @@ static int btrfs_search_path_in_tree_user(struct inode *inode, ret = PTR_ERR(temp_inode); goto out_put; } - ret = inode_permission(temp_inode, MAY_READ | MAY_EXEC); + ret = inode_permission(&init_user_ns, temp_inode, MAY_READ | MAY_EXEC); iput(temp_inode); if (ret) { ret = -EACCES; @@ -3017,7 +3017,7 @@ static noinline int btrfs_ioctl_snap_destroy(struct file *file, if (root == dest) goto out_dput; - err = inode_permission(inode, MAY_WRITE | MAY_EXEC); + err = inode_permission(&init_user_ns, inode, MAY_WRITE | MAY_EXEC); if (err) goto out_dput; } @@ -3088,7 +3088,7 @@ static int btrfs_ioctl_defrag(struct file *file, void __user *argp) * running and allows defrag on files open in read-only mode. */ if (!capable(CAP_SYS_ADMIN) && - inode_permission(inode, MAY_WRITE)) { + inode_permission(&init_user_ns, inode, MAY_WRITE)) { ret = -EPERM; goto out; } diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 526faf4778ce..abfe42df8a1a 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -2335,7 +2335,7 @@ int ceph_permission(struct inode *inode, int mask) err = ceph_do_getattr(inode, CEPH_CAP_AUTH_SHARED, false); if (!err) - err = generic_permission(inode, mask); + err = generic_permission(&init_user_ns, inode, mask); return err; } diff --git a/fs/cifs/cifsfs.c b/fs/cifs/cifsfs.c index 472cb7777e3e..f79dbc581eee 100644 --- a/fs/cifs/cifsfs.c +++ b/fs/cifs/cifsfs.c @@ -316,7 +316,7 @@ static int cifs_permission(struct inode *inode, int mask) on the client (above and beyond ACL on servers) for servers which do not support setting and viewing mode bits, so allowing client to check permissions is useful */ - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } static struct kmem_cache *cifs_inode_cachep; diff --git a/fs/configfs/symlink.c b/fs/configfs/symlink.c index cb61467478ca..0b592c55f38e 100644 --- a/fs/configfs/symlink.c +++ b/fs/configfs/symlink.c @@ -197,7 +197,7 @@ int configfs_symlink(struct inode *dir, struct dentry *dentry, const char *symna if (dentry->d_inode || d_unhashed(dentry)) ret = -EEXIST; else - ret = inode_permission(dir, MAY_WRITE | MAY_EXEC); + ret = inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); if (!ret) ret = type->ct_item_ops->allow_link(parent_item, target_item); if (!ret) { diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index e23752d9a79f..9b1ae410983c 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -864,7 +864,7 @@ int ecryptfs_truncate(struct dentry *dentry, loff_t new_length) static int ecryptfs_permission(struct inode *inode, int mask) { - return inode_permission(ecryptfs_inode_to_lower(inode), mask); + return inode_permission(&init_user_ns, ecryptfs_inode_to_lower(inode), mask); } /** diff --git a/fs/exec.c b/fs/exec.c index 8e75d7a33514..b499a1a03934 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1391,7 +1391,7 @@ EXPORT_SYMBOL(begin_new_exec); void would_dump(struct linux_binprm *bprm, struct file *file) { struct inode *inode = file_inode(file); - if (inode_permission(inode, MAY_READ) < 0) { + if (inode_permission(&init_user_ns, inode, MAY_READ) < 0) { struct user_namespace *old, *user_ns; bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index ff7dbeb16f88..7ce7baed3f4f 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1254,7 +1254,7 @@ static int fuse_permission(struct inode *inode, int mask) } if (fc->default_permissions) { - err = generic_permission(inode, mask); + err = generic_permission(&init_user_ns, inode, mask); /* If permission is denied, try to refresh file attributes. This is also needed, because the root @@ -1262,7 +1262,7 @@ static int fuse_permission(struct inode *inode, int mask) if (err == -EACCES && !refreshed) { err = fuse_perm_getattr(inode, mask); if (!err) - err = generic_permission(inode, mask); + err = generic_permission(&init_user_ns, inode, mask); } /* Note: the opposite of the above test does not diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 077ccb1b3ccc..8445588b58a6 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1851,7 +1851,7 @@ int gfs2_permission(struct inode *inode, int mask) if ((mask & MAY_WRITE) && IS_IMMUTABLE(inode)) error = -EPERM; else - error = generic_permission(inode, mask); + error = generic_permission(&init_user_ns, inode, mask); if (gfs2_holder_initialized(&i_gh)) gfs2_glock_dq_uninit(&i_gh); diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index c070c0d8e3e9..36da0c31d053 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -779,7 +779,7 @@ static int hostfs_permission(struct inode *ino, int desired) err = access_file(name, r, w, x); __putname(name); if (!err) - err = generic_permission(ino, desired); + err = generic_permission(&init_user_ns, ino, desired); return err; } diff --git a/fs/init.c b/fs/init.c index e9c320a48cf1..2b4842f4802b 100644 --- a/fs/init.c +++ b/fs/init.c @@ -49,7 +49,8 @@ int __init init_chdir(const char *filename) error = kern_path(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path); if (error) return error; - error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); + error = inode_permission(&init_user_ns, path.dentry->d_inode, + MAY_EXEC | MAY_CHDIR); if (!error) set_fs_pwd(current->fs, &path); path_put(&path); @@ -64,7 +65,8 @@ int __init init_chroot(const char *filename) error = kern_path(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path); if (error) return error; - error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); + error = inode_permission(&init_user_ns, path.dentry->d_inode, + MAY_EXEC | MAY_CHDIR); if (error) goto dput_and_out; error = -EPERM; @@ -118,7 +120,8 @@ int __init init_eaccess(const char *filename) error = kern_path(filename, LOOKUP_FOLLOW, &path); if (error) return error; - error = inode_permission(d_inode(path.dentry), MAY_ACCESS); + error = inode_permission(&init_user_ns, d_inode(path.dentry), + MAY_ACCESS); path_put(&path); return error; } diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index fc2469a20fed..ff5598cc1de0 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -285,7 +285,7 @@ int kernfs_iop_permission(struct inode *inode, int mask) kernfs_refresh_inode(kn, inode); mutex_unlock(&kernfs_mutex); - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } int kernfs_xattr_get(struct kernfs_node *kn, const char *name, diff --git a/fs/libfs.c b/fs/libfs.c index 7124c2e8df2f..fdd3d51035b6 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -1318,9 +1318,14 @@ static ssize_t empty_dir_listxattr(struct dentry *dentry, char *list, size_t siz return -EOPNOTSUPP; } +static int empty_dir_permission(struct inode *inode, int mask) +{ + return generic_permission(&init_user_ns, inode, mask); +} + static const struct inode_operations empty_dir_inode_operations = { .lookup = empty_dir_lookup, - .permission = generic_permission, + .permission = empty_dir_permission, .setattr = empty_dir_setattr, .getattr = empty_dir_getattr, .listxattr = empty_dir_listxattr, diff --git a/fs/namei.c b/fs/namei.c index 3f52730af6c5..79814dbbfc82 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -259,7 +259,23 @@ void putname(struct filename *name) __putname(name); } -static int check_acl(struct inode *inode, int mask) +/** + * check_acl - perform ACL permission checking + * @mnt_userns: user namespace of the mount the inode was found from + * @inode: inode to check permissions on + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC ...) + * + * This function performs the ACL permission checking. Since this function + * retrieve POSIX acls it needs to know whether it is called from a blocking or + * non-blocking context and thus cares about the MAY_NOT_BLOCK bit. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +static int check_acl(struct user_namespace *mnt_userns, struct inode *inode, int mask) { #ifdef CONFIG_FS_POSIX_ACL struct posix_acl *acl; @@ -271,14 +287,14 @@ static int check_acl(struct inode *inode, int mask) /* no ->get_acl() calls in RCU mode... */ if (is_uncached_acl(acl)) return -ECHILD; - return posix_acl_permission(inode, acl, mask); + return posix_acl_permission(mnt_userns, inode, acl, mask); } acl = get_acl(inode, ACL_TYPE_ACCESS); if (IS_ERR(acl)) return PTR_ERR(acl); if (acl) { - int error = posix_acl_permission(inode, acl, mask); + int error = posix_acl_permission(mnt_userns, inode, acl, mask); posix_acl_release(acl); return error; } @@ -287,18 +303,32 @@ static int check_acl(struct inode *inode, int mask) return -EAGAIN; } -/* - * This does the basic UNIX permission checking. +/** + * acl_permission_check - perform basic UNIX permission checking + * @mnt_userns: user namespace of the mount the inode was found from + * @inode: inode to check permissions on + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC ...) + * + * This function performs the basic UNIX permission checking. Since this + * function may retrieve POSIX acls it needs to know whether it is called from a + * blocking or non-blocking context and thus cares about the MAY_NOT_BLOCK bit. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. * - * Note that the POSIX ACL check cares about the MAY_NOT_BLOCK bit, - * for RCU walking. */ -static int acl_permission_check(struct inode *inode, int mask) +static int acl_permission_check(struct user_namespace *mnt_userns, + struct inode *inode, int mask) { unsigned int mode = inode->i_mode; + kuid_t i_uid; /* Are we the owner? If so, ACL's don't matter */ - if (likely(uid_eq(current_fsuid(), inode->i_uid))) { + i_uid = i_uid_into_mnt(mnt_userns, inode); + if (likely(uid_eq(current_fsuid(), i_uid))) { mask &= 7; mode >>= 6; return (mask & ~mode) ? -EACCES : 0; @@ -306,7 +336,7 @@ static int acl_permission_check(struct inode *inode, int mask) /* Do we have ACL's? */ if (IS_POSIXACL(inode) && (mode & S_IRWXG)) { - int error = check_acl(inode, mask); + int error = check_acl(mnt_userns, inode, mask); if (error != -EAGAIN) return error; } @@ -320,7 +350,8 @@ static int acl_permission_check(struct inode *inode, int mask) * about? Need to check group ownership if so. */ if (mask & (mode ^ (mode >> 3))) { - if (in_group_p(inode->i_gid)) + kgid_t kgid = i_gid_into_mnt(mnt_userns, inode); + if (in_group_p(kgid)) mode >>= 3; } @@ -330,6 +361,7 @@ static int acl_permission_check(struct inode *inode, int mask) /** * generic_permission - check for access rights on a Posix-like filesystem + * @mnt_userns: user namespace of the mount the inode was found from * @inode: inode to check access rights for * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC, * %MAY_NOT_BLOCK ...) @@ -342,26 +374,32 @@ static int acl_permission_check(struct inode *inode, int mask) * generic_permission is rcu-walk aware. It returns -ECHILD in case an rcu-walk * request cannot be satisfied (eg. requires blocking or too much complexity). * It would then be called again in ref-walk mode. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. */ -int generic_permission(struct inode *inode, int mask) +int generic_permission(struct user_namespace *mnt_userns, struct inode *inode, + int mask) { int ret; /* * Do the basic permission checks. */ - ret = acl_permission_check(inode, mask); + ret = acl_permission_check(mnt_userns, inode, mask); if (ret != -EACCES) return ret; if (S_ISDIR(inode->i_mode)) { /* DACs are overridable for directories */ if (!(mask & MAY_WRITE)) - if (capable_wrt_inode_uidgid(&init_user_ns, inode, + if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_DAC_READ_SEARCH)) return 0; - if (capable_wrt_inode_uidgid(&init_user_ns, inode, - CAP_DAC_OVERRIDE)) + if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_DAC_OVERRIDE)) return 0; return -EACCES; } @@ -371,7 +409,7 @@ int generic_permission(struct inode *inode, int mask) */ mask &= MAY_READ | MAY_WRITE | MAY_EXEC; if (mask == MAY_READ) - if (capable_wrt_inode_uidgid(&init_user_ns, inode, + if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_DAC_READ_SEARCH)) return 0; /* @@ -380,21 +418,26 @@ int generic_permission(struct inode *inode, int mask) * at least one exec bit set. */ if (!(mask & MAY_EXEC) || (inode->i_mode & S_IXUGO)) - if (capable_wrt_inode_uidgid(&init_user_ns, inode, - CAP_DAC_OVERRIDE)) + if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_DAC_OVERRIDE)) return 0; return -EACCES; } EXPORT_SYMBOL(generic_permission); -/* +/** + * do_inode_permission - UNIX permission checking + * @mnt_userns: user namespace of the mount the inode was found from + * @inode: inode to check permissions on + * @mask: right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC ...) + * * We _really_ want to just do "generic_permission()" without * even looking at the inode->i_op values. So we keep a cache * flag in inode->i_opflags, that says "this has not special * permission function, use the fast case". */ -static inline int do_inode_permission(struct inode *inode, int mask) +static inline int do_inode_permission(struct user_namespace *mnt_userns, + struct inode *inode, int mask) { if (unlikely(!(inode->i_opflags & IOP_FASTPERM))) { if (likely(inode->i_op->permission)) @@ -405,7 +448,7 @@ static inline int do_inode_permission(struct inode *inode, int mask) inode->i_opflags |= IOP_FASTPERM; spin_unlock(&inode->i_lock); } - return generic_permission(inode, mask); + return generic_permission(mnt_userns, inode, mask); } /** @@ -430,8 +473,9 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask) /** * inode_permission - Check for access rights to a given inode - * @inode: Inode to check permission on - * @mask: Right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) + * @mnt_userns: User namespace of the mount the inode was found from + * @inode: Inode to check permission on + * @mask: Right to check for (%MAY_READ, %MAY_WRITE, %MAY_EXEC) * * Check for read/write/execute permissions on an inode. We use fs[ug]id for * this, letting us set arbitrary permissions for filesystem access without @@ -439,7 +483,8 @@ static int sb_permission(struct super_block *sb, struct inode *inode, int mask) * * When checking for MAY_APPEND, MAY_WRITE must also be set in @mask. */ -int inode_permission(struct inode *inode, int mask) +int inode_permission(struct user_namespace *mnt_userns, + struct inode *inode, int mask) { int retval; @@ -463,7 +508,7 @@ int inode_permission(struct inode *inode, int mask) return -EACCES; } - retval = do_inode_permission(inode, mask); + retval = do_inode_permission(mnt_userns, inode, mask); if (retval) return retval; @@ -1009,7 +1054,7 @@ static bool safe_hardlink_source(struct inode *inode) return false; /* Hardlinking to unreadable or unwritable sources is dangerous. */ - if (inode_permission(inode, MAY_READ | MAY_WRITE)) + if (inode_permission(&init_user_ns, inode, MAY_READ | MAY_WRITE)) return false; return true; @@ -1569,13 +1614,14 @@ static struct dentry *lookup_slow(const struct qstr *name, static inline int may_lookup(struct nameidata *nd) { if (nd->flags & LOOKUP_RCU) { - int err = inode_permission(nd->inode, MAY_EXEC|MAY_NOT_BLOCK); + int err = inode_permission(&init_user_ns, nd->inode, + MAY_EXEC | MAY_NOT_BLOCK); if (err != -ECHILD) return err; if (unlazy_walk(nd)) return -ECHILD; } - return inode_permission(nd->inode, MAY_EXEC); + return inode_permission(&init_user_ns, nd->inode, MAY_EXEC); } static int reserve_stack(struct nameidata *nd, struct path *link, unsigned seq) @@ -2507,7 +2553,7 @@ static int lookup_one_len_common(const char *name, struct dentry *base, return err; } - return inode_permission(base->d_inode, MAY_EXEC); + return inode_permission(&init_user_ns, base->d_inode, MAY_EXEC); } /** @@ -2701,7 +2747,7 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir) audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE); - error = inode_permission(dir, MAY_WRITE | MAY_EXEC); + error = inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); if (error) return error; if (IS_APPEND(dir)) @@ -2745,7 +2791,7 @@ static inline int may_create(struct inode *dir, struct dentry *child) if (!kuid_has_mapping(s_user_ns, current_fsuid()) || !kgid_has_mapping(s_user_ns, current_fsgid())) return -EOVERFLOW; - return inode_permission(dir, MAY_WRITE | MAY_EXEC); + return inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); } /* @@ -2875,7 +2921,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) break; } - error = inode_permission(inode, MAY_OPEN | acc_mode); + error = inode_permission(&init_user_ns, inode, MAY_OPEN | acc_mode); if (error) return error; @@ -2937,7 +2983,8 @@ static int may_o_create(const struct path *dir, struct dentry *dentry, umode_t m !kgid_has_mapping(s_user_ns, current_fsgid())) return -EOVERFLOW; - error = inode_permission(dir->dentry->d_inode, MAY_WRITE | MAY_EXEC); + error = inode_permission(&init_user_ns, dir->dentry->d_inode, + MAY_WRITE | MAY_EXEC); if (error) return error; @@ -3274,7 +3321,7 @@ struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, int open_flag) int error; /* we want directory to be writable */ - error = inode_permission(dir, MAY_WRITE | MAY_EXEC); + error = inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); if (error) goto out_err; error = -EOPNOTSUPP; @@ -4265,12 +4312,12 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, */ if (new_dir != old_dir) { if (is_dir) { - error = inode_permission(source, MAY_WRITE); + error = inode_permission(&init_user_ns, source, MAY_WRITE); if (error) return error; } if ((flags & RENAME_EXCHANGE) && new_is_dir) { - error = inode_permission(target, MAY_WRITE); + error = inode_permission(&init_user_ns, target, MAY_WRITE); if (error) return error; } diff --git a/fs/nfs/dir.c b/fs/nfs/dir.c index 4e011adaf967..170ecc575519 100644 --- a/fs/nfs/dir.c +++ b/fs/nfs/dir.c @@ -2788,7 +2788,7 @@ int nfs_permission(struct inode *inode, int mask) res = nfs_revalidate_inode(NFS_SERVER(inode), inode); if (res == 0) - res = generic_permission(inode, mask); + res = generic_permission(&init_user_ns, inode, mask); goto out; } EXPORT_SYMBOL_GPL(nfs_permission); diff --git a/fs/nfsd/nfsfh.c b/fs/nfsd/nfsfh.c index c81dbbad8792..380f0f74740c 100644 --- a/fs/nfsd/nfsfh.c +++ b/fs/nfsd/nfsfh.c @@ -40,7 +40,7 @@ static int nfsd_acceptable(void *expv, struct dentry *dentry) /* make sure parents give x permission to user */ int err; parent = dget_parent(tdentry); - err = inode_permission(d_inode(parent), MAY_EXEC); + err = inode_permission(&init_user_ns, d_inode(parent), MAY_EXEC); if (err < 0) { dput(parent); break; diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 1ecaceebee13..3cad79c3b441 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -2380,13 +2380,13 @@ nfsd_permission(struct svc_rqst *rqstp, struct svc_export *exp, return 0; /* This assumes NFSD_MAY_{READ,WRITE,EXEC} == MAY_{READ,WRITE,EXEC} */ - err = inode_permission(inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC)); + err = inode_permission(&init_user_ns, inode, acc & (MAY_READ|MAY_WRITE|MAY_EXEC)); /* Allow read access to binaries even when mode 111 */ if (err == -EACCES && S_ISREG(inode->i_mode) && (acc == (NFSD_MAY_READ | NFSD_MAY_OWNER_OVERRIDE) || acc == (NFSD_MAY_READ | NFSD_MAY_READ_IF_EXEC))) - err = inode_permission(inode, MAY_EXEC); + err = inode_permission(&init_user_ns, inode, MAY_EXEC); return err? nfserrno(err) : 0; } diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 745d371d6fea..b6517220cad5 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -851,7 +851,7 @@ int nilfs_permission(struct inode *inode, int mask) root->cno != NILFS_CPTREE_CURRENT_CNO) return -EROFS; /* snapshot is not writable */ - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } int nilfs_load_inode_block(struct inode *inode, struct buffer_head **pbh) diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index 3e01d8f2ab90..de4d01bb1d8d 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -702,7 +702,7 @@ static int fanotify_find_path(int dfd, const char __user *filename, } /* you can only watch an inode if you have read permissions on it */ - ret = inode_permission(path->dentry->d_inode, MAY_READ); + ret = inode_permission(&init_user_ns, path->dentry->d_inode, MAY_READ); if (ret) { path_put(path); goto out; diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index 186722ba3894..e995fd4e4e53 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -343,7 +343,7 @@ static int inotify_find_inode(const char __user *dirname, struct path *path, if (error) return error; /* you can only watch an inode if you have read permissions on it */ - error = inode_permission(path->dentry->d_inode, MAY_READ); + error = inode_permission(&init_user_ns, path->dentry->d_inode, MAY_READ); if (error) { path_put(path); return error; diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index 85979e2214b3..0c75619adf54 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -1355,7 +1355,7 @@ int ocfs2_permission(struct inode *inode, int mask) dump_stack(); } - ret = generic_permission(inode, mask); + ret = generic_permission(&init_user_ns, inode, mask); ocfs2_inode_unlock_tracker(inode, 0, &oh, had_lock); out: diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c index 3b397fa9c9e8..c26937824be1 100644 --- a/fs/ocfs2/refcounttree.c +++ b/fs/ocfs2/refcounttree.c @@ -4346,7 +4346,7 @@ static inline int ocfs2_may_create(struct inode *dir, struct dentry *child) return -EEXIST; if (IS_DEADDIR(dir)) return -ENOENT; - return inode_permission(dir, MAY_WRITE | MAY_EXEC); + return inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); } /** @@ -4400,7 +4400,7 @@ static int ocfs2_vfs_reflink(struct dentry *old_dentry, struct inode *dir, * file. */ if (!preserve) { - error = inode_permission(inode, MAY_READ); + error = inode_permission(&init_user_ns, inode, MAY_READ); if (error) return error; } diff --git a/fs/open.c b/fs/open.c index 9af548fb841b..b0e8430e29f3 100644 --- a/fs/open.c +++ b/fs/open.c @@ -83,7 +83,7 @@ long vfs_truncate(const struct path *path, loff_t length) if (error) goto out; - error = inode_permission(inode, MAY_WRITE); + error = inode_permission(&init_user_ns, inode, MAY_WRITE); if (error) goto mnt_drop_write_and_out; @@ -436,7 +436,7 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla goto out_path_release; } - res = inode_permission(inode, mode | MAY_ACCESS); + res = inode_permission(&init_user_ns, inode, mode | MAY_ACCESS); /* SuS v2 requires we report a read only fs too */ if (res || !(mode & S_IWOTH) || special_file(inode->i_mode)) goto out_path_release; @@ -492,7 +492,7 @@ SYSCALL_DEFINE1(chdir, const char __user *, filename) if (error) goto out; - error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); + error = inode_permission(&init_user_ns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); if (error) goto dput_and_out; @@ -521,7 +521,7 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd) if (!d_can_lookup(f.file->f_path.dentry)) goto out_putf; - error = inode_permission(file_inode(f.file), MAY_EXEC | MAY_CHDIR); + error = inode_permission(&init_user_ns, file_inode(f.file), MAY_EXEC | MAY_CHDIR); if (!error) set_fs_pwd(current->fs, &f.file->f_path); out_putf: @@ -540,7 +540,7 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename) if (error) goto out; - error = inode_permission(path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); + error = inode_permission(&init_user_ns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); if (error) goto dput_and_out; diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 48f0547d4850..4c790cc8042d 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -933,7 +933,7 @@ int orangefs_permission(struct inode *inode, int mask) if (ret < 0) return ret; - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } int orangefs_update_time(struct inode *inode, struct timespec64 *time, int flags) diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index efccb7c1f9bc..f966b5108358 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -50,7 +50,7 @@ static struct file *ovl_open_realfile(const struct file *file, acc_mode |= MAY_APPEND; old_cred = ovl_override_creds(inode->i_sb); - err = inode_permission(realinode, MAY_OPEN | acc_mode); + err = inode_permission(&init_user_ns, realinode, MAY_OPEN | acc_mode); if (err) { realfile = ERR_PTR(err); } else if (!inode_owner_or_capable(realinode)) { diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index b584dca845ba..c8e3c17ca131 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -294,7 +294,7 @@ int ovl_permission(struct inode *inode, int mask) * Check overlay inode with the creds of task and underlying inode * with creds of mounter */ - err = generic_permission(inode, mask); + err = generic_permission(&init_user_ns, inode, mask); if (err) return err; @@ -305,7 +305,7 @@ int ovl_permission(struct inode *inode, int mask) /* Make sure mounter can read file for copy up later */ mask |= MAY_READ; } - err = inode_permission(realinode, mask); + err = inode_permission(&init_user_ns, realinode, mask); revert_creds(old_cred); return err; diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 23f475627d07..ff67da201303 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -476,7 +476,7 @@ struct file *ovl_path_open(struct path *path, int flags) BUG(); } - err = inode_permission(inode, acc_mode | MAY_OPEN); + err = inode_permission(&init_user_ns, inode, acc_mode | MAY_OPEN); if (err) return ERR_PTR(err); diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 4ca6d53c6f0a..5d9fe2fb2953 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -345,10 +345,13 @@ EXPORT_SYMBOL(posix_acl_from_mode); * by the acl. Returns -E... otherwise. */ int -posix_acl_permission(struct inode *inode, const struct posix_acl *acl, int want) +posix_acl_permission(struct user_namespace *mnt_userns, struct inode *inode, + const struct posix_acl *acl, int want) { const struct posix_acl_entry *pa, *pe, *mask_obj; int found = 0; + kuid_t uid; + kgid_t gid; want &= MAY_READ | MAY_WRITE | MAY_EXEC; @@ -356,22 +359,26 @@ posix_acl_permission(struct inode *inode, const struct posix_acl *acl, int want) switch(pa->e_tag) { case ACL_USER_OBJ: /* (May have been checked already) */ - if (uid_eq(inode->i_uid, current_fsuid())) + uid = i_uid_into_mnt(mnt_userns, inode); + if (uid_eq(uid, current_fsuid())) goto check_perm; break; case ACL_USER: - if (uid_eq(pa->e_uid, current_fsuid())) + uid = kuid_into_mnt(mnt_userns, pa->e_uid); + if (uid_eq(uid, current_fsuid())) goto mask; break; case ACL_GROUP_OBJ: - if (in_group_p(inode->i_gid)) { + gid = i_gid_into_mnt(mnt_userns, inode); + if (in_group_p(gid)) { found = 1; if ((pa->e_perm & want) == want) goto mask; } break; case ACL_GROUP: - if (in_group_p(pa->e_gid)) { + gid = kgid_into_mnt(mnt_userns, pa->e_gid); + if (in_group_p(gid)) { found = 1; if ((pa->e_perm & want) == want) goto mask; diff --git a/fs/proc/base.c b/fs/proc/base.c index b362523a9829..ff7259f820c8 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -751,7 +751,7 @@ static int proc_pid_permission(struct inode *inode, int mask) return -EPERM; } - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } @@ -3489,7 +3489,7 @@ static int proc_tid_comm_permission(struct inode *inode, int mask) return 0; } - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } static const struct inode_operations proc_tid_comm_inode_operations = { diff --git a/fs/proc/fd.c b/fs/proc/fd.c index 81882a13212d..ad692a39381d 100644 --- a/fs/proc/fd.c +++ b/fs/proc/fd.c @@ -299,7 +299,7 @@ int proc_fd_permission(struct inode *inode, int mask) struct task_struct *p; int rv; - rv = generic_permission(inode, mask); + rv = generic_permission(&init_user_ns, inode, mask); if (rv == 0) return rv; diff --git a/fs/reiserfs/xattr.c b/fs/reiserfs/xattr.c index fe63a7c3e0da..ec440d1957a1 100644 --- a/fs/reiserfs/xattr.c +++ b/fs/reiserfs/xattr.c @@ -957,7 +957,7 @@ int reiserfs_permission(struct inode *inode, int mask) if (IS_PRIVATE(inode)) return 0; - return generic_permission(inode, mask); + return generic_permission(&init_user_ns, inode, mask); } static int xattr_hide_revalidate(struct dentry *dentry, unsigned int flags) diff --git a/fs/remap_range.c b/fs/remap_range.c index e6099beefa97..9e5b27641756 100644 --- a/fs/remap_range.c +++ b/fs/remap_range.c @@ -438,7 +438,7 @@ static bool allow_file_dedupe(struct file *file) return true; if (uid_eq(current_fsuid(), file_inode(file)->i_uid)) return true; - if (!inode_permission(file_inode(file), MAY_WRITE)) + if (!inode_permission(&init_user_ns, file_inode(file), MAY_WRITE)) return true; return false; } diff --git a/fs/udf/file.c b/fs/udf/file.c index ad8eefad27d7..928283925d68 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -183,7 +183,7 @@ long udf_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) long old_block, new_block; int result; - if (inode_permission(inode, MAY_READ) != 0) { + if (inode_permission(&init_user_ns, inode, MAY_READ) != 0) { udf_debug("no permission to access inode %lu\n", inode->i_ino); return -EPERM; } diff --git a/fs/verity/enable.c b/fs/verity/enable.c index 5ab3bbec8108..7449ef0050f4 100644 --- a/fs/verity/enable.c +++ b/fs/verity/enable.c @@ -369,7 +369,7 @@ int fsverity_ioctl_enable(struct file *filp, const void __user *uarg) * has verity enabled, and to stabilize the data being hashed. */ - err = inode_permission(inode, MAY_WRITE); + err = inode_permission(&init_user_ns, inode, MAY_WRITE); if (err) return err; diff --git a/fs/xattr.c b/fs/xattr.c index cd7a563e8bcd..61a9947f62f4 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -131,7 +131,7 @@ xattr_permission(struct inode *inode, const char *name, int mask) return -EPERM; } - return inode_permission(inode, mask); + return inode_permission(&init_user_ns, inode, mask); } /* diff --git a/include/linux/fs.h b/include/linux/fs.h index b4935793605f..8fb288dd9185 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2807,8 +2807,8 @@ static inline int bmap(struct inode *inode, sector_t *block) #endif extern int notify_change(struct dentry *, struct iattr *, struct inode **); -extern int inode_permission(struct inode *, int); -extern int generic_permission(struct inode *, int); +extern int inode_permission(struct user_namespace *, struct inode *, int); +extern int generic_permission(struct user_namespace *, struct inode *, int); extern int __check_sticky(struct inode *dir, struct inode *inode); static inline bool execute_ok(struct inode *inode) diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h index 90797f1b421d..8276baefed13 100644 --- a/include/linux/posix_acl.h +++ b/include/linux/posix_acl.h @@ -15,6 +15,8 @@ #include #include +struct user_namespace; + struct posix_acl_entry { short e_tag; unsigned short e_perm; @@ -62,7 +64,7 @@ posix_acl_release(struct posix_acl *acl) extern void posix_acl_init(struct posix_acl *, int); extern struct posix_acl *posix_acl_alloc(int, gfp_t); extern int posix_acl_valid(struct user_namespace *, const struct posix_acl *); -extern int posix_acl_permission(struct inode *, const struct posix_acl *, int); +extern int posix_acl_permission(struct user_namespace *, struct inode *, const struct posix_acl *, int); extern struct posix_acl *posix_acl_from_mode(umode_t, gfp_t); extern int posix_acl_equiv_mode(const struct posix_acl *, umode_t *); extern int __posix_acl_create(struct posix_acl **, gfp_t, umode_t *); diff --git a/ipc/mqueue.c b/ipc/mqueue.c index beff0cfcd1e8..693f01fe1216 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -873,7 +873,7 @@ static int prepare_open(struct dentry *dentry, int oflag, int ro, if ((oflag & O_ACCMODE) == (O_RDWR | O_WRONLY)) return -EINVAL; acc = oflag2acc[oflag & O_ACCMODE]; - return inode_permission(d_inode(dentry), acc); + return inode_permission(&init_user_ns, d_inode(dentry), acc); } static int do_mq_open(const char __user *u_name, int oflag, umode_t mode, diff --git a/kernel/bpf/inode.c b/kernel/bpf/inode.c index dd4b7fd60ee7..f1c393e5d47d 100644 --- a/kernel/bpf/inode.c +++ b/kernel/bpf/inode.c @@ -507,7 +507,7 @@ static void *bpf_obj_do_get(const char __user *pathname, return ERR_PTR(ret); inode = d_backing_inode(path.dentry); - ret = inode_permission(inode, ACC_MODE(flags)); + ret = inode_permission(&init_user_ns, inode, ACC_MODE(flags)); if (ret) goto out; @@ -558,7 +558,7 @@ int bpf_obj_get_user(const char __user *pathname, int flags) static struct bpf_prog *__get_prog_inode(struct inode *inode, enum bpf_prog_type type) { struct bpf_prog *prog; - int ret = inode_permission(inode, MAY_READ); + int ret = inode_permission(&init_user_ns, inode, MAY_READ); if (ret) return ERR_PTR(ret); diff --git a/kernel/cgroup/cgroup.c b/kernel/cgroup/cgroup.c index e41c21819ba0..b13aab5a9715 100644 --- a/kernel/cgroup/cgroup.c +++ b/kernel/cgroup/cgroup.c @@ -4673,7 +4673,7 @@ static int cgroup_may_write(const struct cgroup *cgrp, struct super_block *sb) if (!inode) return -ENOMEM; - ret = inode_permission(inode, MAY_WRITE); + ret = inode_permission(&init_user_ns, inode, MAY_WRITE); iput(inode); return ret; } diff --git a/kernel/sys.c b/kernel/sys.c index a730c03ee607..469a659c8e84 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -1847,7 +1847,7 @@ static int prctl_set_mm_exe_file(struct mm_struct *mm, unsigned int fd) if (!S_ISREG(inode->i_mode) || path_noexec(&exe.file->f_path)) goto exit; - err = inode_permission(inode, MAY_EXEC); + err = inode_permission(&init_user_ns, inode, MAY_EXEC); if (err) goto exit; diff --git a/mm/madvise.c b/mm/madvise.c index a8d8d48a57fe..bd87d0019266 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -540,7 +540,7 @@ static inline bool can_do_pageout(struct vm_area_struct *vma) * opens a side channel. */ return inode_owner_or_capable(file_inode(vma->vm_file)) || - inode_permission(file_inode(vma->vm_file), MAY_WRITE) == 0; + inode_permission(&init_user_ns, file_inode(vma->vm_file), MAY_WRITE) == 0; } static long madvise_pageout(struct vm_area_struct *vma, diff --git a/mm/memcontrol.c b/mm/memcontrol.c index 29459a6ce1c7..d90f3b72f385 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -4911,7 +4911,7 @@ static ssize_t memcg_write_event_control(struct kernfs_open_file *of, /* the process need read permission on control file */ /* AV: shouldn't we check that it's been opened for read instead? */ - ret = inode_permission(file_inode(cfile.file), MAY_READ); + ret = inode_permission(&init_user_ns, file_inode(cfile.file), MAY_READ); if (ret < 0) goto out_put_cfile; diff --git a/mm/mincore.c b/mm/mincore.c index 02db1a834021..d5a58e61eac6 100644 --- a/mm/mincore.c +++ b/mm/mincore.c @@ -167,7 +167,7 @@ static inline bool can_do_mincore(struct vm_area_struct *vma) * mappings, which opens a side channel. */ return inode_owner_or_capable(file_inode(vma->vm_file)) || - inode_permission(file_inode(vma->vm_file), MAY_WRITE) == 0; + inode_permission(&init_user_ns, file_inode(vma->vm_file), MAY_WRITE) == 0; } static const struct mm_walk_ops mincore_walk_ops = { diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index 41c3303c3357..f568526d4a02 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -936,7 +936,7 @@ static struct sock *unix_find_other(struct net *net, if (err) goto fail; inode = d_backing_inode(path.dentry); - err = inode_permission(inode, MAY_WRITE); + err = inode_permission(&init_user_ns, inode, MAY_WRITE); if (err) goto put_fail; From patchwork Thu Dec 3 23:57:08 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950099 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B1B3CC49EC9 for ; Fri, 4 Dec 2020 00:02:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 8411A222B6 for ; Fri, 4 Dec 2020 00:02:37 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389324AbgLDACd (ORCPT ); Thu, 3 Dec 2020 19:02:33 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41731 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388388AbgLDACa (ORCPT ); Thu, 3 Dec 2020 19:02:30 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXY-0007ka-9A; Fri, 04 Dec 2020 00:01:36 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 12/40] inode: make init and permission helpers idmapped mount aware Date: Fri, 4 Dec 2020 00:57:08 +0100 Message-Id: <20201203235736.3528991-13-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The inode_owner_or_capable() helper determines whether the caller is the owner of the inode or is capable with respect to that inode. Allow it to handle idmapped mounts. If the inode is accessed through an idmapped mount we first need to map it according to the mount's user namespace. Afterwards the checks are identical to non-idmapped mounts. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Similarly, we allow the inode_init_owner() helper to handle idmapped mounts. It initializes a new inode on idmapped mounts by mapping the fsuid and fsgid of the caller from the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ unchanged /* v4 */ - Christoph Hellwig : - Change commit message to reflect the fact that no new permission helpers are introduced but only the existing ones changed. - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. - Note that inode_owner_or_capable() contains a comparison of the caller's fsuid to the inode's i_uid to determine whether the caller is the owner of the inode. Serge asked whether it is possible that the caller's fsuid is INVALID_UID. If so and i_uid_into_mnt() makes the inode's i_uid INVALID_UID too then the caller could potentially gain privilege over an inode it doesn't own. But this is not feasible since the callers fsuid should never be INVALID_UID unless we have a severe bug how we manage set*id() calls or cred management. Also, if it is possible that the caller's fsuid were to be INVALID_UID we might already have an issue in case a filesystem was mounted with an s_user_ns that doesn't contain a mapping for a inode that was created when the filesystem was mounted with a different user namespace. --- fs/9p/acl.c | 2 +- fs/9p/vfs_inode.c | 2 +- fs/attr.c | 6 +++--- fs/bfs/dir.c | 2 +- fs/btrfs/inode.c | 2 +- fs/btrfs/ioctl.c | 10 +++++----- fs/btrfs/tests/btrfs-tests.c | 2 +- fs/crypto/policy.c | 2 +- fs/efivarfs/file.c | 2 +- fs/ext2/ialloc.c | 2 +- fs/ext2/ioctl.c | 6 +++--- fs/ext4/ialloc.c | 2 +- fs/ext4/ioctl.c | 14 +++++++------- fs/f2fs/file.c | 14 +++++++------- fs/f2fs/namei.c | 2 +- fs/f2fs/xattr.c | 2 +- fs/fcntl.c | 2 +- fs/gfs2/file.c | 2 +- fs/hfsplus/inode.c | 2 +- fs/hfsplus/ioctl.c | 2 +- fs/hugetlbfs/inode.c | 2 +- fs/inode.c | 35 ++++++++++++++++++++++++++--------- fs/jfs/ioctl.c | 2 +- fs/jfs/jfs_inode.c | 2 +- fs/minix/bitmap.c | 2 +- fs/namei.c | 4 ++-- fs/nilfs2/inode.c | 2 +- fs/nilfs2/ioctl.c | 2 +- fs/ocfs2/dlmfs/dlmfs.c | 4 ++-- fs/ocfs2/ioctl.c | 2 +- fs/ocfs2/namei.c | 2 +- fs/omfs/inode.c | 2 +- fs/overlayfs/dir.c | 2 +- fs/overlayfs/file.c | 4 ++-- fs/overlayfs/super.c | 2 +- fs/overlayfs/util.c | 2 +- fs/posix_acl.c | 2 +- fs/ramfs/inode.c | 2 +- fs/reiserfs/ioctl.c | 4 ++-- fs/reiserfs/namei.c | 2 +- fs/sysv/ialloc.c | 2 +- fs/ubifs/dir.c | 2 +- fs/ubifs/ioctl.c | 2 +- fs/udf/ialloc.c | 2 +- fs/ufs/ialloc.c | 2 +- fs/xattr.c | 2 +- fs/xfs/xfs_ioctl.c | 2 +- fs/zonefs/super.c | 2 +- include/linux/fs.h | 9 +++++---- kernel/bpf/inode.c | 2 +- mm/madvise.c | 2 +- mm/mincore.c | 2 +- mm/shmem.c | 2 +- security/selinux/hooks.c | 4 ++-- 54 files changed, 108 insertions(+), 90 deletions(-) diff --git a/fs/9p/acl.c b/fs/9p/acl.c index 6261719f6f2a..d77b28e8d57a 100644 --- a/fs/9p/acl.c +++ b/fs/9p/acl.c @@ -258,7 +258,7 @@ static int v9fs_xattr_set_acl(const struct xattr_handler *handler, if (S_ISLNK(inode->i_mode)) return -EOPNOTSUPP; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; if (value) { /* update the cached acl value */ diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index ae0c38ad1fcb..a3eb4f345f2c 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -251,7 +251,7 @@ int v9fs_init_inode(struct v9fs_session_info *v9ses, { int err = 0; - inode_init_owner(inode, NULL, mode); + inode_init_owner(&init_user_ns,inode, NULL, mode); inode->i_blocks = 0; inode->i_rdev = rdev; inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); diff --git a/fs/attr.c b/fs/attr.c index c9e29e589cec..00ae0b000146 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -87,7 +87,7 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr) /* Make sure a caller can chmod. */ if (ia_valid & ATTR_MODE) { - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; /* Also check the setgid bit! */ if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid : @@ -98,7 +98,7 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr) /* Check for setting the inode time. */ if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) { - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; } @@ -243,7 +243,7 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de if (IS_IMMUTABLE(inode)) return -EPERM; - if (!inode_owner_or_capable(inode)) { + if (!inode_owner_or_capable(&init_user_ns, inode)) { error = inode_permission(&init_user_ns, inode, MAY_WRITE); if (error) diff --git a/fs/bfs/dir.c b/fs/bfs/dir.c index d8dfe3a0cb39..be1335a8d25b 100644 --- a/fs/bfs/dir.c +++ b/fs/bfs/dir.c @@ -96,7 +96,7 @@ static int bfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, } set_bit(ino, info->si_imap); info->si_freei--; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); inode->i_blocks = 0; inode->i_op = &bfs_file_inops; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index ea4a70850f5e..4a67533c72fb 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -6073,7 +6073,7 @@ static struct inode *btrfs_new_inode(struct btrfs_trans_handle *trans, if (ret != 0) goto fail_unlock; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode_set_bytes(inode, 0); inode->i_mtime = current_time(inode); diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index 74500c15bdc1..f5d3ebca4c5f 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -205,7 +205,7 @@ static int btrfs_ioctl_setflags(struct file *file, void __user *arg) const char *comp = NULL; u32 binode_flags; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; if (btrfs_root_readonly(root)) @@ -417,7 +417,7 @@ static int btrfs_ioctl_fssetxattr(struct file *file, void __user *arg) unsigned old_i_flags; int ret = 0; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; if (btrfs_root_readonly(root)) @@ -1811,7 +1811,7 @@ static noinline int __btrfs_ioctl_snap_create(struct file *file, btrfs_info(BTRFS_I(file_inode(file))->root->fs_info, "Snapshot src from another FS"); ret = -EXDEV; - } else if (!inode_owner_or_capable(src_inode)) { + } else if (!inode_owner_or_capable(&init_user_ns, src_inode)) { /* * Subvolume creation is not restricted, but snapshots * are limited to own subvolumes only @@ -1931,7 +1931,7 @@ static noinline int btrfs_ioctl_subvol_setflags(struct file *file, u64 flags; int ret = 0; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; ret = mnt_want_write_file(file); @@ -4401,7 +4401,7 @@ static long _btrfs_ioctl_set_received_subvol(struct file *file, int ret = 0; int received_uuid_changed; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; ret = mnt_want_write_file(file); diff --git a/fs/btrfs/tests/btrfs-tests.c b/fs/btrfs/tests/btrfs-tests.c index 999c14e5d0bd..a4ac32f15c33 100644 --- a/fs/btrfs/tests/btrfs-tests.c +++ b/fs/btrfs/tests/btrfs-tests.c @@ -56,7 +56,7 @@ struct inode *btrfs_new_test_inode(void) inode = new_inode(test_mnt->mnt_sb); if (inode) - inode_init_owner(inode, NULL, S_IFREG); + inode_init_owner(&init_user_ns, inode, NULL, S_IFREG); return inode; } diff --git a/fs/crypto/policy.c b/fs/crypto/policy.c index 4441d9944b9e..6ddd9f0d8b36 100644 --- a/fs/crypto/policy.c +++ b/fs/crypto/policy.c @@ -462,7 +462,7 @@ int fscrypt_ioctl_set_policy(struct file *filp, const void __user *arg) return -EFAULT; policy.version = version; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; ret = mnt_want_write_file(filp); diff --git a/fs/efivarfs/file.c b/fs/efivarfs/file.c index feaa5e182b7b..e6bc0302643b 100644 --- a/fs/efivarfs/file.c +++ b/fs/efivarfs/file.c @@ -137,7 +137,7 @@ efivarfs_ioc_setxflags(struct file *file, void __user *arg) unsigned int oldflags = efivarfs_getflags(inode); int error; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (copy_from_user(&flags, arg, sizeof(flags))) diff --git a/fs/ext2/ialloc.c b/fs/ext2/ialloc.c index 432c3febea6d..df14e750e9fe 100644 --- a/fs/ext2/ialloc.c +++ b/fs/ext2/ialloc.c @@ -551,7 +551,7 @@ struct inode *ext2_new_inode(struct inode *dir, umode_t mode, inode->i_uid = current_fsuid(); inode->i_gid = dir->i_gid; } else - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_ino = ino; inode->i_blocks = 0; diff --git a/fs/ext2/ioctl.c b/fs/ext2/ioctl.c index 32a8d10b579d..b399cbb7022d 100644 --- a/fs/ext2/ioctl.c +++ b/fs/ext2/ioctl.c @@ -39,7 +39,7 @@ long ext2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (ret) return ret; - if (!inode_owner_or_capable(inode)) { + if (!inode_owner_or_capable(&init_user_ns, inode)) { ret = -EACCES; goto setflags_out; } @@ -84,7 +84,7 @@ long ext2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case EXT2_IOC_SETVERSION: { __u32 generation; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; ret = mnt_want_write_file(filp); if (ret) @@ -117,7 +117,7 @@ long ext2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (!test_opt(inode->i_sb, RESERVATION) ||!S_ISREG(inode->i_mode)) return -ENOTTY; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (get_user(rsv_window_size, (int __user *)arg)) diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c index b215c564bc31..00c1ec6eee16 100644 --- a/fs/ext4/ialloc.c +++ b/fs/ext4/ialloc.c @@ -972,7 +972,7 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir, inode->i_uid = current_fsuid(); inode->i_gid = dir->i_gid; } else - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); if (ext4_has_feature_project(sb) && ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT)) diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index f0381876a7e5..e35aba820254 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -139,7 +139,7 @@ static long swap_inode_boot_loader(struct super_block *sb, } if (IS_RDONLY(inode) || IS_APPEND(inode) || IS_IMMUTABLE(inode) || - !inode_owner_or_capable(inode) || !capable(CAP_SYS_ADMIN)) { + !inode_owner_or_capable(&init_user_ns, inode) || !capable(CAP_SYS_ADMIN)) { err = -EPERM; goto journal_err_out; } @@ -829,7 +829,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case FS_IOC_SETFLAGS: { int err; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (get_user(flags, (int __user *) arg)) @@ -871,7 +871,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) __u32 generation; int err; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; if (ext4_has_metadata_csum(inode->i_sb)) { @@ -1010,7 +1010,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case EXT4_IOC_MIGRATE: { int err; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; err = mnt_want_write_file(filp); @@ -1032,7 +1032,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case EXT4_IOC_ALLOC_DA_BLKS: { int err; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; err = mnt_want_write_file(filp); @@ -1214,7 +1214,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case EXT4_IOC_CLEAR_ES_CACHE: { - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; ext4_clear_inode_es(inode); return 0; @@ -1260,7 +1260,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return -EFAULT; /* Make sure caller has proper permission */ - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (fa.fsx_xflags & ~EXT4_SUPPORTED_FS_XFLAGS) diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index ee861c6d9ff0..333442e96cc4 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -1955,7 +1955,7 @@ static int f2fs_ioc_setflags(struct file *filp, unsigned long arg) u32 iflags; int ret; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (get_user(fsflags, (int __user *)arg)) @@ -2002,7 +2002,7 @@ static int f2fs_ioc_start_atomic_write(struct file *filp) struct f2fs_sb_info *sbi = F2FS_I_SB(inode); int ret; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (!S_ISREG(inode->i_mode)) @@ -2069,7 +2069,7 @@ static int f2fs_ioc_commit_atomic_write(struct file *filp) struct inode *inode = file_inode(filp); int ret; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; ret = mnt_want_write_file(filp); @@ -2111,7 +2111,7 @@ static int f2fs_ioc_start_volatile_write(struct file *filp) struct inode *inode = file_inode(filp); int ret; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (!S_ISREG(inode->i_mode)) @@ -2146,7 +2146,7 @@ static int f2fs_ioc_release_volatile_write(struct file *filp) struct inode *inode = file_inode(filp); int ret; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; ret = mnt_want_write_file(filp); @@ -2175,7 +2175,7 @@ static int f2fs_ioc_abort_volatile_write(struct file *filp) struct inode *inode = file_inode(filp); int ret; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; ret = mnt_want_write_file(filp); @@ -3153,7 +3153,7 @@ static int f2fs_ioc_fssetxattr(struct file *filp, unsigned long arg) return -EFAULT; /* Make sure caller has proper permission */ - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (fa.fsx_xflags & ~F2FS_SUPPORTED_XFLAGS) diff --git a/fs/f2fs/namei.c b/fs/f2fs/namei.c index 8fa37d1434de..8a69cf3e3b15 100644 --- a/fs/f2fs/namei.c +++ b/fs/f2fs/namei.c @@ -46,7 +46,7 @@ static struct inode *f2fs_new_inode(struct inode *dir, umode_t mode) nid_free = true; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_ino = ino; inode->i_blocks = 0; diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c index 65afcc3cc68a..d772bf13a814 100644 --- a/fs/f2fs/xattr.c +++ b/fs/f2fs/xattr.c @@ -114,7 +114,7 @@ static int f2fs_xattr_advise_set(const struct xattr_handler *handler, unsigned char old_advise = F2FS_I(inode)->i_advise; unsigned char new_advise; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; if (value == NULL) return -EINVAL; diff --git a/fs/fcntl.c b/fs/fcntl.c index 19ac5baad50f..df091d435603 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -46,7 +46,7 @@ static int setfl(int fd, struct file * filp, unsigned long arg) /* O_NOATIME can only be set by the owner or superuser */ if ((arg & O_NOATIME) && !(filp->f_flags & O_NOATIME)) - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; /* required for strict SunOS emulation */ diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c index b39b339feddc..1d994bdfffaa 100644 --- a/fs/gfs2/file.c +++ b/fs/gfs2/file.c @@ -238,7 +238,7 @@ static int do_gfs2_set_flags(struct file *filp, u32 reqflags, u32 mask, goto out; error = -EACCES; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) goto out; error = 0; diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index e3da9e96b835..21357046b039 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -376,7 +376,7 @@ struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir, return NULL; inode->i_ino = sbi->next_cnid++; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); set_nlink(inode, 1); inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); diff --git a/fs/hfsplus/ioctl.c b/fs/hfsplus/ioctl.c index ce15b9496b77..3edb1926d127 100644 --- a/fs/hfsplus/ioctl.c +++ b/fs/hfsplus/ioctl.c @@ -91,7 +91,7 @@ static int hfsplus_ioctl_setflags(struct file *file, int __user *user_flags) if (err) goto out; - if (!inode_owner_or_capable(inode)) { + if (!inode_owner_or_capable(&init_user_ns, inode)) { err = -EACCES; goto out_drop_write; } diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index b5c109703daa..6737929e443c 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -836,7 +836,7 @@ static struct inode *hugetlbfs_get_inode(struct super_block *sb, struct hugetlbfs_inode_info *info = HUGETLBFS_I(inode); inode->i_ino = get_next_ino(); - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); lockdep_set_class(&inode->i_mapping->i_mmap_rwsem, &hugetlbfs_i_mmap_rwsem_key); inode->i_mapping->a_ops = &hugetlbfs_aops; diff --git a/fs/inode.c b/fs/inode.c index 7a15372d9c2d..b402e45b3ada 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -2131,14 +2131,21 @@ EXPORT_SYMBOL(init_special_inode); /** * inode_init_owner - Init uid,gid,mode for new inode according to posix standards + * @mnt_userns: User namespace of the mount the inode was created from * @inode: New inode * @dir: Directory inode * @mode: mode of the new inode + * + * If the inode has been created through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions + * and initializing i_uid and i_gid. On non-idmapped mounts or if permission + * checking is to be performed on the raw inode simply passs init_user_ns. */ -void inode_init_owner(struct inode *inode, const struct inode *dir, - umode_t mode) +void inode_init_owner(struct user_namespace *mnt_userns, struct inode *inode, + const struct inode *dir, umode_t mode) { - inode->i_uid = current_fsuid(); + inode->i_uid = fsuid_into_mnt(mnt_userns); if (dir && dir->i_mode & S_ISGID) { inode->i_gid = dir->i_gid; @@ -2146,31 +2153,41 @@ void inode_init_owner(struct inode *inode, const struct inode *dir, if (S_ISDIR(mode)) mode |= S_ISGID; else if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP) && - !in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(&init_user_ns, dir, CAP_FSETID)) + !in_group_p(i_gid_into_mnt(mnt_userns, dir)) && + !capable_wrt_inode_uidgid(mnt_userns, dir, CAP_FSETID)) mode &= ~S_ISGID; } else - inode->i_gid = current_fsgid(); + inode->i_gid = fsgid_into_mnt(mnt_userns); inode->i_mode = mode; } EXPORT_SYMBOL(inode_init_owner); /** * inode_owner_or_capable - check current task permissions to inode + * @mnt_userns: user namespace of the mount the inode was found from * @inode: inode being checked * * Return true if current either has CAP_FOWNER in a namespace with the * inode owner uid mapped, or owns the file. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. + * On non-idmapped mounts or if permission checking is to be performed on the + * raw inode simply passs init_user_ns. */ -bool inode_owner_or_capable(const struct inode *inode) +bool inode_owner_or_capable(struct user_namespace *mnt_userns, + const struct inode *inode) { + kuid_t i_uid; struct user_namespace *ns; - if (uid_eq(current_fsuid(), inode->i_uid)) + i_uid = i_uid_into_mnt(mnt_userns, inode); + if (uid_eq(current_fsuid(), i_uid)) return true; ns = current_user_ns(); - if (kuid_has_mapping(ns, inode->i_uid) && ns_capable(ns, CAP_FOWNER)) + if (kuid_has_mapping(ns, i_uid) && ns_capable(ns, CAP_FOWNER)) return true; return false; } diff --git a/fs/jfs/ioctl.c b/fs/jfs/ioctl.c index 10ee0ecca1a8..2581d4db58ff 100644 --- a/fs/jfs/ioctl.c +++ b/fs/jfs/ioctl.c @@ -76,7 +76,7 @@ long jfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (err) return err; - if (!inode_owner_or_capable(inode)) { + if (!inode_owner_or_capable(&init_user_ns, inode)) { err = -EACCES; goto setflags_out; } diff --git a/fs/jfs/jfs_inode.c b/fs/jfs/jfs_inode.c index 4cef170630db..59379089e939 100644 --- a/fs/jfs/jfs_inode.c +++ b/fs/jfs/jfs_inode.c @@ -64,7 +64,7 @@ struct inode *ialloc(struct inode *parent, umode_t mode) goto fail_put; } - inode_init_owner(inode, parent, mode); + inode_init_owner(&init_user_ns, inode, parent, mode); /* * New inodes need to save sane values on disk when * uid & gid mount options are used diff --git a/fs/minix/bitmap.c b/fs/minix/bitmap.c index f4e5e5181a14..9115948c624e 100644 --- a/fs/minix/bitmap.c +++ b/fs/minix/bitmap.c @@ -252,7 +252,7 @@ struct inode *minix_new_inode(const struct inode *dir, umode_t mode, int *error) iput(inode); return NULL; } - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_ino = j; inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); inode->i_blocks = 0; diff --git a/fs/namei.c b/fs/namei.c index 79814dbbfc82..aed328571a6b 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -1086,7 +1086,7 @@ int may_linkat(struct path *link) /* Source inode owner (or CAP_FOWNER) can hardlink all they like, * otherwise, it must be a safe source. */ - if (safe_hardlink_source(inode) || inode_owner_or_capable(inode)) + if (safe_hardlink_source(inode) || inode_owner_or_capable(&init_user_ns, inode)) return 0; audit_log_path_denied(AUDIT_ANOM_LINK, "linkat"); @@ -2936,7 +2936,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) } /* O_NOATIME can only be set by the owner or superuser */ - if (flag & O_NOATIME && !inode_owner_or_capable(inode)) + if (flag & O_NOATIME && !inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; return 0; diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index b6517220cad5..11225a659736 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -348,7 +348,7 @@ struct inode *nilfs_new_inode(struct inode *dir, umode_t mode) /* reference count of i_bh inherits from nilfs_mdt_read_block() */ atomic64_inc(&root->inodes_count); - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_ino = ino; inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); diff --git a/fs/nilfs2/ioctl.c b/fs/nilfs2/ioctl.c index 07d26f61f22a..b053b40315bf 100644 --- a/fs/nilfs2/ioctl.c +++ b/fs/nilfs2/ioctl.c @@ -132,7 +132,7 @@ static int nilfs_ioctl_setflags(struct inode *inode, struct file *filp, unsigned int flags, oldflags; int ret; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (get_user(flags, (int __user *)argp)) diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index 583820ec63e2..37c7d03a6284 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -329,7 +329,7 @@ static struct inode *dlmfs_get_root_inode(struct super_block *sb) if (inode) { inode->i_ino = get_next_ino(); - inode_init_owner(inode, NULL, mode); + inode_init_owner(&init_user_ns, inode, NULL, mode); inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); inc_nlink(inode); @@ -352,7 +352,7 @@ static struct inode *dlmfs_get_inode(struct inode *parent, return NULL; inode->i_ino = get_next_ino(); - inode_init_owner(inode, parent, mode); + inode_init_owner(&init_user_ns, inode, parent, mode); inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); ip = DLMFS_I(inode); diff --git a/fs/ocfs2/ioctl.c b/fs/ocfs2/ioctl.c index 89984172fc4a..50c9b30ee9f6 100644 --- a/fs/ocfs2/ioctl.c +++ b/fs/ocfs2/ioctl.c @@ -96,7 +96,7 @@ static int ocfs2_set_inode_attr(struct inode *inode, unsigned flags, } status = -EACCES; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) goto bail_unlock; if (!S_ISDIR(inode->i_mode)) diff --git a/fs/ocfs2/namei.c b/fs/ocfs2/namei.c index c46bf7f581a1..a4965133bd5e 100644 --- a/fs/ocfs2/namei.c +++ b/fs/ocfs2/namei.c @@ -198,7 +198,7 @@ static struct inode *ocfs2_get_init_inode(struct inode *dir, umode_t mode) * callers. */ if (S_ISDIR(mode)) set_nlink(inode, 2); - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); status = dquot_initialize(inode); if (status) return ERR_PTR(status); diff --git a/fs/omfs/inode.c b/fs/omfs/inode.c index ce93ccca8639..2a0e83236c01 100644 --- a/fs/omfs/inode.c +++ b/fs/omfs/inode.c @@ -48,7 +48,7 @@ struct inode *omfs_new_inode(struct inode *dir, umode_t mode) goto fail; inode->i_ino = new_block; - inode_init_owner(inode, NULL, mode); + inode_init_owner(&init_user_ns, inode, NULL, mode); inode->i_mapping->a_ops = &omfs_aops; inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 28a075b5f5b2..98a23353b19a 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -636,7 +636,7 @@ static int ovl_create_object(struct dentry *dentry, int mode, dev_t rdev, inode->i_state |= I_CREATING; spin_unlock(&inode->i_lock); - inode_init_owner(inode, dentry->d_parent->d_inode, mode); + inode_init_owner(&init_user_ns, inode, dentry->d_parent->d_inode, mode); attr.mode = inode->i_mode; err = ovl_create_or_link(dentry, inode, &attr, false); diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c index f966b5108358..d58b49a1ea3b 100644 --- a/fs/overlayfs/file.c +++ b/fs/overlayfs/file.c @@ -53,7 +53,7 @@ static struct file *ovl_open_realfile(const struct file *file, err = inode_permission(&init_user_ns, realinode, MAY_OPEN | acc_mode); if (err) { realfile = ERR_PTR(err); - } else if (!inode_owner_or_capable(realinode)) { + } else if (!inode_owner_or_capable(&init_user_ns, realinode)) { realfile = ERR_PTR(-EPERM); } else { realfile = open_with_fake_path(&file->f_path, flags, realinode, @@ -582,7 +582,7 @@ static long ovl_ioctl_set_flags(struct file *file, unsigned int cmd, struct inode *inode = file_inode(file); unsigned int oldflags; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; ret = mnt_want_write_file(file); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 196fe3e3f02b..82f2c35894e4 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -960,7 +960,7 @@ ovl_posix_acl_xattr_set(const struct xattr_handler *handler, goto out_acl_release; } err = -EPERM; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) goto out_acl_release; posix_acl_release(acl); diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index ff67da201303..0a1f4bccb5da 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -481,7 +481,7 @@ struct file *ovl_path_open(struct path *path, int flags) return ERR_PTR(err); /* O_NOATIME is an optimization, don't fail if not permitted */ - if (inode_owner_or_capable(inode)) + if (inode_owner_or_capable(&init_user_ns, inode)) flags |= O_NOATIME; return dentry_open(path, flags, current_cred()); diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 5d9fe2fb2953..9ce8214bfdac 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -874,7 +874,7 @@ set_posix_acl(struct inode *inode, int type, struct posix_acl *acl) if (type == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode)) return acl ? -EACCES : 0; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; if (acl) { diff --git a/fs/ramfs/inode.c b/fs/ramfs/inode.c index ee179a81b3da..3fd4326f36b5 100644 --- a/fs/ramfs/inode.c +++ b/fs/ramfs/inode.c @@ -67,7 +67,7 @@ struct inode *ramfs_get_inode(struct super_block *sb, if (inode) { inode->i_ino = get_next_ino(); - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_mapping->a_ops = &ramfs_aops; mapping_set_gfp_mask(inode->i_mapping, GFP_HIGHUSER); mapping_set_unevictable(inode->i_mapping); diff --git a/fs/reiserfs/ioctl.c b/fs/reiserfs/ioctl.c index adb21bea3d60..4f1cbd930179 100644 --- a/fs/reiserfs/ioctl.c +++ b/fs/reiserfs/ioctl.c @@ -59,7 +59,7 @@ long reiserfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) if (err) break; - if (!inode_owner_or_capable(inode)) { + if (!inode_owner_or_capable(&init_user_ns, inode)) { err = -EPERM; goto setflags_out; } @@ -101,7 +101,7 @@ long reiserfs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) err = put_user(inode->i_generation, (int __user *)arg); break; case REISERFS_IOC_SETVERSION: - if (!inode_owner_or_capable(inode)) { + if (!inode_owner_or_capable(&init_user_ns, inode)) { err = -EPERM; break; } diff --git a/fs/reiserfs/namei.c b/fs/reiserfs/namei.c index 1594687582f0..a67a7d371725 100644 --- a/fs/reiserfs/namei.c +++ b/fs/reiserfs/namei.c @@ -615,7 +615,7 @@ static int new_inode_init(struct inode *inode, struct inode *dir, umode_t mode) * the quota init calls have to know who to charge the quota to, so * we have to set uid and gid here */ - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); return dquot_initialize(inode); } diff --git a/fs/sysv/ialloc.c b/fs/sysv/ialloc.c index 6c9801986af6..50df794a3c1f 100644 --- a/fs/sysv/ialloc.c +++ b/fs/sysv/ialloc.c @@ -163,7 +163,7 @@ struct inode * sysv_new_inode(const struct inode * dir, umode_t mode) *sbi->s_sb_fic_count = cpu_to_fs16(sbi, count); fs16_add(sbi, sbi->s_sb_total_free_inodes, -1); dirty_sb(sb); - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_ino = fs16_to_cpu(sbi, ino); inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); inode->i_blocks = 0; diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index 155521e51ac5..a2bbd7ed39e9 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -94,7 +94,7 @@ struct inode *ubifs_new_inode(struct ubifs_info *c, struct inode *dir, */ inode->i_flags |= S_NOCMTIME; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); inode->i_mapping->nrpages = 0; diff --git a/fs/ubifs/ioctl.c b/fs/ubifs/ioctl.c index 4363d85a3fd4..2326d5122beb 100644 --- a/fs/ubifs/ioctl.c +++ b/fs/ubifs/ioctl.c @@ -155,7 +155,7 @@ long ubifs_ioctl(struct file *file, unsigned int cmd, unsigned long arg) if (IS_RDONLY(inode)) return -EROFS; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EACCES; if (get_user(flags, (int __user *) arg)) diff --git a/fs/udf/ialloc.c b/fs/udf/ialloc.c index 84ed23edebfd..2ecf0e87660e 100644 --- a/fs/udf/ialloc.c +++ b/fs/udf/ialloc.c @@ -103,7 +103,7 @@ struct inode *udf_new_inode(struct inode *dir, umode_t mode) mutex_unlock(&sbi->s_alloc_mutex); } - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); if (UDF_QUERY_FLAG(sb, UDF_FLAG_UID_SET)) inode->i_uid = sbi->s_uid; if (UDF_QUERY_FLAG(sb, UDF_FLAG_GID_SET)) diff --git a/fs/ufs/ialloc.c b/fs/ufs/ialloc.c index 969fd60436d3..7e3e08c0166f 100644 --- a/fs/ufs/ialloc.c +++ b/fs/ufs/ialloc.c @@ -289,7 +289,7 @@ struct inode *ufs_new_inode(struct inode *dir, umode_t mode) ufs_mark_sb_dirty(sb); inode->i_ino = cg * uspi->s_ipg + bit; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_blocks = 0; inode->i_generation = 0; inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode); diff --git a/fs/xattr.c b/fs/xattr.c index 61a9947f62f4..fcc79c2a1ea1 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -127,7 +127,7 @@ xattr_permission(struct inode *inode, const char *name, int mask) if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) return (mask & MAY_WRITE) ? -EPERM : -ENODATA; if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) && - (mask & MAY_WRITE) && !inode_owner_or_capable(inode)) + (mask & MAY_WRITE) && !inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; } diff --git a/fs/xfs/xfs_ioctl.c b/fs/xfs/xfs_ioctl.c index 97bd29fc8c43..218e80afc859 100644 --- a/fs/xfs/xfs_ioctl.c +++ b/fs/xfs/xfs_ioctl.c @@ -1300,7 +1300,7 @@ xfs_ioctl_setattr_get_trans( * The user ID of the calling process must be equal to the file owner * ID, except in cases where the CAP_FSETID capability is applicable. */ - if (!inode_owner_or_capable(VFS_I(ip))) { + if (!inode_owner_or_capable(&init_user_ns, VFS_I(ip))) { error = -EPERM; goto out_cancel; } diff --git a/fs/zonefs/super.c b/fs/zonefs/super.c index ff5930be096c..e301a131bf1e 100644 --- a/fs/zonefs/super.c +++ b/fs/zonefs/super.c @@ -1221,7 +1221,7 @@ static void zonefs_init_dir_inode(struct inode *parent, struct inode *inode, struct super_block *sb = parent->i_sb; inode->i_ino = blkdev_nr_zones(sb->s_bdev->bd_disk) + type + 1; - inode_init_owner(inode, parent, S_IFDIR | 0555); + inode_init_owner(&init_user_ns, inode, parent, S_IFDIR | 0555); inode->i_op = &zonefs_dir_inode_operations; inode->i_fop = &simple_dir_operations; set_nlink(inode, 2); diff --git a/include/linux/fs.h b/include/linux/fs.h index 8fb288dd9185..e77a270bd195 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1763,8 +1763,8 @@ static inline bool sb_start_intwrite_trylock(struct super_block *sb) return __sb_start_write_trylock(sb, SB_FREEZE_FS); } - -extern bool inode_owner_or_capable(const struct inode *inode); +extern bool inode_owner_or_capable(struct user_namespace *mnt_userns, + const struct inode *inode); /* * VFS helper functions.. @@ -1806,8 +1806,9 @@ extern long compat_ptr_ioctl(struct file *file, unsigned int cmd, /* * VFS file helper functions. */ -extern void inode_init_owner(struct inode *inode, const struct inode *dir, - umode_t mode); +extern void inode_init_owner(struct user_namespace *mnt_userns, + struct inode *inode, const struct inode *dir, + umode_t mode); extern bool may_open_dev(const struct path *path); /* diff --git a/kernel/bpf/inode.c b/kernel/bpf/inode.c index f1c393e5d47d..dd6f124c12c7 100644 --- a/kernel/bpf/inode.c +++ b/kernel/bpf/inode.c @@ -122,7 +122,7 @@ static struct inode *bpf_get_inode(struct super_block *sb, inode->i_mtime = inode->i_atime; inode->i_ctime = inode->i_atime; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); return inode; } diff --git a/mm/madvise.c b/mm/madvise.c index bd87d0019266..5b2ec08e1484 100644 --- a/mm/madvise.c +++ b/mm/madvise.c @@ -539,7 +539,7 @@ static inline bool can_do_pageout(struct vm_area_struct *vma) * otherwise we'd be including shared non-exclusive mappings, which * opens a side channel. */ - return inode_owner_or_capable(file_inode(vma->vm_file)) || + return inode_owner_or_capable(&init_user_ns, file_inode(vma->vm_file)) || inode_permission(&init_user_ns, file_inode(vma->vm_file), MAY_WRITE) == 0; } diff --git a/mm/mincore.c b/mm/mincore.c index d5a58e61eac6..ad2dfb7a4500 100644 --- a/mm/mincore.c +++ b/mm/mincore.c @@ -166,7 +166,7 @@ static inline bool can_do_mincore(struct vm_area_struct *vma) * for writing; otherwise we'd be including shared non-exclusive * mappings, which opens a side channel. */ - return inode_owner_or_capable(file_inode(vma->vm_file)) || + return inode_owner_or_capable(&init_user_ns, file_inode(vma->vm_file)) || inode_permission(&init_user_ns, file_inode(vma->vm_file), MAY_WRITE) == 0; } diff --git a/mm/shmem.c b/mm/shmem.c index 537c137698f8..90dfdc8b7423 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -2303,7 +2303,7 @@ static struct inode *shmem_get_inode(struct super_block *sb, const struct inode inode = new_inode(sb); if (inode) { inode->i_ino = ino; - inode_init_owner(inode, dir, mode); + inode_init_owner(&init_user_ns, inode, dir, mode); inode->i_blocks = 0; inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode); inode->i_generation = prandom_u32(); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 6b1826fc3658..14a195fa55eb 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3133,13 +3133,13 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, } if (!selinux_initialized(&selinux_state)) - return (inode_owner_or_capable(inode) ? 0 : -EPERM); + return (inode_owner_or_capable(&init_user_ns, inode) ? 0 : -EPERM); sbsec = inode->i_sb->s_security; if (!(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; - if (!inode_owner_or_capable(inode)) + if (!inode_owner_or_capable(&init_user_ns, inode)) return -EPERM; ad.type = LSM_AUDIT_DATA_DENTRY; From patchwork Thu Dec 3 23:57:09 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950109 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id A04AEC352A6 for ; Fri, 4 Dec 2020 00:03:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 740DC22224 for ; Fri, 4 Dec 2020 00:03:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2501866AbgLDACm (ORCPT ); Thu, 3 Dec 2020 19:02:42 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41755 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388423AbgLDACg (ORCPT ); Thu, 3 Dec 2020 19:02:36 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXa-0007ka-4R; Fri, 04 Dec 2020 00:01:38 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 13/40] attr: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:09 +0100 Message-Id: <20201203235736.3528991-14-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org When file attributes are changed filesystems almost exclusively rely on the setattr_prepare(), setattr_copy(), and notify_change() helpers for initialization and permission checking. Let them handle idmapped mounts. If the inode is accessed through an idmapped mount it is mapped according to the mount's user namespace. Afterwards the checks are identical to non-idmapped mounts. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Helpers that perform checks on the ia_uid and ia_gid fields in struct iattr assume that ia_uid and ia_gid are intended values and have already been mapped correctly at the userspace-kernelspace boundary as we already do today. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ - kernel test robot : - Adapt __setattr_copy() in fs/f2fs when CONFIG_F2FS_FS_POSIX_ACL is selected. /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- arch/powerpc/platforms/cell/spufs/inode.c | 2 +- drivers/base/devtmpfs.c | 4 +- fs/9p/vfs_inode.c | 4 +- fs/9p/vfs_inode_dotl.c | 4 +- fs/adfs/inode.c | 2 +- fs/affs/inode.c | 4 +- fs/attr.c | 117 ++++++++++++++++------ fs/btrfs/inode.c | 4 +- fs/cachefiles/interface.c | 4 +- fs/ceph/inode.c | 2 +- fs/cifs/inode.c | 8 +- fs/ecryptfs/inode.c | 6 +- fs/exfat/file.c | 4 +- fs/ext2/inode.c | 4 +- fs/ext4/inode.c | 4 +- fs/f2fs/file.c | 10 +- fs/fat/file.c | 4 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 4 +- fs/hfs/inode.c | 4 +- fs/hfsplus/inode.c | 4 +- fs/hostfs/hostfs_kern.c | 4 +- fs/hpfs/inode.c | 4 +- fs/hugetlbfs/inode.c | 4 +- fs/inode.c | 2 +- fs/jffs2/fs.c | 2 +- fs/jfs/file.c | 4 +- fs/kernfs/inode.c | 4 +- fs/libfs.c | 4 +- fs/minix/file.c | 4 +- fs/nfsd/nfsproc.c | 2 +- fs/nfsd/vfs.c | 4 +- fs/nilfs2/inode.c | 4 +- fs/ntfs/inode.c | 2 +- fs/ocfs2/dlmfs/dlmfs.c | 4 +- fs/ocfs2/file.c | 4 +- fs/omfs/file.c | 4 +- fs/open.c | 6 +- fs/orangefs/inode.c | 4 +- fs/overlayfs/copy_up.c | 8 +- fs/overlayfs/dir.c | 2 +- fs/overlayfs/inode.c | 4 +- fs/overlayfs/super.c | 2 +- fs/proc/base.c | 4 +- fs/proc/generic.c | 4 +- fs/proc/proc_sysctl.c | 4 +- fs/ramfs/file-nommu.c | 4 +- fs/reiserfs/inode.c | 4 +- fs/sysv/file.c | 4 +- fs/ubifs/file.c | 2 +- fs/udf/file.c | 4 +- fs/ufs/inode.c | 4 +- fs/utimes.c | 2 +- fs/xfs/xfs_iops.c | 2 +- fs/zonefs/super.c | 4 +- include/linux/fs.h | 8 +- mm/shmem.c | 4 +- 57 files changed, 200 insertions(+), 137 deletions(-) diff --git a/arch/powerpc/platforms/cell/spufs/inode.c b/arch/powerpc/platforms/cell/spufs/inode.c index 25390569e24c..3de526eb2275 100644 --- a/arch/powerpc/platforms/cell/spufs/inode.c +++ b/arch/powerpc/platforms/cell/spufs/inode.c @@ -98,7 +98,7 @@ spufs_setattr(struct dentry *dentry, struct iattr *attr) if ((attr->ia_valid & ATTR_SIZE) && (attr->ia_size != inode->i_size)) return -EINVAL; - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index eac184e6d657..2e0c3cdb4184 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -221,7 +221,7 @@ static int handle_create(const char *nodename, umode_t mode, kuid_t uid, newattrs.ia_gid = gid; newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID; inode_lock(d_inode(dentry)); - notify_change(dentry, &newattrs, NULL); + notify_change(&init_user_ns, dentry, &newattrs, NULL); inode_unlock(d_inode(dentry)); /* mark as kernel-created inode */ @@ -328,7 +328,7 @@ static int handle_remove(const char *nodename, struct device *dev) newattrs.ia_valid = ATTR_UID|ATTR_GID|ATTR_MODE; inode_lock(d_inode(dentry)); - notify_change(dentry, &newattrs, NULL); + notify_change(&init_user_ns, dentry, &newattrs, NULL); inode_unlock(d_inode(dentry)); err = vfs_unlink(d_inode(parent.dentry), dentry, NULL); if (!err || err == -ENOENT) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index a3eb4f345f2c..1b2028be2e08 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1040,7 +1040,7 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr) struct p9_wstat wstat; p9_debug(P9_DEBUG_VFS, "\n"); - retval = setattr_prepare(dentry, iattr); + retval = setattr_prepare(&init_user_ns, dentry, iattr); if (retval) return retval; @@ -1090,7 +1090,7 @@ static int v9fs_vfs_setattr(struct dentry *dentry, struct iattr *iattr) v9fs_invalidate_inode_attr(d_inode(dentry)); - setattr_copy(d_inode(dentry), iattr); + setattr_copy(&init_user_ns, d_inode(dentry), iattr); mark_inode_dirty(d_inode(dentry)); return 0; } diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 0028eccb665a..282ec5cb45dc 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -546,7 +546,7 @@ int v9fs_vfs_setattr_dotl(struct dentry *dentry, struct iattr *iattr) p9_debug(P9_DEBUG_VFS, "\n"); - retval = setattr_prepare(dentry, iattr); + retval = setattr_prepare(&init_user_ns, dentry, iattr); if (retval) return retval; @@ -582,7 +582,7 @@ int v9fs_vfs_setattr_dotl(struct dentry *dentry, struct iattr *iattr) truncate_setsize(inode, iattr->ia_size); v9fs_invalidate_inode_attr(inode); - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); mark_inode_dirty(inode); if (iattr->ia_valid & ATTR_MODE) { /* We also want to update ACL when we update mode bits */ diff --git a/fs/adfs/inode.c b/fs/adfs/inode.c index 32620f4a7623..278dcee6ae22 100644 --- a/fs/adfs/inode.c +++ b/fs/adfs/inode.c @@ -299,7 +299,7 @@ adfs_notify_change(struct dentry *dentry, struct iattr *attr) unsigned int ia_valid = attr->ia_valid; int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); /* * we can't change the UID or GID of any file - diff --git a/fs/affs/inode.c b/fs/affs/inode.c index 044412110b52..767e5bdfb703 100644 --- a/fs/affs/inode.c +++ b/fs/affs/inode.c @@ -223,7 +223,7 @@ affs_notify_change(struct dentry *dentry, struct iattr *attr) pr_debug("notify_change(%lu,0x%x)\n", inode->i_ino, attr->ia_valid); - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) goto out; @@ -249,7 +249,7 @@ affs_notify_change(struct dentry *dentry, struct iattr *attr) affs_truncate(inode); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); if (attr->ia_valid & ATTR_MODE) diff --git a/fs/attr.c b/fs/attr.c index 00ae0b000146..939402dea6f7 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -18,27 +18,55 @@ #include #include -static bool chown_ok(const struct inode *inode, kuid_t uid) +/** + * chown_ok - verify permissions to chown inode + * @mnt_userns: user namespace of the mount @inode was found from + * @inode: inode to check permissions on + * @uid: uid to chown @inode to + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +static bool chown_ok(struct user_namespace *mnt_userns, + const struct inode *inode, + kuid_t uid) { - if (uid_eq(current_fsuid(), inode->i_uid) && - uid_eq(uid, inode->i_uid)) + kuid_t kuid = i_uid_into_mnt(mnt_userns, inode); + if (uid_eq(current_fsuid(), kuid) && uid_eq(uid, kuid)) return true; - if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN)) + if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_CHOWN)) return true; - if (uid_eq(inode->i_uid, INVALID_UID) && + if (uid_eq(kuid, INVALID_UID) && ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN)) return true; return false; } -static bool chgrp_ok(const struct inode *inode, kgid_t gid) +/** + * chgrp_ok - verify permissions to chgrp inode + * @mnt_userns: user namespace of the mount @inode was found from + * @inode: inode to check permissions on + * @gid: gid to chown @inode to + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +static bool chgrp_ok(struct user_namespace *mnt_userns, + const struct inode *inode, kgid_t gid) { - if (uid_eq(current_fsuid(), inode->i_uid) && - (in_group_p(gid) || gid_eq(gid, inode->i_gid))) + kgid_t kgid = i_gid_into_mnt(mnt_userns, inode); + if (uid_eq(current_fsuid(), i_uid_into_mnt(mnt_userns, inode)) && + (in_group_p(gid) || gid_eq(gid, kgid))) return true; - if (capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_CHOWN)) + if (capable_wrt_inode_uidgid(mnt_userns, inode, CAP_CHOWN)) return true; - if (gid_eq(inode->i_gid, INVALID_GID) && + if (gid_eq(kgid, INVALID_GID) && ns_capable(inode->i_sb->s_user_ns, CAP_CHOWN)) return true; return false; @@ -46,6 +74,7 @@ static bool chgrp_ok(const struct inode *inode, kgid_t gid) /** * setattr_prepare - check if attribute changes to a dentry are allowed + * @mnt_userns: user namespace of the mount the inode was found from * @dentry: dentry to check * @attr: attributes to change * @@ -55,10 +84,17 @@ static bool chgrp_ok(const struct inode *inode, kgid_t gid) * SGID bit from mode if user is not allowed to set it. Also file capabilities * and IMA extended attributes are cleared if ATTR_KILL_PRIV is set. * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * Should be called as the first thing in ->setattr implementations, * possibly after taking additional locks. */ -int setattr_prepare(struct dentry *dentry, struct iattr *attr) +int setattr_prepare(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr) { struct inode *inode = d_inode(dentry); unsigned int ia_valid = attr->ia_valid; @@ -78,27 +114,27 @@ int setattr_prepare(struct dentry *dentry, struct iattr *attr) goto kill_priv; /* Make sure a caller can chown. */ - if ((ia_valid & ATTR_UID) && !chown_ok(inode, attr->ia_uid)) + if ((ia_valid & ATTR_UID) && !chown_ok(mnt_userns, inode, attr->ia_uid)) return -EPERM; /* Make sure caller can chgrp. */ - if ((ia_valid & ATTR_GID) && !chgrp_ok(inode, attr->ia_gid)) + if ((ia_valid & ATTR_GID) && !chgrp_ok(mnt_userns, inode, attr->ia_gid)) return -EPERM; /* Make sure a caller can chmod. */ if (ia_valid & ATTR_MODE) { - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EPERM; /* Also check the setgid bit! */ - if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid : - inode->i_gid) && - !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) + if (!in_group_p((ia_valid & ATTR_GID) ? attr->ia_gid : + i_gid_into_mnt(mnt_userns, inode)) && + !capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FSETID)) attr->ia_mode &= ~S_ISGID; } /* Check for setting the inode time. */ if (ia_valid & (ATTR_MTIME_SET | ATTR_ATIME_SET | ATTR_TIMES_SET)) { - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EPERM; } @@ -162,20 +198,33 @@ EXPORT_SYMBOL(inode_newsize_ok); /** * setattr_copy - copy simple metadata updates into the generic inode + * @mnt_userns: user namespace of the mount the inode was found from * @inode: the inode to be updated * @attr: the new attributes * * setattr_copy must be called with i_mutex held. * * setattr_copy updates the inode's metadata with that specified - * in attr. Noticeably missing is inode size update, which is more complex + * in attr on idmapped mounts. If file ownership is changed setattr_copy + * doesn't map ia_uid and ia_gid. It will asssume the caller has already + * provided the intended values. Necessary permission checks to determine + * whether or not the S_ISGID property needs to be removed are performed with + * the correct idmapped mount permission helpers. + * Noticeably missing is inode size update, which is more complex * as it requires pagecache updates. * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * The inode is not marked as dirty after this operation. The rationale is * that for "simple" filesystems, the struct inode is the inode storage. * The caller is free to mark the inode dirty afterwards if needed. */ -void setattr_copy(struct inode *inode, const struct iattr *attr) +void setattr_copy(struct user_namespace *mnt_userns, struct inode *inode, + const struct iattr *attr) { unsigned int ia_valid = attr->ia_valid; @@ -191,9 +240,9 @@ void setattr_copy(struct inode *inode, const struct iattr *attr) inode->i_ctime = attr->ia_ctime; if (ia_valid & ATTR_MODE) { umode_t mode = attr->ia_mode; - - if (!in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) + kgid_t kgid = i_gid_into_mnt(mnt_userns, inode); + if (!in_group_p(kgid) && + !capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FSETID)) mode &= ~S_ISGID; inode->i_mode = mode; } @@ -202,6 +251,7 @@ EXPORT_SYMBOL(setattr_copy); /** * notify_change - modify attributes of a filesytem object + * @mnt_userns: user namespace of the mount the inode was found from * @dentry: object affected * @attr: new attributes * @delegated_inode: returns inode, if the inode is delegated @@ -214,13 +264,23 @@ EXPORT_SYMBOL(setattr_copy); * retry. Because breaking a delegation may take a long time, the * caller should drop the i_mutex before doing so. * + * If file ownership is changed notify_change() doesn't map ia_uid and + * ia_gid. It will asssume the caller has already provided the intended values. + * * Alternatively, a caller may pass NULL for delegated_inode. This may * be appropriate for callers that expect the underlying filesystem not * to be NFS exported. Also, passing NULL is fine for callers holding * the file open for write, as there can be no conflicting delegation in * that case. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. */ -int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **delegated_inode) +int notify_change(struct user_namespace *mnt_userns, struct dentry *dentry, + struct iattr *attr, struct inode **delegated_inode) { struct inode *inode = dentry->d_inode; umode_t mode = inode->i_mode; @@ -243,9 +303,8 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de if (IS_IMMUTABLE(inode)) return -EPERM; - if (!inode_owner_or_capable(&init_user_ns, inode)) { - error = inode_permission(&init_user_ns, inode, - MAY_WRITE); + if (!inode_owner_or_capable(mnt_userns, inode)) { + error = inode_permission(mnt_userns, inode, MAY_WRITE); if (error) return error; } @@ -321,9 +380,9 @@ int notify_change(struct dentry * dentry, struct iattr * attr, struct inode **de /* Don't allow modifications of files with invalid uids or * gids unless those uids & gids are being made valid. */ - if (!(ia_valid & ATTR_UID) && !uid_valid(inode->i_uid)) + if (!(ia_valid & ATTR_UID) && !uid_valid(i_uid_into_mnt(mnt_userns, inode))) return -EOVERFLOW; - if (!(ia_valid & ATTR_GID) && !gid_valid(inode->i_gid)) + if (!(ia_valid & ATTR_GID) && !gid_valid(i_gid_into_mnt(mnt_userns, inode))) return -EOVERFLOW; error = security_inode_setattr(dentry, attr); diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 4a67533c72fb..732d9ff1e282 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -4933,7 +4933,7 @@ static int btrfs_setattr(struct dentry *dentry, struct iattr *attr) if (btrfs_root_readonly(root)) return -EROFS; - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err) return err; @@ -4944,7 +4944,7 @@ static int btrfs_setattr(struct dentry *dentry, struct iattr *attr) } if (attr->ia_valid) { - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); inode_inc_iversion(inode); err = btrfs_dirty_inode(inode); diff --git a/fs/cachefiles/interface.c b/fs/cachefiles/interface.c index 4cea5fbf695e..5efa6a3702c0 100644 --- a/fs/cachefiles/interface.c +++ b/fs/cachefiles/interface.c @@ -470,14 +470,14 @@ static int cachefiles_attr_changed(struct fscache_object *_object) _debug("discard tail %llx", oi_size); newattrs.ia_valid = ATTR_SIZE; newattrs.ia_size = oi_size & PAGE_MASK; - ret = notify_change(object->backer, &newattrs, NULL); + ret = notify_change(&init_user_ns, object->backer, &newattrs, NULL); if (ret < 0) goto truncate_failed; } newattrs.ia_valid = ATTR_SIZE; newattrs.ia_size = ni_size; - ret = notify_change(object->backer, &newattrs, NULL); + ret = notify_change(&init_user_ns, object->backer, &newattrs, NULL); truncate_failed: inode_unlock(d_inode(object->backer)); diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index abfe42df8a1a..06645c2efa6f 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -2251,7 +2251,7 @@ int ceph_setattr(struct dentry *dentry, struct iattr *attr) if (ceph_snap(inode) != CEPH_NOSNAP) return -EROFS; - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err != 0) return err; diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index 9ee5f304592f..4d69b786e403 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -2602,7 +2602,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_PERM) attrs->ia_valid |= ATTR_FORCE; - rc = setattr_prepare(direntry, attrs); + rc = setattr_prepare(&init_user_ns, direntry, attrs); if (rc < 0) goto out; @@ -2707,7 +2707,7 @@ cifs_setattr_unix(struct dentry *direntry, struct iattr *attrs) attrs->ia_size != i_size_read(inode)) truncate_setsize(inode, attrs->ia_size); - setattr_copy(inode, attrs); + setattr_copy(&init_user_ns, inode, attrs); mark_inode_dirty(inode); /* force revalidate when any of these times are set since some @@ -2749,7 +2749,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) if (cifs_sb->mnt_cifs_flags & CIFS_MOUNT_NO_PERM) attrs->ia_valid |= ATTR_FORCE; - rc = setattr_prepare(direntry, attrs); + rc = setattr_prepare(&init_user_ns, direntry, attrs); if (rc < 0) { free_xid(xid); return rc; @@ -2897,7 +2897,7 @@ cifs_setattr_nounix(struct dentry *direntry, struct iattr *attrs) attrs->ia_size != i_size_read(inode)) truncate_setsize(inode, attrs->ia_size); - setattr_copy(inode, attrs); + setattr_copy(&init_user_ns, inode, attrs); mark_inode_dirty(inode); cifs_setattr_exit: diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 9b1ae410983c..2454aef02a68 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -855,7 +855,7 @@ int ecryptfs_truncate(struct dentry *dentry, loff_t new_length) struct dentry *lower_dentry = ecryptfs_dentry_to_lower(dentry); inode_lock(d_inode(lower_dentry)); - rc = notify_change(lower_dentry, &lower_ia, NULL); + rc = notify_change(&init_user_ns, lower_dentry, &lower_ia, NULL); inode_unlock(d_inode(lower_dentry)); } return rc; @@ -933,7 +933,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia) } mutex_unlock(&crypt_stat->cs_mutex); - rc = setattr_prepare(dentry, ia); + rc = setattr_prepare(&init_user_ns, dentry, ia); if (rc) goto out; if (ia->ia_valid & ATTR_SIZE) { @@ -959,7 +959,7 @@ static int ecryptfs_setattr(struct dentry *dentry, struct iattr *ia) lower_ia.ia_valid &= ~ATTR_MODE; inode_lock(d_inode(lower_dentry)); - rc = notify_change(lower_dentry, &lower_ia, NULL); + rc = notify_change(&init_user_ns, lower_dentry, &lower_ia, NULL); inode_unlock(d_inode(lower_dentry)); out: fsstack_copy_attr_all(inode, lower_inode); diff --git a/fs/exfat/file.c b/fs/exfat/file.c index a92478eabfa4..ace35aa8e64b 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -305,7 +305,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) ATTR_TIMES_SET); } - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); attr->ia_valid = ia_valid; if (error) goto out; @@ -340,7 +340,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) up_write(&EXFAT_I(inode)->truncate_lock); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); exfat_truncate_atime(&inode->i_atime); mark_inode_dirty(inode); diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index 11c5c6fe75bb..8eccb67d04b2 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -1668,7 +1668,7 @@ int ext2_setattr(struct dentry *dentry, struct iattr *iattr) struct inode *inode = d_inode(dentry); int error; - error = setattr_prepare(dentry, iattr); + error = setattr_prepare(&init_user_ns, dentry, iattr); if (error) return error; @@ -1688,7 +1688,7 @@ int ext2_setattr(struct dentry *dentry, struct iattr *iattr) if (error) return error; } - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); if (iattr->ia_valid & ATTR_MODE) error = posix_acl_chmod(inode, inode->i_mode); mark_inode_dirty(inode); diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 0d8385aea898..96e33198066f 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -5324,7 +5324,7 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr) ATTR_GID | ATTR_TIMES_SET)))) return -EPERM; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -5499,7 +5499,7 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr) } if (!error) { - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); } diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index 333442e96cc4..bceb4619dcce 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -825,7 +825,8 @@ int f2fs_getattr(const struct path *path, struct kstat *stat, } #ifdef CONFIG_F2FS_FS_POSIX_ACL -static void __setattr_copy(struct inode *inode, const struct iattr *attr) +static void __setattr_copy(struct user_namespace *mnt_userns, struct inode *inode, + const struct iattr *attr) { unsigned int ia_valid = attr->ia_valid; @@ -841,8 +842,9 @@ static void __setattr_copy(struct inode *inode, const struct iattr *attr) inode->i_ctime = attr->ia_ctime; if (ia_valid & ATTR_MODE) { umode_t mode = attr->ia_mode; + kgid_t kgid = i_gid_into_mnt(mnt_userns, inode); - if (!in_group_p(inode->i_gid) && !capable(CAP_FSETID)) + if (!in_group_p(kgid) && !capable(CAP_FSETID)) mode &= ~S_ISGID; set_acl_inode(inode, mode); } @@ -863,7 +865,7 @@ int f2fs_setattr(struct dentry *dentry, struct iattr *attr) !f2fs_is_compress_backend_ready(inode)) return -EOPNOTSUPP; - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err) return err; @@ -939,7 +941,7 @@ int f2fs_setattr(struct dentry *dentry, struct iattr *attr) spin_unlock(&F2FS_I(inode)->i_size_lock); } - __setattr_copy(inode, attr); + __setattr_copy(&init_user_ns, inode, attr); if (attr->ia_valid & ATTR_MODE) { err = posix_acl_chmod(inode, f2fs_get_inode_mode(inode)); diff --git a/fs/fat/file.c b/fs/fat/file.c index f9ee27cf4d7c..805b501467e9 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -480,7 +480,7 @@ int fat_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_valid &= ~TIMES_SET_FLAGS; } - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); attr->ia_valid = ia_valid; if (error) { if (sbi->options.quiet) @@ -550,7 +550,7 @@ int fat_setattr(struct dentry *dentry, struct iattr *attr) fat_truncate_time(inode, &attr->ia_mtime, S_MTIME); attr->ia_valid &= ~(ATTR_ATIME|ATTR_CTIME|ATTR_MTIME); - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); out: return error; diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index 7ce7baed3f4f..dc6e8cc565d2 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1584,7 +1584,7 @@ int fuse_do_setattr(struct dentry *dentry, struct iattr *attr, if (!fc->default_permissions) attr->ia_valid |= ATTR_FORCE; - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err) return err; diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 8445588b58a6..2cf9ed1575c4 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1860,7 +1860,7 @@ int gfs2_permission(struct inode *inode, int mask) static int __gfs2_setattr_simple(struct inode *inode, struct iattr *attr) { - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } @@ -1981,7 +1981,7 @@ static int gfs2_setattr(struct dentry *dentry, struct iattr *attr) if (IS_IMMUTABLE(inode) || IS_APPEND(inode)) goto error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) goto error; diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c index f35a37c65e5f..c646218b72bf 100644 --- a/fs/hfs/inode.c +++ b/fs/hfs/inode.c @@ -608,7 +608,7 @@ int hfs_inode_setattr(struct dentry *dentry, struct iattr * attr) struct hfs_sb_info *hsb = HFS_SB(inode->i_sb); int error; - error = setattr_prepare(dentry, attr); /* basic permission checks */ + error = setattr_prepare(&init_user_ns, dentry, attr); /* basic permission checks */ if (error) return error; @@ -647,7 +647,7 @@ int hfs_inode_setattr(struct dentry *dentry, struct iattr * attr) current_time(inode); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index 21357046b039..ffa137f8234e 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -246,7 +246,7 @@ static int hfsplus_setattr(struct dentry *dentry, struct iattr *attr) struct inode *inode = d_inode(dentry); int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -264,7 +264,7 @@ static int hfsplus_setattr(struct dentry *dentry, struct iattr *attr) inode->i_mtime = inode->i_ctime = current_time(inode); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; diff --git a/fs/hostfs/hostfs_kern.c b/fs/hostfs/hostfs_kern.c index 36da0c31d053..a00899c8fd52 100644 --- a/fs/hostfs/hostfs_kern.c +++ b/fs/hostfs/hostfs_kern.c @@ -792,7 +792,7 @@ static int hostfs_setattr(struct dentry *dentry, struct iattr *attr) int fd = HOSTFS_I(inode)->fd; - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err) return err; @@ -849,7 +849,7 @@ static int hostfs_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_size != i_size_read(inode)) truncate_setsize(inode, attr->ia_size); - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/hpfs/inode.c b/fs/hpfs/inode.c index eb8b4baf0f2e..8ba2152a78ba 100644 --- a/fs/hpfs/inode.c +++ b/fs/hpfs/inode.c @@ -274,7 +274,7 @@ int hpfs_setattr(struct dentry *dentry, struct iattr *attr) if ((attr->ia_valid & ATTR_SIZE) && attr->ia_size > inode->i_size) goto out_unlock; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) goto out_unlock; @@ -288,7 +288,7 @@ int hpfs_setattr(struct dentry *dentry, struct iattr *attr) hpfs_truncate(inode); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); hpfs_write_inode(inode); diff --git a/fs/hugetlbfs/inode.c b/fs/hugetlbfs/inode.c index 6737929e443c..327e572b4e00 100644 --- a/fs/hugetlbfs/inode.c +++ b/fs/hugetlbfs/inode.c @@ -761,7 +761,7 @@ static int hugetlbfs_setattr(struct dentry *dentry, struct iattr *attr) BUG_ON(!inode); - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -780,7 +780,7 @@ static int hugetlbfs_setattr(struct dentry *dentry, struct iattr *attr) return error; } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/inode.c b/fs/inode.c index b402e45b3ada..d96710878fba 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -1913,7 +1913,7 @@ static int __remove_privs(struct dentry *dentry, int kill) * Note we call this on write, so notify_change will not * encounter any conflicting delegations: */ - return notify_change(dentry, &newattrs, NULL); + return notify_change(&init_user_ns, dentry, &newattrs, NULL); } /* diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c index 78858f6e9583..67993808f4da 100644 --- a/fs/jffs2/fs.c +++ b/fs/jffs2/fs.c @@ -195,7 +195,7 @@ int jffs2_setattr(struct dentry *dentry, struct iattr *iattr) struct inode *inode = d_inode(dentry); int rc; - rc = setattr_prepare(dentry, iattr); + rc = setattr_prepare(&init_user_ns, dentry, iattr); if (rc) return rc; diff --git a/fs/jfs/file.c b/fs/jfs/file.c index 930d2701f206..ff49876e9c9b 100644 --- a/fs/jfs/file.c +++ b/fs/jfs/file.c @@ -90,7 +90,7 @@ int jfs_setattr(struct dentry *dentry, struct iattr *iattr) struct inode *inode = d_inode(dentry); int rc; - rc = setattr_prepare(dentry, iattr); + rc = setattr_prepare(&init_user_ns, dentry, iattr); if (rc) return rc; @@ -118,7 +118,7 @@ int jfs_setattr(struct dentry *dentry, struct iattr *iattr) jfs_truncate(inode); } - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); mark_inode_dirty(inode); if (iattr->ia_valid & ATTR_MODE) diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index ff5598cc1de0..86bd4c593b78 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -122,7 +122,7 @@ int kernfs_iop_setattr(struct dentry *dentry, struct iattr *iattr) return -EINVAL; mutex_lock(&kernfs_mutex); - error = setattr_prepare(dentry, iattr); + error = setattr_prepare(&init_user_ns, dentry, iattr); if (error) goto out; @@ -131,7 +131,7 @@ int kernfs_iop_setattr(struct dentry *dentry, struct iattr *iattr) goto out; /* this ignores size changes */ - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); out: mutex_unlock(&kernfs_mutex); diff --git a/fs/libfs.c b/fs/libfs.c index fdd3d51035b6..a0ca5c9f946d 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -497,13 +497,13 @@ int simple_setattr(struct dentry *dentry, struct iattr *iattr) struct inode *inode = d_inode(dentry); int error; - error = setattr_prepare(dentry, iattr); + error = setattr_prepare(&init_user_ns, dentry, iattr); if (error) return error; if (iattr->ia_valid & ATTR_SIZE) truncate_setsize(inode, iattr->ia_size); - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); mark_inode_dirty(inode); return 0; } diff --git a/fs/minix/file.c b/fs/minix/file.c index c50b0a20fcd9..f07acd268577 100644 --- a/fs/minix/file.c +++ b/fs/minix/file.c @@ -27,7 +27,7 @@ static int minix_setattr(struct dentry *dentry, struct iattr *attr) struct inode *inode = d_inode(dentry); int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -41,7 +41,7 @@ static int minix_setattr(struct dentry *dentry, struct iattr *attr) minix_truncate(inode); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/nfsd/nfsproc.c b/fs/nfsd/nfsproc.c index 0d71549f9d42..aaa1dd09e243 100644 --- a/fs/nfsd/nfsproc.c +++ b/fs/nfsd/nfsproc.c @@ -90,7 +90,7 @@ nfsd_proc_setattr(struct svc_rqst *rqstp) if (delta < 0) delta = -delta; if (delta < MAX_TOUCH_TIME_ERROR && - setattr_prepare(fhp->fh_dentry, iap) != 0) { + setattr_prepare(&init_user_ns, fhp->fh_dentry, iap) != 0) { /* * Turn off ATTR_[AM]TIME_SET but leave ATTR_[AM]TIME. * This will cause notify_change to set these times diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 3cad79c3b441..e9b1452b505e 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -448,7 +448,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, .ia_size = iap->ia_size, }; - host_err = notify_change(dentry, &size_attr, NULL); + host_err = notify_change(&init_user_ns, dentry, &size_attr, NULL); if (host_err) goto out_unlock; iap->ia_valid &= ~ATTR_SIZE; @@ -463,7 +463,7 @@ nfsd_setattr(struct svc_rqst *rqstp, struct svc_fh *fhp, struct iattr *iap, } iap->ia_valid |= ATTR_CTIME; - host_err = notify_change(dentry, iap, NULL); + host_err = notify_change(&init_user_ns, dentry, iap, NULL); out_unlock: fh_unlock(fhp); diff --git a/fs/nilfs2/inode.c b/fs/nilfs2/inode.c index 11225a659736..8aad3c48092a 100644 --- a/fs/nilfs2/inode.c +++ b/fs/nilfs2/inode.c @@ -812,7 +812,7 @@ int nilfs_setattr(struct dentry *dentry, struct iattr *iattr) struct super_block *sb = inode->i_sb; int err; - err = setattr_prepare(dentry, iattr); + err = setattr_prepare(&init_user_ns, dentry, iattr); if (err) return err; @@ -827,7 +827,7 @@ int nilfs_setattr(struct dentry *dentry, struct iattr *iattr) nilfs_truncate(inode); } - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); mark_inode_dirty(inode); if (iattr->ia_valid & ATTR_MODE) { diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c index caf563981532..1f2ab03f4903 100644 --- a/fs/ntfs/inode.c +++ b/fs/ntfs/inode.c @@ -2868,7 +2868,7 @@ int ntfs_setattr(struct dentry *dentry, struct iattr *attr) int err; unsigned int ia_valid = attr->ia_valid; - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err) goto out; /* We do not support NTFS ACLs yet. */ diff --git a/fs/ocfs2/dlmfs/dlmfs.c b/fs/ocfs2/dlmfs/dlmfs.c index 37c7d03a6284..9fa66cd1f622 100644 --- a/fs/ocfs2/dlmfs/dlmfs.c +++ b/fs/ocfs2/dlmfs/dlmfs.c @@ -196,11 +196,11 @@ static int dlmfs_file_setattr(struct dentry *dentry, struct iattr *attr) struct inode *inode = d_inode(dentry); attr->ia_valid &= ~ATTR_SIZE; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index 0c75619adf54..cabf355b148f 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -1142,7 +1142,7 @@ int ocfs2_setattr(struct dentry *dentry, struct iattr *attr) if (!(attr->ia_valid & OCFS2_VALID_ATTRS)) return 0; - status = setattr_prepare(dentry, attr); + status = setattr_prepare(&init_user_ns, dentry, attr); if (status) return status; @@ -1263,7 +1263,7 @@ int ocfs2_setattr(struct dentry *dentry, struct iattr *attr) } } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); status = ocfs2_mark_inode_dirty(handle, inode, bh); diff --git a/fs/omfs/file.c b/fs/omfs/file.c index 2c7b70ee1388..729339cd7902 100644 --- a/fs/omfs/file.c +++ b/fs/omfs/file.c @@ -348,7 +348,7 @@ static int omfs_setattr(struct dentry *dentry, struct iattr *attr) struct inode *inode = d_inode(dentry); int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -361,7 +361,7 @@ static int omfs_setattr(struct dentry *dentry, struct iattr *attr) omfs_truncate(inode); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/open.c b/fs/open.c index b0e8430e29f3..2dc94689a7dc 100644 --- a/fs/open.c +++ b/fs/open.c @@ -61,7 +61,7 @@ int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, inode_lock(dentry->d_inode); /* Note any delegations or leases have already been broken: */ - ret = notify_change(dentry, &newattrs, NULL); + ret = notify_change(&init_user_ns, dentry, &newattrs, NULL); inode_unlock(dentry->d_inode); return ret; } @@ -580,7 +580,7 @@ int chmod_common(const struct path *path, umode_t mode) goto out_unlock; newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; - error = notify_change(path->dentry, &newattrs, &delegated_inode); + error = notify_change(&init_user_ns, path->dentry, &newattrs, &delegated_inode); out_unlock: inode_unlock(inode); if (delegated_inode) { @@ -671,7 +671,7 @@ int chown_common(const struct path *path, uid_t user, gid_t group) inode_lock(inode); error = security_path_chown(path, uid, gid); if (!error) - error = notify_change(path->dentry, &newattrs, &delegated_inode); + error = notify_change(&init_user_ns, path->dentry, &newattrs, &delegated_inode); inode_unlock(inode); if (delegated_inode) { error = break_deleg_wait(&delegated_inode); diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 4c790cc8042d..8ac9491ceb9a 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -855,7 +855,7 @@ int __orangefs_setattr(struct inode *inode, struct iattr *iattr) ORANGEFS_I(inode)->attr_uid = current_fsuid(); ORANGEFS_I(inode)->attr_gid = current_fsgid(); } - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); spin_unlock(&inode->i_lock); mark_inode_dirty(inode); @@ -876,7 +876,7 @@ int orangefs_setattr(struct dentry *dentry, struct iattr *iattr) int ret; gossip_debug(GOSSIP_INODE_DEBUG, "__orangefs_setattr: called on %pd\n", dentry); - ret = setattr_prepare(dentry, iattr); + ret = setattr_prepare(&init_user_ns, dentry, iattr); if (ret) goto out; ret = __orangefs_setattr(d_inode(dentry), iattr); diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index 955ecd4030f0..b972a58f1836 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -235,7 +235,7 @@ static int ovl_set_size(struct dentry *upperdentry, struct kstat *stat) .ia_size = stat->size, }; - return notify_change(upperdentry, &attr, NULL); + return notify_change(&init_user_ns, upperdentry, &attr, NULL); } static int ovl_set_timestamps(struct dentry *upperdentry, struct kstat *stat) @@ -247,7 +247,7 @@ static int ovl_set_timestamps(struct dentry *upperdentry, struct kstat *stat) .ia_mtime = stat->mtime, }; - return notify_change(upperdentry, &attr, NULL); + return notify_change(&init_user_ns, upperdentry, &attr, NULL); } int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) @@ -259,7 +259,7 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) .ia_valid = ATTR_MODE, .ia_mode = stat->mode, }; - err = notify_change(upperdentry, &attr, NULL); + err = notify_change(&init_user_ns, upperdentry, &attr, NULL); } if (!err) { struct iattr attr = { @@ -267,7 +267,7 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) .ia_uid = stat->uid, .ia_gid = stat->gid, }; - err = notify_change(upperdentry, &attr, NULL); + err = notify_change(&init_user_ns, upperdentry, &attr, NULL); } if (!err) ovl_set_timestamps(upperdentry, stat); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 98a23353b19a..29840820a46c 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -508,7 +508,7 @@ static int ovl_create_over_whiteout(struct dentry *dentry, struct inode *inode, .ia_mode = cattr->mode, }; inode_lock(newdentry->d_inode); - err = notify_change(newdentry, &attr, NULL); + err = notify_change(&init_user_ns, newdentry, &attr, NULL); inode_unlock(newdentry->d_inode); if (err) goto out_cleanup; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index c8e3c17ca131..7d1a455e4177 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -21,7 +21,7 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) struct dentry *upperdentry; const struct cred *old_cred; - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err) return err; @@ -79,7 +79,7 @@ int ovl_setattr(struct dentry *dentry, struct iattr *attr) inode_lock(upperdentry->d_inode); old_cred = ovl_override_creds(dentry->d_sb); - err = notify_change(upperdentry, attr, NULL); + err = notify_change(&init_user_ns, upperdentry, attr, NULL); revert_creds(old_cred); if (!err) ovl_copyattr(upperdentry->d_inode, dentry->d_inode); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 82f2c35894e4..64f5f8f6f84e 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -759,7 +759,7 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs, /* Clear any inherited mode bits */ inode_lock(work->d_inode); - err = notify_change(work, &attr, NULL); + err = notify_change(&init_user_ns, work, &attr, NULL); inode_unlock(work->d_inode); if (err) goto out_dput; diff --git a/fs/proc/base.c b/fs/proc/base.c index ff7259f820c8..266b54b75c4c 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -693,11 +693,11 @@ int proc_setattr(struct dentry *dentry, struct iattr *attr) if (attr->ia_valid & ATTR_MODE) return -EPERM; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/proc/generic.c b/fs/proc/generic.c index b84663252add..896f2ebde75c 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -121,11 +121,11 @@ static int proc_notify_change(struct dentry *dentry, struct iattr *iattr) struct proc_dir_entry *de = PDE(inode); int error; - error = setattr_prepare(dentry, iattr); + error = setattr_prepare(&init_user_ns, dentry, iattr); if (error) return error; - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); mark_inode_dirty(inode); proc_set_user(de, inode->i_uid, inode->i_gid); diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index 317899222d7f..ec67dbc1f705 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -821,11 +821,11 @@ static int proc_sys_setattr(struct dentry *dentry, struct iattr *attr) if (attr->ia_valid & (ATTR_MODE | ATTR_UID | ATTR_GID)) return -EPERM; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/ramfs/file-nommu.c b/fs/ramfs/file-nommu.c index 355523f4a4bf..f0358fe410d3 100644 --- a/fs/ramfs/file-nommu.c +++ b/fs/ramfs/file-nommu.c @@ -165,7 +165,7 @@ static int ramfs_nommu_setattr(struct dentry *dentry, struct iattr *ia) int ret = 0; /* POSIX UID/GID verification for setting inode attributes */ - ret = setattr_prepare(dentry, ia); + ret = setattr_prepare(&init_user_ns, dentry, ia); if (ret) return ret; @@ -185,7 +185,7 @@ static int ramfs_nommu_setattr(struct dentry *dentry, struct iattr *ia) } } - setattr_copy(inode, ia); + setattr_copy(&init_user_ns, inode, ia); out: ia->ia_valid = old_ia_valid; return ret; diff --git a/fs/reiserfs/inode.c b/fs/reiserfs/inode.c index c76d563dec0e..944f2b487cf8 100644 --- a/fs/reiserfs/inode.c +++ b/fs/reiserfs/inode.c @@ -3288,7 +3288,7 @@ int reiserfs_setattr(struct dentry *dentry, struct iattr *attr) unsigned int ia_valid; int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -3413,7 +3413,7 @@ int reiserfs_setattr(struct dentry *dentry, struct iattr *attr) } if (!error) { - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); } diff --git a/fs/sysv/file.c b/fs/sysv/file.c index 45fc79a18594..ca7e216b7b9e 100644 --- a/fs/sysv/file.c +++ b/fs/sysv/file.c @@ -34,7 +34,7 @@ static int sysv_setattr(struct dentry *dentry, struct iattr *attr) struct inode *inode = d_inode(dentry); int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -47,7 +47,7 @@ static int sysv_setattr(struct dentry *dentry, struct iattr *attr) sysv_truncate(inode); } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/ubifs/file.c b/fs/ubifs/file.c index b77d1637bbbc..25041c6d08e3 100644 --- a/fs/ubifs/file.c +++ b/fs/ubifs/file.c @@ -1265,7 +1265,7 @@ int ubifs_setattr(struct dentry *dentry, struct iattr *attr) dbg_gen("ino %lu, mode %#x, ia_valid %#x", inode->i_ino, inode->i_mode, attr->ia_valid); - err = setattr_prepare(dentry, attr); + err = setattr_prepare(&init_user_ns, dentry, attr); if (err) return err; diff --git a/fs/udf/file.c b/fs/udf/file.c index 928283925d68..0d5ade491b1a 100644 --- a/fs/udf/file.c +++ b/fs/udf/file.c @@ -259,7 +259,7 @@ static int udf_setattr(struct dentry *dentry, struct iattr *attr) struct super_block *sb = inode->i_sb; int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -282,7 +282,7 @@ static int udf_setattr(struct dentry *dentry, struct iattr *attr) if (attr->ia_valid & ATTR_MODE) udf_update_extra_perms(inode, attr->ia_mode); - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/ufs/inode.c b/fs/ufs/inode.c index c843ec858cf7..6b51f3b20143 100644 --- a/fs/ufs/inode.c +++ b/fs/ufs/inode.c @@ -1217,7 +1217,7 @@ int ufs_setattr(struct dentry *dentry, struct iattr *attr) unsigned int ia_valid = attr->ia_valid; int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -1227,7 +1227,7 @@ int ufs_setattr(struct dentry *dentry, struct iattr *attr) return error; } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); mark_inode_dirty(inode); return 0; } diff --git a/fs/utimes.c b/fs/utimes.c index fd3cc4226224..1a4130bee157 100644 --- a/fs/utimes.c +++ b/fs/utimes.c @@ -62,7 +62,7 @@ int vfs_utimes(const struct path *path, struct timespec64 *times) } retry_deleg: inode_lock(inode); - error = notify_change(path->dentry, &newattrs, &delegated_inode); + error = notify_change(&init_user_ns, path->dentry, &newattrs, &delegated_inode); inode_unlock(inode); if (delegated_inode) { error = break_deleg_wait(&delegated_inode); diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index 1414ab79eacf..b1be836cf35e 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -639,7 +639,7 @@ xfs_vn_change_ok( if (XFS_FORCED_SHUTDOWN(mp)) return -EIO; - return setattr_prepare(dentry, iattr); + return setattr_prepare(&init_user_ns, dentry, iattr); } /* diff --git a/fs/zonefs/super.c b/fs/zonefs/super.c index e301a131bf1e..18039a57f70f 100644 --- a/fs/zonefs/super.c +++ b/fs/zonefs/super.c @@ -488,7 +488,7 @@ static int zonefs_inode_setattr(struct dentry *dentry, struct iattr *iattr) if (unlikely(IS_IMMUTABLE(inode))) return -EPERM; - ret = setattr_prepare(dentry, iattr); + ret = setattr_prepare(&init_user_ns, dentry, iattr); if (ret) return ret; @@ -516,7 +516,7 @@ static int zonefs_inode_setattr(struct dentry *dentry, struct iattr *iattr) return ret; } - setattr_copy(inode, iattr); + setattr_copy(&init_user_ns, inode, iattr); return 0; } diff --git a/include/linux/fs.h b/include/linux/fs.h index e77a270bd195..fb634ea3ce05 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2807,7 +2807,8 @@ static inline int bmap(struct inode *inode, sector_t *block) } #endif -extern int notify_change(struct dentry *, struct iattr *, struct inode **); +extern int notify_change(struct user_namespace *, struct dentry *, + struct iattr *, struct inode **); extern int inode_permission(struct user_namespace *, struct inode *, int); extern int generic_permission(struct user_namespace *, struct inode *, int); extern int __check_sticky(struct inode *dir, struct inode *inode); @@ -3264,9 +3265,10 @@ extern int buffer_migrate_page_norefs(struct address_space *, #define buffer_migrate_page_norefs NULL #endif -extern int setattr_prepare(struct dentry *, struct iattr *); +extern int setattr_prepare(struct user_namespace *, struct dentry *, struct iattr *); extern int inode_newsize_ok(const struct inode *, loff_t offset); -extern void setattr_copy(struct inode *inode, const struct iattr *attr); +extern void setattr_copy(struct user_namespace *, struct inode *inode, + const struct iattr *attr); extern int file_update_time(struct file *file); diff --git a/mm/shmem.c b/mm/shmem.c index 90dfdc8b7423..95d2ebcfc95c 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1087,7 +1087,7 @@ static int shmem_setattr(struct dentry *dentry, struct iattr *attr) struct shmem_sb_info *sbinfo = SHMEM_SB(inode->i_sb); int error; - error = setattr_prepare(dentry, attr); + error = setattr_prepare(&init_user_ns, dentry, attr); if (error) return error; @@ -1141,7 +1141,7 @@ static int shmem_setattr(struct dentry *dentry, struct iattr *attr) } } - setattr_copy(inode, attr); + setattr_copy(&init_user_ns, inode, attr); if (attr->ia_valid & ATTR_MODE) error = posix_acl_chmod(inode, inode->i_mode); return error; From patchwork Thu Dec 3 23:57:10 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950105 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7FE55C197BF for ; Fri, 4 Dec 2020 00:03:12 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5481C22285 for ; Fri, 4 Dec 2020 00:03:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389455AbgLDACl (ORCPT ); Thu, 3 Dec 2020 19:02:41 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41770 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388087AbgLDACg (ORCPT ); Thu, 3 Dec 2020 19:02:36 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXb-0007ka-Vm; Fri, 04 Dec 2020 00:01:40 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 14/40] acl: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:10 +0100 Message-Id: <20201203235736.3528991-15-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The posix acl permission checking helpers determine whether a caller is privileged over an inode according to the acls associated with the inode. Add helpers that make it possible to handle acls on idmapped mounts. The vfs and the filesystems targeted by this first iteration make use of posix_acl_fix_xattr_from_user() and posix_acl_fix_xattr_to_user() to translate basic posix access and default permissions such as the ACL_USER and ACL_GROUP type according to the initial user namespace (or the superblock's user namespace) to and from the caller's current user namespace. Adapt these two helpers to handle idmapped mounts whereby we either shift from or into the mount's user namespace depending on in which direction we're translating. Similarly, cap_convert_nscap() is used by the vfs to translate user namespace and non-user namespace aware filesystem capabilities from the superblock's user namespace to the caller's user namespace. Enable it to handle idmapped mounts by accounting for the mount's user namespace. In addition the fileystems targeted in the first iteration of this patch series make use of the posix_acl_chmod() and, posix_acl_update_mode() helpers. Both helpers perform permission checks on the target inode. Let them handle idmapped mounts. These two helpers are called when posix acls are set by the respective filesystems to handle this case we extend the ->set() method to take an additional user namespace argument to pass the mount's user namespace down. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- Documentation/filesystems/locking.rst | 6 +-- Documentation/filesystems/porting.rst | 2 + fs/9p/acl.c | 4 +- fs/9p/xattr.c | 1 + fs/afs/xattr.c | 2 + fs/btrfs/acl.c | 2 +- fs/btrfs/inode.c | 2 +- fs/btrfs/xattr.c | 2 + fs/ceph/acl.c | 2 +- fs/ceph/inode.c | 2 +- fs/ceph/xattr.c | 1 + fs/cifs/xattr.c | 1 + fs/ecryptfs/inode.c | 1 + fs/ext2/acl.c | 2 +- fs/ext2/inode.c | 2 +- fs/ext2/xattr_security.c | 1 + fs/ext2/xattr_trusted.c | 1 + fs/ext2/xattr_user.c | 1 + fs/ext4/acl.c | 2 +- fs/ext4/inode.c | 2 +- fs/ext4/xattr_hurd.c | 1 + fs/ext4/xattr_security.c | 1 + fs/ext4/xattr_trusted.c | 1 + fs/ext4/xattr_user.c | 1 + fs/f2fs/acl.c | 2 +- fs/f2fs/file.c | 2 +- fs/f2fs/xattr.c | 2 + fs/fuse/xattr.c | 2 + fs/gfs2/acl.c | 2 +- fs/gfs2/inode.c | 2 +- fs/gfs2/xattr.c | 1 + fs/hfs/attr.c | 1 + fs/hfsplus/xattr.c | 1 + fs/hfsplus/xattr_security.c | 1 + fs/hfsplus/xattr_trusted.c | 1 + fs/hfsplus/xattr_user.c | 1 + fs/jffs2/acl.c | 2 +- fs/jffs2/fs.c | 2 +- fs/jffs2/security.c | 1 + fs/jffs2/xattr_trusted.c | 1 + fs/jffs2/xattr_user.c | 1 + fs/jfs/acl.c | 2 +- fs/jfs/file.c | 2 +- fs/jfs/xattr.c | 2 + fs/kernfs/inode.c | 2 + fs/nfs/nfs4proc.c | 3 ++ fs/nfsd/nfs2acl.c | 4 +- fs/nfsd/nfs3acl.c | 4 +- fs/nfsd/nfs4acl.c | 4 +- fs/ocfs2/acl.c | 2 +- fs/ocfs2/xattr.c | 3 ++ fs/orangefs/acl.c | 2 +- fs/orangefs/inode.c | 2 +- fs/orangefs/xattr.c | 1 + fs/overlayfs/super.c | 3 ++ fs/posix_acl.c | 77 +++++++++++++++++++-------- fs/reiserfs/xattr_acl.c | 4 +- fs/reiserfs/xattr_security.c | 3 +- fs/reiserfs/xattr_trusted.c | 3 +- fs/reiserfs/xattr_user.c | 3 +- fs/ubifs/xattr.c | 1 + fs/xattr.c | 10 ++-- fs/xfs/xfs_acl.c | 2 +- fs/xfs/xfs_iops.c | 2 +- fs/xfs/xfs_xattr.c | 3 +- include/linux/capability.h | 3 +- include/linux/posix_acl.h | 9 ++-- include/linux/posix_acl_xattr.h | 12 +++-- include/linux/xattr.h | 6 +-- mm/shmem.c | 3 +- net/socket.c | 1 + security/commoncap.c | 47 +++++++++++++--- 72 files changed, 209 insertions(+), 84 deletions(-) diff --git a/Documentation/filesystems/locking.rst b/Documentation/filesystems/locking.rst index c0f2c7586531..daf132a038ad 100644 --- a/Documentation/filesystems/locking.rst +++ b/Documentation/filesystems/locking.rst @@ -126,9 +126,9 @@ prototypes:: int (*get)(const struct xattr_handler *handler, struct dentry *dentry, struct inode *inode, const char *name, void *buffer, size_t size); - int (*set)(const struct xattr_handler *handler, struct dentry *dentry, - struct inode *inode, const char *name, const void *buffer, - size_t size, int flags); + int (*set)(const struct xattr_handler *handler, struct user_namespace *mnt_userns, + struct dentry *dentry, struct inode *inode, const char *name, + const void *buffer, size_t size, int flags); locking rules: all may block diff --git a/Documentation/filesystems/porting.rst b/Documentation/filesystems/porting.rst index 867036aa90b8..de1dcec3b5b8 100644 --- a/Documentation/filesystems/porting.rst +++ b/Documentation/filesystems/porting.rst @@ -717,6 +717,8 @@ be removed. Switch while you still can; the old one won't stay. **mandatory** ->setxattr() and xattr_handler.set() get dentry and inode passed separately. +The xattr_handler.set() gets passed the user namespace of the mount the inode +is seen from so filesystems can idmap the i_uid and i_gid accordingly. dentry might be yet to be attached to inode, so do _not_ use its ->d_inode in the instances. Rationale: !@#!@# security_d_instantiate() needs to be called before we attach dentry to inode and !@#!@##!@$!$#!@#$!@$!@$ smack diff --git a/fs/9p/acl.c b/fs/9p/acl.c index d77b28e8d57a..1c14f18a6ec9 100644 --- a/fs/9p/acl.c +++ b/fs/9p/acl.c @@ -239,6 +239,7 @@ static int v9fs_xattr_get_acl(const struct xattr_handler *handler, } static int v9fs_xattr_set_acl(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -279,7 +280,8 @@ static int v9fs_xattr_set_acl(const struct xattr_handler *handler, struct iattr iattr = { 0 }; struct posix_acl *old_acl = acl; - retval = posix_acl_update_mode(inode, &iattr.ia_mode, &acl); + retval = posix_acl_update_mode(mnt_userns, inode, + &iattr.ia_mode, &acl); if (retval) goto err_out; if (!acl) { diff --git a/fs/9p/xattr.c b/fs/9p/xattr.c index ac8ff8ca4c11..6a822ce705b0 100644 --- a/fs/9p/xattr.c +++ b/fs/9p/xattr.c @@ -147,6 +147,7 @@ static int v9fs_xattr_handler_get(const struct xattr_handler *handler, } static int v9fs_xattr_handler_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/afs/xattr.c b/fs/afs/xattr.c index 95c573dcda11..c629caae5002 100644 --- a/fs/afs/xattr.c +++ b/fs/afs/xattr.c @@ -120,6 +120,7 @@ static const struct afs_operation_ops afs_store_acl_operation = { * Set a file's AFS3 ACL. */ static int afs_xattr_set_acl(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) @@ -248,6 +249,7 @@ static const struct afs_operation_ops yfs_store_opaque_acl2_operation = { * Set a file's YFS ACL. */ static int afs_xattr_set_yfs(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/btrfs/acl.c b/fs/btrfs/acl.c index a0af1b952c4d..b5a683e895c6 100644 --- a/fs/btrfs/acl.c +++ b/fs/btrfs/acl.c @@ -113,7 +113,7 @@ int btrfs_set_acl(struct inode *inode, struct posix_acl *acl, int type) umode_t old_mode = inode->i_mode; if (type == ACL_TYPE_ACCESS && acl) { - ret = posix_acl_update_mode(inode, &inode->i_mode, &acl); + ret = posix_acl_update_mode(&init_user_ns, inode, &inode->i_mode, &acl); if (ret) return ret; } diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 732d9ff1e282..502b7db93894 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -4949,7 +4949,7 @@ static int btrfs_setattr(struct dentry *dentry, struct iattr *attr) err = btrfs_dirty_inode(inode); if (!err && attr->ia_valid & ATTR_MODE) - err = posix_acl_chmod(inode, inode->i_mode); + err = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); } return err; diff --git a/fs/btrfs/xattr.c b/fs/btrfs/xattr.c index 95d9aebff2c4..f20927f6a281 100644 --- a/fs/btrfs/xattr.c +++ b/fs/btrfs/xattr.c @@ -360,6 +360,7 @@ static int btrfs_xattr_handler_get(const struct xattr_handler *handler, } static int btrfs_xattr_handler_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) @@ -369,6 +370,7 @@ static int btrfs_xattr_handler_set(const struct xattr_handler *handler, } static int btrfs_xattr_handler_set_prop(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ceph/acl.c b/fs/ceph/acl.c index e0465741c591..bceab4c01585 100644 --- a/fs/ceph/acl.c +++ b/fs/ceph/acl.c @@ -100,7 +100,7 @@ int ceph_set_acl(struct inode *inode, struct posix_acl *acl, int type) case ACL_TYPE_ACCESS: name = XATTR_NAME_POSIX_ACL_ACCESS; if (acl) { - ret = posix_acl_update_mode(inode, &new_mode, &acl); + ret = posix_acl_update_mode(&init_user_ns, inode, &new_mode, &acl); if (ret) goto out; } diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 06645c2efa6f..19ec845ba5ec 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -2266,7 +2266,7 @@ int ceph_setattr(struct dentry *dentry, struct iattr *attr) err = __ceph_setattr(inode, attr); if (err >= 0 && (attr->ia_valid & ATTR_MODE)) - err = posix_acl_chmod(inode, attr->ia_mode); + err = posix_acl_chmod(&init_user_ns, inode, attr->ia_mode); return err; } diff --git a/fs/ceph/xattr.c b/fs/ceph/xattr.c index 197cb1234341..a7ef6498bee2 100644 --- a/fs/ceph/xattr.c +++ b/fs/ceph/xattr.c @@ -1163,6 +1163,7 @@ static int ceph_get_xattr_handler(const struct xattr_handler *handler, } static int ceph_set_xattr_handler(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/cifs/xattr.c b/fs/cifs/xattr.c index b8299173ea7e..eb6fa254a8d4 100644 --- a/fs/cifs/xattr.c +++ b/fs/cifs/xattr.c @@ -99,6 +99,7 @@ static int cifs_creation_time_set(unsigned int xid, struct cifs_tcon *pTcon, } static int cifs_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 2454aef02a68..fa794c98c395 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -1131,6 +1131,7 @@ static int ecryptfs_xattr_get(const struct xattr_handler *handler, } static int ecryptfs_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext2/acl.c b/fs/ext2/acl.c index cf4c77f8dd08..826987b23ccb 100644 --- a/fs/ext2/acl.c +++ b/fs/ext2/acl.c @@ -223,7 +223,7 @@ ext2_set_acl(struct inode *inode, struct posix_acl *acl, int type) umode_t mode = inode->i_mode; if (type == ACL_TYPE_ACCESS && acl) { - error = posix_acl_update_mode(inode, &mode, &acl); + error = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (error) return error; update_mode = 1; diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index 8eccb67d04b2..5ff1e5e3c0fe 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -1690,7 +1690,7 @@ int ext2_setattr(struct dentry *dentry, struct iattr *iattr) } setattr_copy(&init_user_ns, inode, iattr); if (iattr->ia_valid & ATTR_MODE) - error = posix_acl_chmod(inode, inode->i_mode); + error = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); mark_inode_dirty(inode); return error; diff --git a/fs/ext2/xattr_security.c b/fs/ext2/xattr_security.c index 9a682e440acb..ebade1f52451 100644 --- a/fs/ext2/xattr_security.c +++ b/fs/ext2/xattr_security.c @@ -19,6 +19,7 @@ ext2_xattr_security_get(const struct xattr_handler *handler, static int ext2_xattr_security_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext2/xattr_trusted.c b/fs/ext2/xattr_trusted.c index 49add1107850..18a87d5dd1ab 100644 --- a/fs/ext2/xattr_trusted.c +++ b/fs/ext2/xattr_trusted.c @@ -26,6 +26,7 @@ ext2_xattr_trusted_get(const struct xattr_handler *handler, static int ext2_xattr_trusted_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext2/xattr_user.c b/fs/ext2/xattr_user.c index c243a3b4d69d..58092449f8ff 100644 --- a/fs/ext2/xattr_user.c +++ b/fs/ext2/xattr_user.c @@ -30,6 +30,7 @@ ext2_xattr_user_get(const struct xattr_handler *handler, static int ext2_xattr_user_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c index 68aaed48315f..4aad060010d8 100644 --- a/fs/ext4/acl.c +++ b/fs/ext4/acl.c @@ -245,7 +245,7 @@ ext4_set_acl(struct inode *inode, struct posix_acl *acl, int type) ext4_fc_start_update(inode); if ((type == ACL_TYPE_ACCESS) && acl) { - error = posix_acl_update_mode(inode, &mode, &acl); + error = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (error) goto out_stop; if (mode != inode->i_mode) diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index 96e33198066f..f0487795afd1 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -5511,7 +5511,7 @@ int ext4_setattr(struct dentry *dentry, struct iattr *attr) ext4_orphan_del(NULL, inode); if (!error && (ia_valid & ATTR_MODE)) - rc = posix_acl_chmod(inode, inode->i_mode); + rc = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); err_out: if (error) diff --git a/fs/ext4/xattr_hurd.c b/fs/ext4/xattr_hurd.c index 8cfa74a56361..c78df5790377 100644 --- a/fs/ext4/xattr_hurd.c +++ b/fs/ext4/xattr_hurd.c @@ -32,6 +32,7 @@ ext4_xattr_hurd_get(const struct xattr_handler *handler, static int ext4_xattr_hurd_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/xattr_security.c b/fs/ext4/xattr_security.c index 197a9d8a15ef..8213f66f7b2d 100644 --- a/fs/ext4/xattr_security.c +++ b/fs/ext4/xattr_security.c @@ -23,6 +23,7 @@ ext4_xattr_security_get(const struct xattr_handler *handler, static int ext4_xattr_security_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/xattr_trusted.c b/fs/ext4/xattr_trusted.c index e9389e5d75c3..7c21ffb26d25 100644 --- a/fs/ext4/xattr_trusted.c +++ b/fs/ext4/xattr_trusted.c @@ -30,6 +30,7 @@ ext4_xattr_trusted_get(const struct xattr_handler *handler, static int ext4_xattr_trusted_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/ext4/xattr_user.c b/fs/ext4/xattr_user.c index d4546184b34b..2fe7ff0a479c 100644 --- a/fs/ext4/xattr_user.c +++ b/fs/ext4/xattr_user.c @@ -31,6 +31,7 @@ ext4_xattr_user_get(const struct xattr_handler *handler, static int ext4_xattr_user_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/f2fs/acl.c b/fs/f2fs/acl.c index 306413589827..50735e8a354e 100644 --- a/fs/f2fs/acl.c +++ b/fs/f2fs/acl.c @@ -213,7 +213,7 @@ static int __f2fs_set_acl(struct inode *inode, int type, case ACL_TYPE_ACCESS: name_index = F2FS_XATTR_INDEX_POSIX_ACL_ACCESS; if (acl && !ipage) { - error = posix_acl_update_mode(inode, &mode, &acl); + error = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (error) return error; set_acl_inode(inode, mode); diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index bceb4619dcce..e4a13570deff 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -944,7 +944,7 @@ int f2fs_setattr(struct dentry *dentry, struct iattr *attr) __setattr_copy(&init_user_ns, inode, attr); if (attr->ia_valid & ATTR_MODE) { - err = posix_acl_chmod(inode, f2fs_get_inode_mode(inode)); + err = posix_acl_chmod(&init_user_ns, inode, f2fs_get_inode_mode(inode)); if (err || is_inode_flag_set(inode, FI_ACL_MODE)) { inode->i_mode = F2FS_I(inode)->i_acl_mode; clear_inode_flag(inode, FI_ACL_MODE); diff --git a/fs/f2fs/xattr.c b/fs/f2fs/xattr.c index d772bf13a814..10081bf74324 100644 --- a/fs/f2fs/xattr.c +++ b/fs/f2fs/xattr.c @@ -64,6 +64,7 @@ static int f2fs_xattr_generic_get(const struct xattr_handler *handler, } static int f2fs_xattr_generic_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -107,6 +108,7 @@ static int f2fs_xattr_advise_get(const struct xattr_handler *handler, } static int f2fs_xattr_advise_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/fuse/xattr.c b/fs/fuse/xattr.c index 371bdcbc7233..c7f490e86c5a 100644 --- a/fs/fuse/xattr.c +++ b/fs/fuse/xattr.c @@ -182,6 +182,7 @@ static int fuse_xattr_get(const struct xattr_handler *handler, } static int fuse_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -205,6 +206,7 @@ static int no_xattr_get(const struct xattr_handler *handler, } static int no_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *nodee, const char *name, const void *value, size_t size, int flags) diff --git a/fs/gfs2/acl.c b/fs/gfs2/acl.c index 2e939f5fe751..ce88ef29eef0 100644 --- a/fs/gfs2/acl.c +++ b/fs/gfs2/acl.c @@ -130,7 +130,7 @@ int gfs2_set_acl(struct inode *inode, struct posix_acl *acl, int type) mode = inode->i_mode; if (type == ACL_TYPE_ACCESS && acl) { - ret = posix_acl_update_mode(inode, &mode, &acl); + ret = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (ret) goto unlock; } diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index 2cf9ed1575c4..a29b66116c01 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -1992,7 +1992,7 @@ static int gfs2_setattr(struct dentry *dentry, struct iattr *attr) else { error = gfs2_setattr_simple(inode, attr); if (!error && attr->ia_valid & ATTR_MODE) - error = posix_acl_chmod(inode, inode->i_mode); + error = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); } error: diff --git a/fs/gfs2/xattr.c b/fs/gfs2/xattr.c index 9d7667bc4292..13969a813410 100644 --- a/fs/gfs2/xattr.c +++ b/fs/gfs2/xattr.c @@ -1214,6 +1214,7 @@ int __gfs2_xattr_set(struct inode *inode, const char *name, } static int gfs2_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/hfs/attr.c b/fs/hfs/attr.c index 74fa62643136..2bd54efaf416 100644 --- a/fs/hfs/attr.c +++ b/fs/hfs/attr.c @@ -121,6 +121,7 @@ static int hfs_xattr_get(const struct xattr_handler *handler, } static int hfs_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/hfsplus/xattr.c b/fs/hfsplus/xattr.c index bb0b27d88e50..4d169c5a2673 100644 --- a/fs/hfsplus/xattr.c +++ b/fs/hfsplus/xattr.c @@ -858,6 +858,7 @@ static int hfsplus_osx_getxattr(const struct xattr_handler *handler, } static int hfsplus_osx_setxattr(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/hfsplus/xattr_security.c b/fs/hfsplus/xattr_security.c index cfbe6a3bfb1e..c1c7a16cbf21 100644 --- a/fs/hfsplus/xattr_security.c +++ b/fs/hfsplus/xattr_security.c @@ -23,6 +23,7 @@ static int hfsplus_security_getxattr(const struct xattr_handler *handler, } static int hfsplus_security_setxattr(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/hfsplus/xattr_trusted.c b/fs/hfsplus/xattr_trusted.c index fbad91e1dada..e150372ec564 100644 --- a/fs/hfsplus/xattr_trusted.c +++ b/fs/hfsplus/xattr_trusted.c @@ -22,6 +22,7 @@ static int hfsplus_trusted_getxattr(const struct xattr_handler *handler, } static int hfsplus_trusted_setxattr(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/hfsplus/xattr_user.c b/fs/hfsplus/xattr_user.c index 74d19faf255e..a6b60b153916 100644 --- a/fs/hfsplus/xattr_user.c +++ b/fs/hfsplus/xattr_user.c @@ -22,6 +22,7 @@ static int hfsplus_user_getxattr(const struct xattr_handler *handler, } static int hfsplus_user_setxattr(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jffs2/acl.c b/fs/jffs2/acl.c index 093ffbd82395..cf07a2fdf8bf 100644 --- a/fs/jffs2/acl.c +++ b/fs/jffs2/acl.c @@ -236,7 +236,7 @@ int jffs2_set_acl(struct inode *inode, struct posix_acl *acl, int type) if (acl) { umode_t mode; - rc = posix_acl_update_mode(inode, &mode, &acl); + rc = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (rc) return rc; if (inode->i_mode != mode) { diff --git a/fs/jffs2/fs.c b/fs/jffs2/fs.c index 67993808f4da..ee9f51bab4c6 100644 --- a/fs/jffs2/fs.c +++ b/fs/jffs2/fs.c @@ -201,7 +201,7 @@ int jffs2_setattr(struct dentry *dentry, struct iattr *iattr) rc = jffs2_do_setattr(inode, iattr); if (!rc && (iattr->ia_valid & ATTR_MODE)) - rc = posix_acl_chmod(inode, inode->i_mode); + rc = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); return rc; } diff --git a/fs/jffs2/security.c b/fs/jffs2/security.c index c2332e30f218..aef5522551db 100644 --- a/fs/jffs2/security.c +++ b/fs/jffs2/security.c @@ -57,6 +57,7 @@ static int jffs2_security_getxattr(const struct xattr_handler *handler, } static int jffs2_security_setxattr(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jffs2/xattr_trusted.c b/fs/jffs2/xattr_trusted.c index 5d6030826c52..cc3f24883e7d 100644 --- a/fs/jffs2/xattr_trusted.c +++ b/fs/jffs2/xattr_trusted.c @@ -25,6 +25,7 @@ static int jffs2_trusted_getxattr(const struct xattr_handler *handler, } static int jffs2_trusted_setxattr(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jffs2/xattr_user.c b/fs/jffs2/xattr_user.c index 9d027b4abcf9..fb945977c013 100644 --- a/fs/jffs2/xattr_user.c +++ b/fs/jffs2/xattr_user.c @@ -25,6 +25,7 @@ static int jffs2_user_getxattr(const struct xattr_handler *handler, } static int jffs2_user_setxattr(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) diff --git a/fs/jfs/acl.c b/fs/jfs/acl.c index 92cc0ac2d1fc..cf79a34bfada 100644 --- a/fs/jfs/acl.c +++ b/fs/jfs/acl.c @@ -101,7 +101,7 @@ int jfs_set_acl(struct inode *inode, struct posix_acl *acl, int type) tid = txBegin(inode->i_sb, 0); mutex_lock(&JFS_IP(inode)->commit_mutex); if (type == ACL_TYPE_ACCESS && acl) { - rc = posix_acl_update_mode(inode, &mode, &acl); + rc = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (rc) goto end_tx; if (mode != inode->i_mode) diff --git a/fs/jfs/file.c b/fs/jfs/file.c index ff49876e9c9b..61c3b0c1fbf6 100644 --- a/fs/jfs/file.c +++ b/fs/jfs/file.c @@ -122,7 +122,7 @@ int jfs_setattr(struct dentry *dentry, struct iattr *iattr) mark_inode_dirty(inode); if (iattr->ia_valid & ATTR_MODE) - rc = posix_acl_chmod(inode, inode->i_mode); + rc = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); return rc; } diff --git a/fs/jfs/xattr.c b/fs/jfs/xattr.c index db41e7803163..f9273f6901c8 100644 --- a/fs/jfs/xattr.c +++ b/fs/jfs/xattr.c @@ -932,6 +932,7 @@ static int jfs_xattr_get(const struct xattr_handler *handler, } static int jfs_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -950,6 +951,7 @@ static int jfs_xattr_get_os2(const struct xattr_handler *handler, } static int jfs_xattr_set_os2(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index 86bd4c593b78..7e44052b42e1 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -319,6 +319,7 @@ static int kernfs_vfs_xattr_get(const struct xattr_handler *handler, } static int kernfs_vfs_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) @@ -385,6 +386,7 @@ static int kernfs_vfs_user_xattr_rm(struct kernfs_node *kn, } static int kernfs_vfs_user_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c index 9e0ca9b2b210..b1749e93b2c2 100644 --- a/fs/nfs/nfs4proc.c +++ b/fs/nfs/nfs4proc.c @@ -7486,6 +7486,7 @@ nfs4_release_lockowner(struct nfs_server *server, struct nfs4_lock_state *lsp) #define XATTR_NAME_NFSV4_ACL "system.nfs4_acl" static int nfs4_xattr_set_nfs4_acl(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) @@ -7508,6 +7509,7 @@ static bool nfs4_xattr_list_nfs4_acl(struct dentry *dentry) #ifdef CONFIG_NFS_V4_SECURITY_LABEL static int nfs4_xattr_set_nfs4_label(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) @@ -7558,6 +7560,7 @@ nfs4_listxattr_nfs4_label(struct inode *inode, char *list, size_t list_len) #ifdef CONFIG_NFS_V4_2 static int nfs4_xattr_set_nfs4_user(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *key, const void *buf, size_t buflen, int flags) diff --git a/fs/nfsd/nfs2acl.c b/fs/nfsd/nfs2acl.c index 6a900f770dd2..e5f06f21c24a 100644 --- a/fs/nfsd/nfs2acl.c +++ b/fs/nfsd/nfs2acl.c @@ -113,10 +113,10 @@ static __be32 nfsacld_proc_setacl(struct svc_rqst *rqstp) fh_lock(fh); - error = set_posix_acl(inode, ACL_TYPE_ACCESS, argp->acl_access); + error = set_posix_acl(&init_user_ns, inode, ACL_TYPE_ACCESS, argp->acl_access); if (error) goto out_drop_lock; - error = set_posix_acl(inode, ACL_TYPE_DEFAULT, argp->acl_default); + error = set_posix_acl(&init_user_ns, inode, ACL_TYPE_DEFAULT, argp->acl_default); if (error) goto out_drop_lock; diff --git a/fs/nfsd/nfs3acl.c b/fs/nfsd/nfs3acl.c index 34a394e50e1d..3618f62e118e 100644 --- a/fs/nfsd/nfs3acl.c +++ b/fs/nfsd/nfs3acl.c @@ -103,10 +103,10 @@ static __be32 nfsd3_proc_setacl(struct svc_rqst *rqstp) fh_lock(fh); - error = set_posix_acl(inode, ACL_TYPE_ACCESS, argp->acl_access); + error = set_posix_acl(&init_user_ns, inode, ACL_TYPE_ACCESS, argp->acl_access); if (error) goto out_drop_lock; - error = set_posix_acl(inode, ACL_TYPE_DEFAULT, argp->acl_default); + error = set_posix_acl(&init_user_ns, inode, ACL_TYPE_DEFAULT, argp->acl_default); out_drop_lock: fh_unlock(fh); diff --git a/fs/nfsd/nfs4acl.c b/fs/nfsd/nfs4acl.c index 71292a0d6f09..0b35bc490aee 100644 --- a/fs/nfsd/nfs4acl.c +++ b/fs/nfsd/nfs4acl.c @@ -781,12 +781,12 @@ nfsd4_set_nfs4_acl(struct svc_rqst *rqstp, struct svc_fh *fhp, fh_lock(fhp); - host_error = set_posix_acl(inode, ACL_TYPE_ACCESS, pacl); + host_error = set_posix_acl(&init_user_ns, inode, ACL_TYPE_ACCESS, pacl); if (host_error < 0) goto out_drop_lock; if (S_ISDIR(inode->i_mode)) { - host_error = set_posix_acl(inode, ACL_TYPE_DEFAULT, dpacl); + host_error = set_posix_acl(&init_user_ns, inode, ACL_TYPE_DEFAULT, dpacl); } out_drop_lock: diff --git a/fs/ocfs2/acl.c b/fs/ocfs2/acl.c index 7b07f5df3a29..7e64dbe93251 100644 --- a/fs/ocfs2/acl.c +++ b/fs/ocfs2/acl.c @@ -274,7 +274,7 @@ int ocfs2_iop_set_acl(struct inode *inode, struct posix_acl *acl, int type) if (type == ACL_TYPE_ACCESS && acl) { umode_t mode; - status = posix_acl_update_mode(inode, &mode, &acl); + status = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (status) goto unlock; diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c index 9ccd19d8f7b1..36ae47a4aef6 100644 --- a/fs/ocfs2/xattr.c +++ b/fs/ocfs2/xattr.c @@ -7249,6 +7249,7 @@ static int ocfs2_xattr_security_get(const struct xattr_handler *handler, } static int ocfs2_xattr_security_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -7321,6 +7322,7 @@ static int ocfs2_xattr_trusted_get(const struct xattr_handler *handler, } static int ocfs2_xattr_trusted_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -7351,6 +7353,7 @@ static int ocfs2_xattr_user_get(const struct xattr_handler *handler, } static int ocfs2_xattr_user_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/orangefs/acl.c b/fs/orangefs/acl.c index a25e6c890975..ba55d61906c2 100644 --- a/fs/orangefs/acl.c +++ b/fs/orangefs/acl.c @@ -132,7 +132,7 @@ int orangefs_set_acl(struct inode *inode, struct posix_acl *acl, int type) * and "mode" to the new desired value. It is up to * us to propagate the new mode back to the server... */ - error = posix_acl_update_mode(inode, &iattr.ia_mode, &acl); + error = posix_acl_update_mode(&init_user_ns, inode, &iattr.ia_mode, &acl); if (error) { gossip_err("%s: posix_acl_update_mode err: %d\n", __func__, diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 8ac9491ceb9a..563fe9ab8eb2 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -861,7 +861,7 @@ int __orangefs_setattr(struct inode *inode, struct iattr *iattr) if (iattr->ia_valid & ATTR_MODE) /* change mod on a file that has ACLs */ - ret = posix_acl_chmod(inode, inode->i_mode); + ret = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); ret = 0; out: diff --git a/fs/orangefs/xattr.c b/fs/orangefs/xattr.c index bdc285aea360..9a5b757fbd2f 100644 --- a/fs/orangefs/xattr.c +++ b/fs/orangefs/xattr.c @@ -526,6 +526,7 @@ ssize_t orangefs_listxattr(struct dentry *dentry, char *buffer, size_t size) } static int orangefs_xattr_set_default(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 64f5f8f6f84e..54ee5fd6a4cc 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -935,6 +935,7 @@ ovl_posix_acl_xattr_get(const struct xattr_handler *handler, static int __maybe_unused ovl_posix_acl_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -999,6 +1000,7 @@ static int ovl_own_xattr_get(const struct xattr_handler *handler, } static int ovl_own_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) @@ -1014,6 +1016,7 @@ static int ovl_other_xattr_get(const struct xattr_handler *handler, } static int ovl_other_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/posix_acl.c b/fs/posix_acl.c index 9ce8214bfdac..7f579917195b 100644 --- a/fs/posix_acl.c +++ b/fs/posix_acl.c @@ -558,8 +558,21 @@ __posix_acl_chmod(struct posix_acl **acl, gfp_t gfp, umode_t mode) } EXPORT_SYMBOL(__posix_acl_chmod); +/** + * posix_acl_chmod - chmod a posix acl + * + * @mnt_userns: user namespace of the mount @inode was found from + * @inode: inode to check permissions on + * @mode: the new mode of @inode + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ int -posix_acl_chmod(struct inode *inode, umode_t mode) +posix_acl_chmod(struct user_namespace *mnt_userns, struct inode *inode, umode_t mode) { struct posix_acl *acl; int ret = 0; @@ -638,9 +651,10 @@ EXPORT_SYMBOL_GPL(posix_acl_create); /** * posix_acl_update_mode - update mode in set_acl - * @inode: target inode - * @mode_p: mode (pointer) for update - * @acl: acl pointer + * @mnt_userns: user namespace of the mount @inode was found from + * @inode: target inode + * @mode_p: mode (pointer) for update + * @acl: acl pointer * * Update the file mode when setting an ACL: compute the new file permission * bits based on the ACL. In addition, if the ACL is equivalent to the new @@ -649,10 +663,16 @@ EXPORT_SYMBOL_GPL(posix_acl_create); * As with chmod, clear the setgid bit if the caller is not in the owning group * or capable of CAP_FSETID (see inode_change_ok). * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * Called from set_acl inode operations. */ -int posix_acl_update_mode(struct inode *inode, umode_t *mode_p, - struct posix_acl **acl) +int posix_acl_update_mode(struct user_namespace *mnt_userns, struct inode *inode, + umode_t *mode_p, struct posix_acl **acl) { umode_t mode = inode->i_mode; int error; @@ -662,8 +682,8 @@ int posix_acl_update_mode(struct inode *inode, umode_t *mode_p, return error; if (error == 0) *acl = NULL; - if (!in_group_p(inode->i_gid) && - !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FSETID)) + if (!in_group_p(i_gid_into_mnt(mnt_userns, inode)) && + !capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FSETID)) mode &= ~S_ISGID; *mode_p = mode; return 0; @@ -675,7 +695,8 @@ EXPORT_SYMBOL(posix_acl_update_mode); */ static void posix_acl_fix_xattr_userns( struct user_namespace *to, struct user_namespace *from, - void *value, size_t size) + struct user_namespace *mnt_userns, + void *value, size_t size, bool from_user) { struct posix_acl_xattr_header *header = value; struct posix_acl_xattr_entry *entry = (void *)(header + 1), *end; @@ -700,10 +721,18 @@ static void posix_acl_fix_xattr_userns( switch(le16_to_cpu(entry->e_tag)) { case ACL_USER: uid = make_kuid(from, le32_to_cpu(entry->e_id)); + if (from_user) + uid = kuid_from_mnt(mnt_userns, uid); + else + uid = kuid_into_mnt(mnt_userns, uid); entry->e_id = cpu_to_le32(from_kuid(to, uid)); break; case ACL_GROUP: gid = make_kgid(from, le32_to_cpu(entry->e_id)); + if (from_user) + gid = kgid_from_mnt(mnt_userns, gid); + else + gid = kgid_into_mnt(mnt_userns, gid); entry->e_id = cpu_to_le32(from_kgid(to, gid)); break; default: @@ -712,20 +741,22 @@ static void posix_acl_fix_xattr_userns( } } -void posix_acl_fix_xattr_from_user(void *value, size_t size) +void posix_acl_fix_xattr_from_user(struct user_namespace *mnt_userns, + void *value, size_t size) { struct user_namespace *user_ns = current_user_ns(); - if (user_ns == &init_user_ns) + if ((user_ns == &init_user_ns) && (mnt_userns == &init_user_ns)) return; - posix_acl_fix_xattr_userns(&init_user_ns, user_ns, value, size); + posix_acl_fix_xattr_userns(&init_user_ns, user_ns, mnt_userns, value, size, true); } -void posix_acl_fix_xattr_to_user(void *value, size_t size) +void posix_acl_fix_xattr_to_user(struct user_namespace *mnt_userns, + void *value, size_t size) { struct user_namespace *user_ns = current_user_ns(); - if (user_ns == &init_user_ns) + if ((user_ns == &init_user_ns) && (mnt_userns == &init_user_ns)) return; - posix_acl_fix_xattr_userns(user_ns, &init_user_ns, value, size); + posix_acl_fix_xattr_userns(user_ns, &init_user_ns, mnt_userns, value, size, false); } /* @@ -865,7 +896,8 @@ posix_acl_xattr_get(const struct xattr_handler *handler, } int -set_posix_acl(struct inode *inode, int type, struct posix_acl *acl) +set_posix_acl(struct user_namespace *mnt_userns, struct inode *inode, + int type, struct posix_acl *acl) { if (!IS_POSIXACL(inode)) return -EOPNOTSUPP; @@ -874,7 +906,7 @@ set_posix_acl(struct inode *inode, int type, struct posix_acl *acl) if (type == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode)) return acl ? -EACCES : 0; - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EPERM; if (acl) { @@ -888,9 +920,10 @@ EXPORT_SYMBOL(set_posix_acl); static int posix_acl_xattr_set(const struct xattr_handler *handler, - struct dentry *unused, struct inode *inode, - const char *name, const void *value, - size_t size, int flags) + struct user_namespace *mnt_userns, + struct dentry *unused, struct inode *inode, + const char *name, const void *value, size_t size, + int flags) { struct posix_acl *acl = NULL; int ret; @@ -900,7 +933,7 @@ posix_acl_xattr_set(const struct xattr_handler *handler, if (IS_ERR(acl)) return PTR_ERR(acl); } - ret = set_posix_acl(inode, handler->flags, acl); + ret = set_posix_acl(mnt_userns, inode, handler->flags, acl); posix_acl_release(acl); return ret; } @@ -934,7 +967,7 @@ int simple_set_acl(struct inode *inode, struct posix_acl *acl, int type) int error; if (type == ACL_TYPE_ACCESS) { - error = posix_acl_update_mode(inode, + error = posix_acl_update_mode(&init_user_ns, inode, &inode->i_mode, &acl); if (error) return error; diff --git a/fs/reiserfs/xattr_acl.c b/fs/reiserfs/xattr_acl.c index ccd40df6eb45..b8f397134c17 100644 --- a/fs/reiserfs/xattr_acl.c +++ b/fs/reiserfs/xattr_acl.c @@ -40,7 +40,7 @@ reiserfs_set_acl(struct inode *inode, struct posix_acl *acl, int type) reiserfs_write_unlock(inode->i_sb); if (error == 0) { if (type == ACL_TYPE_ACCESS && acl) { - error = posix_acl_update_mode(inode, &mode, &acl); + error = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (error) goto unlock; update_mode = 1; @@ -399,5 +399,5 @@ int reiserfs_acl_chmod(struct inode *inode) !reiserfs_posixacl(inode->i_sb)) return 0; - return posix_acl_chmod(inode, inode->i_mode); + return posix_acl_chmod(&init_user_ns, inode, inode->i_mode); } diff --git a/fs/reiserfs/xattr_security.c b/fs/reiserfs/xattr_security.c index 20be9a0e5870..3411ce2bfbd0 100644 --- a/fs/reiserfs/xattr_security.c +++ b/fs/reiserfs/xattr_security.c @@ -21,7 +21,8 @@ security_get(const struct xattr_handler *handler, struct dentry *unused, } static int -security_set(const struct xattr_handler *handler, struct dentry *unused, +security_set(const struct xattr_handler *handler, struct user_namespace *mnt_userns, + struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) { diff --git a/fs/reiserfs/xattr_trusted.c b/fs/reiserfs/xattr_trusted.c index 5ed48da3d02b..d338a24f26e7 100644 --- a/fs/reiserfs/xattr_trusted.c +++ b/fs/reiserfs/xattr_trusted.c @@ -20,7 +20,8 @@ trusted_get(const struct xattr_handler *handler, struct dentry *unused, } static int -trusted_set(const struct xattr_handler *handler, struct dentry *unused, +trusted_set(const struct xattr_handler *handler, struct user_namespace *mnt_userns, + struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) { diff --git a/fs/reiserfs/xattr_user.c b/fs/reiserfs/xattr_user.c index a573ca45bacc..65d9cd10a5ea 100644 --- a/fs/reiserfs/xattr_user.c +++ b/fs/reiserfs/xattr_user.c @@ -18,7 +18,8 @@ user_get(const struct xattr_handler *handler, struct dentry *unused, } static int -user_set(const struct xattr_handler *handler, struct dentry *unused, +user_set(const struct xattr_handler *handler, struct user_namespace *mnt_userns, + struct dentry *unused, struct inode *inode, const char *name, const void *buffer, size_t size, int flags) { diff --git a/fs/ubifs/xattr.c b/fs/ubifs/xattr.c index a0b9b349efe6..8f4135c22ab6 100644 --- a/fs/ubifs/xattr.c +++ b/fs/ubifs/xattr.c @@ -681,6 +681,7 @@ static int xattr_get(const struct xattr_handler *handler, } static int xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/fs/xattr.c b/fs/xattr.c index fcc79c2a1ea1..ff9ffe77a4b2 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -174,7 +174,7 @@ __vfs_setxattr(struct dentry *dentry, struct inode *inode, const char *name, return -EOPNOTSUPP; if (size == 0) value = ""; /* empty EA, do not remove */ - return handler->set(handler, dentry, inode, name, value, size, flags); + return handler->set(handler, &init_user_ns, dentry, inode, name, value, size, flags); } EXPORT_SYMBOL(__vfs_setxattr); @@ -438,7 +438,7 @@ __vfs_removexattr(struct dentry *dentry, const char *name) return PTR_ERR(handler); if (!handler->set) return -EOPNOTSUPP; - return handler->set(handler, dentry, inode, name, NULL, 0, XATTR_REPLACE); + return handler->set(handler, &init_user_ns, dentry, inode, name, NULL, 0, XATTR_REPLACE); } EXPORT_SYMBOL(__vfs_removexattr); @@ -536,9 +536,9 @@ setxattr(struct dentry *d, const char __user *name, const void __user *value, } if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) || (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0)) - posix_acl_fix_xattr_from_user(kvalue, size); + posix_acl_fix_xattr_from_user(&init_user_ns, kvalue, size); else if (strcmp(kname, XATTR_NAME_CAPS) == 0) { - error = cap_convert_nscap(d, &kvalue, size); + error = cap_convert_nscap(&init_user_ns, d, &kvalue, size); if (error < 0) goto out; size = error; @@ -636,7 +636,7 @@ getxattr(struct dentry *d, const char __user *name, void __user *value, if (error > 0) { if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) || (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0)) - posix_acl_fix_xattr_to_user(kvalue, error); + posix_acl_fix_xattr_to_user(&init_user_ns, kvalue, error); if (size && copy_to_user(value, kvalue, error)) error = -EFAULT; } else if (error == -ERANGE && size >= XATTR_SIZE_MAX) { diff --git a/fs/xfs/xfs_acl.c b/fs/xfs/xfs_acl.c index c544951a0c07..2e9d2d2878ce 100644 --- a/fs/xfs/xfs_acl.c +++ b/fs/xfs/xfs_acl.c @@ -244,7 +244,7 @@ xfs_set_acl(struct inode *inode, struct posix_acl *acl, int type) return error; if (type == ACL_TYPE_ACCESS) { - error = posix_acl_update_mode(inode, &mode, &acl); + error = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); if (error) return error; set_mode = true; diff --git a/fs/xfs/xfs_iops.c b/fs/xfs/xfs_iops.c index b1be836cf35e..c5f0db50c00f 100644 --- a/fs/xfs/xfs_iops.c +++ b/fs/xfs/xfs_iops.c @@ -810,7 +810,7 @@ xfs_setattr_nonsize( * Posix ACL code seems to care about this issue either. */ if ((mask & ATTR_MODE) && !(flags & XFS_ATTR_NOACL)) { - error = posix_acl_chmod(inode, inode->i_mode); + error = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); if (error) return error; } diff --git a/fs/xfs/xfs_xattr.c b/fs/xfs/xfs_xattr.c index bca48b308c02..4ea08dd47826 100644 --- a/fs/xfs/xfs_xattr.c +++ b/fs/xfs/xfs_xattr.c @@ -38,7 +38,8 @@ xfs_xattr_get(const struct xattr_handler *handler, struct dentry *unused, } static int -xfs_xattr_set(const struct xattr_handler *handler, struct dentry *unused, +xfs_xattr_set(const struct xattr_handler *handler, struct user_namespace *mnt_userns, + struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) { diff --git a/include/linux/capability.h b/include/linux/capability.h index b810392a5fba..61e632677358 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -273,6 +273,7 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns) /* audit system wants to get cap info from files as well */ extern int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps); -extern int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size); +extern int cap_convert_nscap(struct user_namespace *mnt_userns, + struct dentry *dentry, void **ivalue, size_t size); #endif /* !_LINUX_CAPABILITY_H */ diff --git a/include/linux/posix_acl.h b/include/linux/posix_acl.h index 8276baefed13..50e266fdaaef 100644 --- a/include/linux/posix_acl.h +++ b/include/linux/posix_acl.h @@ -71,13 +71,13 @@ extern int __posix_acl_create(struct posix_acl **, gfp_t, umode_t *); extern int __posix_acl_chmod(struct posix_acl **, gfp_t, umode_t); extern struct posix_acl *get_posix_acl(struct inode *, int); -extern int set_posix_acl(struct inode *, int, struct posix_acl *); +extern int set_posix_acl(struct user_namespace *, struct inode *, int, struct posix_acl *); #ifdef CONFIG_FS_POSIX_ACL -extern int posix_acl_chmod(struct inode *, umode_t); +extern int posix_acl_chmod(struct user_namespace *, struct inode *, umode_t); extern int posix_acl_create(struct inode *, umode_t *, struct posix_acl **, struct posix_acl **); -extern int posix_acl_update_mode(struct inode *, umode_t *, struct posix_acl **); +extern int posix_acl_update_mode(struct user_namespace *, struct inode *, umode_t *, struct posix_acl **); extern int simple_set_acl(struct inode *, struct posix_acl *, int); extern int simple_acl_create(struct inode *, struct inode *); @@ -94,7 +94,8 @@ static inline void cache_no_acl(struct inode *inode) inode->i_default_acl = NULL; } #else -static inline int posix_acl_chmod(struct inode *inode, umode_t mode) +static inline int posix_acl_chmod(struct user_namespace *mnt_userns, + struct inode *inode, umode_t mode) { return 0; } diff --git a/include/linux/posix_acl_xattr.h b/include/linux/posix_acl_xattr.h index 2387709991b5..060e8d203181 100644 --- a/include/linux/posix_acl_xattr.h +++ b/include/linux/posix_acl_xattr.h @@ -33,13 +33,17 @@ posix_acl_xattr_count(size_t size) } #ifdef CONFIG_FS_POSIX_ACL -void posix_acl_fix_xattr_from_user(void *value, size_t size); -void posix_acl_fix_xattr_to_user(void *value, size_t size); +void posix_acl_fix_xattr_from_user(struct user_namespace *mnt_userns, + void *value, size_t size); +void posix_acl_fix_xattr_to_user(struct user_namespace *mnt_userns, + void *value, size_t size); #else -static inline void posix_acl_fix_xattr_from_user(void *value, size_t size) +static inline void posix_acl_fix_xattr_from_user(struct user_namespace *mnt_userns, + void *value, size_t size) { } -static inline void posix_acl_fix_xattr_to_user(void *value, size_t size) +static inline void posix_acl_fix_xattr_to_user(struct user_namespace *mnt_userns, + void *value, size_t size) { } #endif diff --git a/include/linux/xattr.h b/include/linux/xattr.h index 10b4dc2709f0..d3c7ad273392 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -34,9 +34,9 @@ struct xattr_handler { int (*get)(const struct xattr_handler *, struct dentry *dentry, struct inode *inode, const char *name, void *buffer, size_t size); - int (*set)(const struct xattr_handler *, struct dentry *dentry, - struct inode *inode, const char *name, const void *buffer, - size_t size, int flags); + int (*set)(const struct xattr_handler *, struct user_namespace *mnt_userns, + struct dentry *dentry, struct inode *inode, const char *name, + const void *buffer, size_t size, int flags); }; const char *xattr_full_name(const struct xattr_handler *, const char *); diff --git a/mm/shmem.c b/mm/shmem.c index 95d2ebcfc95c..da36ac107128 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1143,7 +1143,7 @@ static int shmem_setattr(struct dentry *dentry, struct iattr *attr) setattr_copy(&init_user_ns, inode, attr); if (attr->ia_valid & ATTR_MODE) - error = posix_acl_chmod(inode, inode->i_mode); + error = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); return error; } @@ -3278,6 +3278,7 @@ static int shmem_xattr_handler_get(const struct xattr_handler *handler, } static int shmem_xattr_handler_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *unused, struct inode *inode, const char *name, const void *value, size_t size, int flags) diff --git a/net/socket.c b/net/socket.c index 6e6cccc2104f..f2e401b4665e 100644 --- a/net/socket.c +++ b/net/socket.c @@ -334,6 +334,7 @@ static const struct xattr_handler sockfs_xattr_handler = { }; static int sockfs_security_xattr_set(const struct xattr_handler *handler, + struct user_namespace *mnt_userns, struct dentry *dentry, struct inode *inode, const char *suffix, const void *value, size_t size, int flags) diff --git a/security/commoncap.c b/security/commoncap.c index 4cd2bdfd0a8b..ba53eb26c1dc 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -450,16 +450,34 @@ int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer, return size; } +/** + * rootid_from_xattr - translate root uid of vfs caps + * + * @value: vfs caps value which may be modified by this function + * @size: size of @ivalue + * @task_ns: user namespace of the caller + * @mnt_userns: user namespace of the mount the inode was found from + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * + */ static kuid_t rootid_from_xattr(const void *value, size_t size, - struct user_namespace *task_ns) + struct user_namespace *task_ns, + struct user_namespace *mnt_userns) { const struct vfs_ns_cap_data *nscap = value; + kuid_t rootkid; uid_t rootid = 0; if (size == XATTR_CAPS_SZ_3) rootid = le32_to_cpu(nscap->rootid); - return make_kuid(task_ns, rootid); + rootkid = make_kuid(task_ns, rootid); + return kuid_from_mnt(mnt_userns, rootkid); } static bool validheader(size_t size, const struct vfs_cap_data *cap) @@ -467,13 +485,27 @@ static bool validheader(size_t size, const struct vfs_cap_data *cap) return is_v2header(size, cap) || is_v3header(size, cap); } -/* +/** + * cap_convert_nscap - check vfs caps + * + * @mnt_userns: user namespace of the mount the inode was found from + * @dentry: used to retrieve inode to check permissions on + * @ivalue: vfs caps value which may be modified by this function + * @size: size of @ivalue + * * User requested a write of security.capability. If needed, update the * xattr to change from v2 to v3, or to fixup the v3 rootid. * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * If all is ok, we return the new size, on error return < 0. */ -int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size) +int cap_convert_nscap(struct user_namespace *mnt_userns, struct dentry *dentry, + void **ivalue, size_t size) { struct vfs_ns_cap_data *nscap; uid_t nsrootid; @@ -489,14 +521,14 @@ int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size) return -EINVAL; if (!validheader(size, cap)) return -EINVAL; - if (!capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_SETFCAP)) + if (!capable_wrt_inode_uidgid(mnt_userns, inode, CAP_SETFCAP)) return -EPERM; - if (size == XATTR_CAPS_SZ_2) + if (size == XATTR_CAPS_SZ_2 && (mnt_userns == &init_user_ns)) if (ns_capable(inode->i_sb->s_user_ns, CAP_SETFCAP)) /* user is privileged, just write the v2 */ return size; - rootid = rootid_from_xattr(*ivalue, size, task_ns); + rootid = rootid_from_xattr(*ivalue, size, task_ns, mnt_userns); if (!uid_valid(rootid)) return -EINVAL; @@ -520,6 +552,7 @@ int cap_convert_nscap(struct dentry *dentry, void **ivalue, size_t size) *ivalue = nscap; return newsize; } +EXPORT_SYMBOL(cap_convert_nscap); /* * Calculate the new process capability sets from the capability sets attached From patchwork Thu Dec 3 23:57:11 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950113 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2853EC35296 for ; Fri, 4 Dec 2020 00:03:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 07443224D4 for ; Fri, 4 Dec 2020 00:03:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389453AbgLDACl (ORCPT ); Thu, 3 Dec 2020 19:02:41 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41790 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388545AbgLDACf (ORCPT ); Thu, 3 Dec 2020 19:02:35 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXd-0007ka-Tv; Fri, 04 Dec 2020 00:01:42 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Tycho Andersen , Christoph Hellwig , Christian Brauner Subject: [PATCH v4 15/40] xattr: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:11 +0100 Message-Id: <20201203235736.3528991-16-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org From: Tycho Andersen When interacting with extended attributes the vfs verifies that the caller is privileged over the inode with which the extended attribute is associated. For posix access and posix default extended attributes a uid or gid can be stored on-disk. Let the functions handle posix extended attributes on idmapped mounts. If the inode is accessed through an idmapped mount we need to map it according to the mount's user namespace. Afterwards the checks are identical to non-idmapped mounts. This has no effect for e.g. security xattrs since they don't store uids or gids and don't perform permission checks on them like posix acls do. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Tycho Andersen Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/cachefiles/xattr.c | 16 ++-- fs/ecryptfs/crypto.c | 4 +- fs/ecryptfs/inode.c | 4 +- fs/ecryptfs/mmap.c | 4 +- fs/nfsd/vfs.c | 12 +-- fs/overlayfs/copy_up.c | 12 +-- fs/overlayfs/dir.c | 2 +- fs/overlayfs/inode.c | 8 +- fs/overlayfs/overlayfs.h | 6 +- fs/overlayfs/super.c | 4 +- fs/xattr.c | 120 +++++++++++++++----------- include/linux/xattr.h | 24 ++++-- security/apparmor/domain.c | 4 +- security/commoncap.c | 6 +- security/integrity/evm/evm_crypto.c | 11 +-- security/integrity/evm/evm_main.c | 4 +- security/integrity/ima/ima_appraise.c | 8 +- security/selinux/hooks.c | 2 +- security/smack/smack_lsm.c | 4 +- 19 files changed, 143 insertions(+), 112 deletions(-) diff --git a/fs/cachefiles/xattr.c b/fs/cachefiles/xattr.c index 72e42438f3d7..3b6a3f1610f4 100644 --- a/fs/cachefiles/xattr.c +++ b/fs/cachefiles/xattr.c @@ -39,7 +39,7 @@ int cachefiles_check_object_type(struct cachefiles_object *object) _enter("%p{%s}", object, type); /* attempt to install a type label directly */ - ret = vfs_setxattr(dentry, cachefiles_xattr_cache, type, 2, + ret = vfs_setxattr(&init_user_ns, dentry, cachefiles_xattr_cache, type, 2, XATTR_CREATE); if (ret == 0) { _debug("SET"); /* we succeeded */ @@ -54,7 +54,7 @@ int cachefiles_check_object_type(struct cachefiles_object *object) } /* read the current type label */ - ret = vfs_getxattr(dentry, cachefiles_xattr_cache, xtype, 3); + ret = vfs_getxattr(&init_user_ns, dentry, cachefiles_xattr_cache, xtype, 3); if (ret < 0) { if (ret == -ERANGE) goto bad_type_length; @@ -110,7 +110,7 @@ int cachefiles_set_object_xattr(struct cachefiles_object *object, _debug("SET #%u", auxdata->len); clear_bit(FSCACHE_COOKIE_AUX_UPDATED, &object->fscache.cookie->flags); - ret = vfs_setxattr(dentry, cachefiles_xattr_cache, + ret = vfs_setxattr(&init_user_ns, dentry, cachefiles_xattr_cache, &auxdata->type, auxdata->len, XATTR_CREATE); if (ret < 0 && ret != -ENOMEM) @@ -140,7 +140,7 @@ int cachefiles_update_object_xattr(struct cachefiles_object *object, _debug("SET #%u", auxdata->len); clear_bit(FSCACHE_COOKIE_AUX_UPDATED, &object->fscache.cookie->flags); - ret = vfs_setxattr(dentry, cachefiles_xattr_cache, + ret = vfs_setxattr(&init_user_ns, dentry, cachefiles_xattr_cache, &auxdata->type, auxdata->len, XATTR_REPLACE); if (ret < 0 && ret != -ENOMEM) @@ -171,7 +171,7 @@ int cachefiles_check_auxdata(struct cachefiles_object *object) if (!auxbuf) return -ENOMEM; - xlen = vfs_getxattr(dentry, cachefiles_xattr_cache, + xlen = vfs_getxattr(&init_user_ns, dentry, cachefiles_xattr_cache, &auxbuf->type, 512 + 1); ret = -ESTALE; if (xlen < 1 || @@ -213,7 +213,7 @@ int cachefiles_check_object_xattr(struct cachefiles_object *object, } /* read the current type label */ - ret = vfs_getxattr(dentry, cachefiles_xattr_cache, + ret = vfs_getxattr(&init_user_ns, dentry, cachefiles_xattr_cache, &auxbuf->type, 512 + 1); if (ret < 0) { if (ret == -ENODATA) @@ -270,7 +270,7 @@ int cachefiles_check_object_xattr(struct cachefiles_object *object, } /* update the current label */ - ret = vfs_setxattr(dentry, cachefiles_xattr_cache, + ret = vfs_setxattr(&init_user_ns, dentry, cachefiles_xattr_cache, &auxdata->type, auxdata->len, XATTR_REPLACE); if (ret < 0) { @@ -309,7 +309,7 @@ int cachefiles_remove_object_xattr(struct cachefiles_cache *cache, { int ret; - ret = vfs_removexattr(dentry, cachefiles_xattr_cache); + ret = vfs_removexattr(&init_user_ns, dentry, cachefiles_xattr_cache); if (ret < 0) { if (ret == -ENOENT || ret == -ENODATA) ret = 0; diff --git a/fs/ecryptfs/crypto.c b/fs/ecryptfs/crypto.c index 0681540c48d9..943e523f4c9d 100644 --- a/fs/ecryptfs/crypto.c +++ b/fs/ecryptfs/crypto.c @@ -1110,8 +1110,8 @@ ecryptfs_write_metadata_to_xattr(struct dentry *ecryptfs_dentry, } inode_lock(lower_inode); - rc = __vfs_setxattr(lower_dentry, lower_inode, ECRYPTFS_XATTR_NAME, - page_virt, size, 0); + rc = __vfs_setxattr(&init_user_ns, lower_dentry, lower_inode, + ECRYPTFS_XATTR_NAME, page_virt, size, 0); if (!rc && ecryptfs_inode) fsstack_copy_attr_all(ecryptfs_inode, lower_inode); inode_unlock(lower_inode); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index fa794c98c395..7de9fda25dad 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -1022,7 +1022,7 @@ ecryptfs_setxattr(struct dentry *dentry, struct inode *inode, rc = -EOPNOTSUPP; goto out; } - rc = vfs_setxattr(lower_dentry, name, value, size, flags); + rc = vfs_setxattr(&init_user_ns, lower_dentry, name, value, size, flags); if (!rc && inode) fsstack_copy_attr_all(inode, d_inode(lower_dentry)); out: @@ -1087,7 +1087,7 @@ static int ecryptfs_removexattr(struct dentry *dentry, struct inode *inode, goto out; } inode_lock(lower_inode); - rc = __vfs_removexattr(lower_dentry, name); + rc = __vfs_removexattr(&init_user_ns, lower_dentry, name); inode_unlock(lower_inode); out: return rc; diff --git a/fs/ecryptfs/mmap.c b/fs/ecryptfs/mmap.c index 019572c6b39a..2f333a40ff4d 100644 --- a/fs/ecryptfs/mmap.c +++ b/fs/ecryptfs/mmap.c @@ -426,8 +426,8 @@ static int ecryptfs_write_inode_size_to_xattr(struct inode *ecryptfs_inode) if (size < 0) size = 8; put_unaligned_be64(i_size_read(ecryptfs_inode), xattr_virt); - rc = __vfs_setxattr(lower_dentry, lower_inode, ECRYPTFS_XATTR_NAME, - xattr_virt, size, 0); + rc = __vfs_setxattr(&init_user_ns, lower_dentry, lower_inode, + ECRYPTFS_XATTR_NAME, xattr_virt, size, 0); inode_unlock(lower_inode); if (rc) printk(KERN_ERR "Error whilst attempting to write inode size " diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index e9b1452b505e..9a0e0e5b34f5 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -499,7 +499,7 @@ int nfsd4_is_junction(struct dentry *dentry) return 0; if (!(inode->i_mode & S_ISVTX)) return 0; - if (vfs_getxattr(dentry, NFSD_JUNCTION_XATTR_NAME, NULL, 0) <= 0) + if (vfs_getxattr(&init_user_ns, dentry, NFSD_JUNCTION_XATTR_NAME, NULL, 0) <= 0) return 0; return 1; } @@ -2138,7 +2138,7 @@ nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name, inode_lock_shared(inode); - len = vfs_getxattr(dentry, name, NULL, 0); + len = vfs_getxattr(&init_user_ns, dentry, name, NULL, 0); /* * Zero-length attribute, just return. @@ -2165,7 +2165,7 @@ nfsd_getxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name, goto out; } - len = vfs_getxattr(dentry, name, buf, len); + len = vfs_getxattr(&init_user_ns, dentry, name, buf, len); if (len <= 0) { kvfree(buf); buf = NULL; @@ -2272,7 +2272,7 @@ nfsd_removexattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name) fh_lock(fhp); - ret = __vfs_removexattr_locked(fhp->fh_dentry, name, NULL); + ret = __vfs_removexattr_locked(&init_user_ns, fhp->fh_dentry, name, NULL); fh_unlock(fhp); fh_drop_write(fhp); @@ -2296,8 +2296,8 @@ nfsd_setxattr(struct svc_rqst *rqstp, struct svc_fh *fhp, char *name, return nfserrno(ret); fh_lock(fhp); - ret = __vfs_setxattr_locked(fhp->fh_dentry, name, buf, len, flags, - NULL); + ret = __vfs_setxattr_locked(&init_user_ns, fhp->fh_dentry, name, buf, + len, flags, NULL); fh_unlock(fhp); fh_drop_write(fhp); diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index b972a58f1836..0ad1c47e2ca2 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -85,9 +85,9 @@ int ovl_copy_xattr(struct super_block *sb, struct dentry *old, if (ovl_is_private_xattr(sb, name)) continue; retry: - size = vfs_getxattr(old, name, value, value_size); + size = vfs_getxattr(&init_user_ns, old, name, value, value_size); if (size == -ERANGE) - size = vfs_getxattr(old, name, NULL, 0); + size = vfs_getxattr(&init_user_ns, old, name, NULL, 0); if (size < 0) { error = size; @@ -114,7 +114,7 @@ int ovl_copy_xattr(struct super_block *sb, struct dentry *old, error = 0; continue; /* Discard */ } - error = vfs_setxattr(new, name, value, size, 0); + error = vfs_setxattr(&init_user_ns, new, name, value, size, 0); if (error) { if (error != -EOPNOTSUPP || ovl_must_copy_xattr(name)) break; @@ -791,7 +791,7 @@ static ssize_t ovl_getxattr(struct dentry *dentry, char *name, char **value) ssize_t res; char *buf; - res = vfs_getxattr(dentry, name, NULL, 0); + res = vfs_getxattr(&init_user_ns, dentry, name, NULL, 0); if (res == -ENODATA || res == -EOPNOTSUPP) res = 0; @@ -800,7 +800,7 @@ static ssize_t ovl_getxattr(struct dentry *dentry, char *name, char **value) if (!buf) return -ENOMEM; - res = vfs_getxattr(dentry, name, buf, res); + res = vfs_getxattr(&init_user_ns, dentry, name, buf, res); if (res < 0) kfree(buf); else @@ -842,7 +842,7 @@ static int ovl_copy_up_meta_inode_data(struct ovl_copy_up_ctx *c) * don't want that to happen for normal copy-up operation. */ if (capability) { - err = vfs_setxattr(upperpath.dentry, XATTR_NAME_CAPS, + err = vfs_setxattr(&init_user_ns, upperpath.dentry, XATTR_NAME_CAPS, capability, cap_size, 0); if (err) goto out_free; diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 29840820a46c..d75c96cb18c3 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -449,7 +449,7 @@ static int ovl_set_upper_acl(struct dentry *upperdentry, const char *name, if (err < 0) goto out_free; - err = vfs_setxattr(upperdentry, name, buffer, size, XATTR_CREATE); + err = vfs_setxattr(&init_user_ns, upperdentry, name, buffer, size, XATTR_CREATE); out_free: kfree(buffer); return err; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 7d1a455e4177..2835f33f386a 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -346,7 +346,7 @@ int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char *name, goto out; if (!value && !upperdentry) { - err = vfs_getxattr(realdentry, name, NULL, 0); + err = vfs_getxattr(&init_user_ns, realdentry, name, NULL, 0); if (err < 0) goto out_drop_write; } @@ -361,10 +361,10 @@ int ovl_xattr_set(struct dentry *dentry, struct inode *inode, const char *name, old_cred = ovl_override_creds(dentry->d_sb); if (value) - err = vfs_setxattr(realdentry, name, value, size, flags); + err = vfs_setxattr(&init_user_ns, realdentry, name, value, size, flags); else { WARN_ON(flags != XATTR_REPLACE); - err = vfs_removexattr(realdentry, name); + err = vfs_removexattr(&init_user_ns, realdentry, name); } revert_creds(old_cred); @@ -386,7 +386,7 @@ int ovl_xattr_get(struct dentry *dentry, struct inode *inode, const char *name, ovl_i_dentry_upper(inode) ?: ovl_dentry_lower(dentry); old_cred = ovl_override_creds(dentry->d_sb); - res = vfs_getxattr(realdentry, name, value, size); + res = vfs_getxattr(&init_user_ns, realdentry, name, value, size); revert_creds(old_cred); return res; } diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index f8880aa2ba0e..bf26fb3fa2c1 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -184,7 +184,7 @@ static inline ssize_t ovl_do_getxattr(struct ovl_fs *ofs, struct dentry *dentry, size_t size) { const char *name = ovl_xattr(ofs, ox); - return vfs_getxattr(dentry, name, value, size); + return vfs_getxattr(&init_user_ns, dentry, name, value, size); } static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry, @@ -192,7 +192,7 @@ static inline int ovl_do_setxattr(struct ovl_fs *ofs, struct dentry *dentry, size_t size) { const char *name = ovl_xattr(ofs, ox); - int err = vfs_setxattr(dentry, name, value, size, 0); + int err = vfs_setxattr(&init_user_ns, dentry, name, value, size, 0); pr_debug("setxattr(%pd2, \"%s\", \"%*pE\", %zu, 0) = %i\n", dentry, name, min((int)size, 48), value, size, err); return err; @@ -202,7 +202,7 @@ static inline int ovl_do_removexattr(struct ovl_fs *ofs, struct dentry *dentry, enum ovl_xattr ox) { const char *name = ovl_xattr(ofs, ox); - int err = vfs_removexattr(dentry, name); + int err = vfs_removexattr(&init_user_ns, dentry, name); pr_debug("removexattr(%pd2, \"%s\") = %i\n", dentry, name, err); return err; } diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 54ee5fd6a4cc..bc295e695a23 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -749,11 +749,11 @@ static struct dentry *ovl_workdir_create(struct ovl_fs *ofs, * allowed as upper are limited to "normal" ones, where checking * for the above two errors is sufficient. */ - err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_DEFAULT); + err = vfs_removexattr(&init_user_ns, work, XATTR_NAME_POSIX_ACL_DEFAULT); if (err && err != -ENODATA && err != -EOPNOTSUPP) goto out_dput; - err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_ACCESS); + err = vfs_removexattr(&init_user_ns, work, XATTR_NAME_POSIX_ACL_ACCESS); if (err && err != -ENODATA && err != -EOPNOTSUPP) goto out_dput; diff --git a/fs/xattr.c b/fs/xattr.c index ff9ffe77a4b2..8ce654e56b3a 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -83,7 +83,8 @@ xattr_resolve_name(struct inode *inode, const char **name) * because different namespaces have very different rules. */ static int -xattr_permission(struct inode *inode, const char *name, int mask) +xattr_permission(struct user_namespace *mnt_userns, struct inode *inode, + const char *name, int mask) { /* * We can never set or remove an extended attribute on a read-only @@ -127,11 +128,11 @@ xattr_permission(struct inode *inode, const char *name, int mask) if (!S_ISREG(inode->i_mode) && !S_ISDIR(inode->i_mode)) return (mask & MAY_WRITE) ? -EPERM : -ENODATA; if (S_ISDIR(inode->i_mode) && (inode->i_mode & S_ISVTX) && - (mask & MAY_WRITE) && !inode_owner_or_capable(&init_user_ns, inode)) + (mask & MAY_WRITE) && !inode_owner_or_capable(mnt_userns, inode)) return -EPERM; } - return inode_permission(&init_user_ns, inode, mask); + return inode_permission(mnt_userns, inode, mask); } /* @@ -162,8 +163,9 @@ xattr_supported_namespace(struct inode *inode, const char *prefix) EXPORT_SYMBOL(xattr_supported_namespace); int -__vfs_setxattr(struct dentry *dentry, struct inode *inode, const char *name, - const void *value, size_t size, int flags) +__vfs_setxattr(struct user_namespace *mnt_userns, struct dentry *dentry, + struct inode *inode, const char *name, const void *value, + size_t size, int flags) { const struct xattr_handler *handler; @@ -174,7 +176,7 @@ __vfs_setxattr(struct dentry *dentry, struct inode *inode, const char *name, return -EOPNOTSUPP; if (size == 0) value = ""; /* empty EA, do not remove */ - return handler->set(handler, &init_user_ns, dentry, inode, name, value, size, flags); + return handler->set(handler, mnt_userns, dentry, inode, name, value, size, flags); } EXPORT_SYMBOL(__vfs_setxattr); @@ -182,6 +184,7 @@ EXPORT_SYMBOL(__vfs_setxattr); * __vfs_setxattr_noperm - perform setxattr operation without performing * permission checks. * + * @mnt_userns - user namespace of the mount the inode was found from * @dentry - object to perform setxattr on * @name - xattr name to set * @value - value to set @name to @@ -194,8 +197,9 @@ EXPORT_SYMBOL(__vfs_setxattr); * is executed. It also assumes that the caller will make the appropriate * permission checks. */ -int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, - const void *value, size_t size, int flags) +int __vfs_setxattr_noperm(struct user_namespace *mnt_userns, struct dentry *dentry, + const char *name, const void *value, size_t size, + int flags) { struct inode *inode = dentry->d_inode; int error = -EAGAIN; @@ -205,7 +209,7 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, if (issec) inode->i_flags &= ~S_NOSEC; if (inode->i_opflags & IOP_XATTR) { - error = __vfs_setxattr(dentry, inode, name, value, size, flags); + error = __vfs_setxattr(mnt_userns, dentry, inode, name, value, size, flags); if (!error) { fsnotify_xattr(dentry); security_inode_post_setxattr(dentry, name, value, @@ -244,14 +248,14 @@ int __vfs_setxattr_noperm(struct dentry *dentry, const char *name, * a delegation was broken on, NULL if none. */ int -__vfs_setxattr_locked(struct dentry *dentry, const char *name, - const void *value, size_t size, int flags, - struct inode **delegated_inode) +__vfs_setxattr_locked(struct user_namespace *mnt_userns, struct dentry *dentry, + const char *name, const void *value, size_t size, + int flags, struct inode **delegated_inode) { struct inode *inode = dentry->d_inode; int error; - error = xattr_permission(inode, name, MAY_WRITE); + error = xattr_permission(mnt_userns, inode, name, MAY_WRITE); if (error) return error; @@ -263,7 +267,7 @@ __vfs_setxattr_locked(struct dentry *dentry, const char *name, if (error) goto out; - error = __vfs_setxattr_noperm(dentry, name, value, size, flags); + error = __vfs_setxattr_noperm(mnt_userns, dentry, name, value, size, flags); out: return error; @@ -271,8 +275,8 @@ __vfs_setxattr_locked(struct dentry *dentry, const char *name, EXPORT_SYMBOL_GPL(__vfs_setxattr_locked); int -vfs_setxattr(struct dentry *dentry, const char *name, const void *value, - size_t size, int flags) +vfs_setxattr(struct user_namespace *mnt_userns, struct dentry *dentry, + const char *name, const void *value, size_t size, int flags) { struct inode *inode = dentry->d_inode; struct inode *delegated_inode = NULL; @@ -280,8 +284,8 @@ vfs_setxattr(struct dentry *dentry, const char *name, const void *value, retry_deleg: inode_lock(inode); - error = __vfs_setxattr_locked(dentry, name, value, size, flags, - &delegated_inode); + error = __vfs_setxattr_locked(mnt_userns, dentry, name, value, size, flags, + &delegated_inode); inode_unlock(inode); if (delegated_inode) { @@ -328,15 +332,16 @@ xattr_getsecurity(struct inode *inode, const char *name, void *value, * Returns the result of alloc, if failed, or the getxattr operation. */ ssize_t -vfs_getxattr_alloc(struct dentry *dentry, const char *name, char **xattr_value, - size_t xattr_size, gfp_t flags) +vfs_getxattr_alloc(struct user_namespace *mnt_userns, struct dentry *dentry, + const char *name, char **xattr_value, size_t xattr_size, + gfp_t flags) { const struct xattr_handler *handler; struct inode *inode = dentry->d_inode; char *value = *xattr_value; int error; - error = xattr_permission(inode, name, MAY_READ); + error = xattr_permission(mnt_userns, inode, name, MAY_READ); if (error) return error; @@ -377,12 +382,13 @@ __vfs_getxattr(struct dentry *dentry, struct inode *inode, const char *name, EXPORT_SYMBOL(__vfs_getxattr); ssize_t -vfs_getxattr(struct dentry *dentry, const char *name, void *value, size_t size) +vfs_getxattr(struct user_namespace *mnt_userns, struct dentry *dentry, + const char *name, void *value, size_t size) { struct inode *inode = dentry->d_inode; int error; - error = xattr_permission(inode, name, MAY_READ); + error = xattr_permission(mnt_userns, inode, name, MAY_READ); if (error) return error; @@ -428,7 +434,7 @@ vfs_listxattr(struct dentry *dentry, char *list, size_t size) EXPORT_SYMBOL_GPL(vfs_listxattr); int -__vfs_removexattr(struct dentry *dentry, const char *name) +__vfs_removexattr(struct user_namespace *mnt_userns, struct dentry *dentry, const char *name) { struct inode *inode = d_inode(dentry); const struct xattr_handler *handler; @@ -438,7 +444,7 @@ __vfs_removexattr(struct dentry *dentry, const char *name) return PTR_ERR(handler); if (!handler->set) return -EOPNOTSUPP; - return handler->set(handler, &init_user_ns, dentry, inode, name, NULL, 0, XATTR_REPLACE); + return handler->set(handler, mnt_userns, dentry, inode, name, NULL, 0, XATTR_REPLACE); } EXPORT_SYMBOL(__vfs_removexattr); @@ -452,13 +458,13 @@ EXPORT_SYMBOL(__vfs_removexattr); * a delegation was broken on, NULL if none. */ int -__vfs_removexattr_locked(struct dentry *dentry, const char *name, - struct inode **delegated_inode) +__vfs_removexattr_locked(struct user_namespace *mnt_userns, struct dentry *dentry, + const char *name, struct inode **delegated_inode) { struct inode *inode = dentry->d_inode; int error; - error = xattr_permission(inode, name, MAY_WRITE); + error = xattr_permission(mnt_userns, inode, name, MAY_WRITE); if (error) return error; @@ -470,7 +476,7 @@ __vfs_removexattr_locked(struct dentry *dentry, const char *name, if (error) goto out; - error = __vfs_removexattr(dentry, name); + error = __vfs_removexattr(mnt_userns, dentry, name); if (!error) { fsnotify_xattr(dentry); @@ -483,7 +489,8 @@ __vfs_removexattr_locked(struct dentry *dentry, const char *name, EXPORT_SYMBOL_GPL(__vfs_removexattr_locked); int -vfs_removexattr(struct dentry *dentry, const char *name) +vfs_removexattr(struct user_namespace *mnt_userns, struct dentry *dentry, + const char *name) { struct inode *inode = dentry->d_inode; struct inode *delegated_inode = NULL; @@ -491,7 +498,7 @@ vfs_removexattr(struct dentry *dentry, const char *name) retry_deleg: inode_lock(inode); - error = __vfs_removexattr_locked(dentry, name, &delegated_inode); + error = __vfs_removexattr_locked(mnt_userns, dentry, name, &delegated_inode); inode_unlock(inode); if (delegated_inode) { @@ -508,8 +515,9 @@ EXPORT_SYMBOL_GPL(vfs_removexattr); * Extended attribute SET operations */ static long -setxattr(struct dentry *d, const char __user *name, const void __user *value, - size_t size, int flags) +setxattr(struct user_namespace *mnt_userns, struct dentry *d, + const char __user *name, const void __user *value, size_t size, + int flags) { int error; void *kvalue = NULL; @@ -536,16 +544,16 @@ setxattr(struct dentry *d, const char __user *name, const void __user *value, } if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) || (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0)) - posix_acl_fix_xattr_from_user(&init_user_ns, kvalue, size); + posix_acl_fix_xattr_from_user(mnt_userns, kvalue, size); else if (strcmp(kname, XATTR_NAME_CAPS) == 0) { - error = cap_convert_nscap(&init_user_ns, d, &kvalue, size); + error = cap_convert_nscap(mnt_userns, d, &kvalue, size); if (error < 0) goto out; size = error; } } - error = vfs_setxattr(d, kname, kvalue, size, flags); + error = vfs_setxattr(mnt_userns, d, kname, kvalue, size, flags); out: kvfree(kvalue); @@ -558,13 +566,17 @@ static int path_setxattr(const char __user *pathname, { struct path path; int error; + retry: error = user_path_at(AT_FDCWD, pathname, lookup_flags, &path); if (error) return error; error = mnt_want_write(path.mnt); if (!error) { - error = setxattr(path.dentry, name, value, size, flags); + struct user_namespace *mnt_userns; + + mnt_userns = mnt_user_ns(path.mnt); + error = setxattr(mnt_userns, path.dentry, name, value, size, flags); mnt_drop_write(path.mnt); } path_put(&path); @@ -600,7 +612,11 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name, audit_file(f.file); error = mnt_want_write_file(f.file); if (!error) { - error = setxattr(f.file->f_path.dentry, name, value, size, flags); + struct user_namespace *mnt_userns; + + mnt_userns = mnt_user_ns(f.file->f_path.mnt); + error = setxattr(mnt_userns, f.file->f_path.dentry, name, value, + size, flags); mnt_drop_write_file(f.file); } fdput(f); @@ -611,8 +627,8 @@ SYSCALL_DEFINE5(fsetxattr, int, fd, const char __user *, name, * Extended attribute GET operations */ static ssize_t -getxattr(struct dentry *d, const char __user *name, void __user *value, - size_t size) +getxattr(struct user_namespace *mnt_userns, struct dentry *d, + const char __user *name, void __user *value, size_t size) { ssize_t error; void *kvalue = NULL; @@ -632,11 +648,11 @@ getxattr(struct dentry *d, const char __user *name, void __user *value, return -ENOMEM; } - error = vfs_getxattr(d, kname, kvalue, size); + error = vfs_getxattr(mnt_userns, d, kname, kvalue, size); if (error > 0) { if ((strcmp(kname, XATTR_NAME_POSIX_ACL_ACCESS) == 0) || (strcmp(kname, XATTR_NAME_POSIX_ACL_DEFAULT) == 0)) - posix_acl_fix_xattr_to_user(&init_user_ns, kvalue, error); + posix_acl_fix_xattr_to_user(mnt_userns, kvalue, error); if (size && copy_to_user(value, kvalue, error)) error = -EFAULT; } else if (error == -ERANGE && size >= XATTR_SIZE_MAX) { @@ -654,13 +670,15 @@ static ssize_t path_getxattr(const char __user *pathname, const char __user *name, void __user *value, size_t size, unsigned int lookup_flags) { + struct user_namespace *mnt_userns; struct path path; ssize_t error; retry: error = user_path_at(AT_FDCWD, pathname, lookup_flags, &path); if (error) return error; - error = getxattr(path.dentry, name, value, size); + mnt_userns = mnt_user_ns(path.mnt); + error = getxattr(mnt_userns, path.dentry, name, value, size); path_put(&path); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -684,13 +702,15 @@ SYSCALL_DEFINE4(lgetxattr, const char __user *, pathname, SYSCALL_DEFINE4(fgetxattr, int, fd, const char __user *, name, void __user *, value, size_t, size) { + struct user_namespace *mnt_userns; struct fd f = fdget(fd); ssize_t error = -EBADF; if (!f.file) return error; audit_file(f.file); - error = getxattr(f.file->f_path.dentry, name, value, size); + mnt_userns = mnt_user_ns(f.file->f_path.mnt); + error = getxattr(mnt_userns, f.file->f_path.dentry, name, value, size); fdput(f); return error; } @@ -774,7 +794,7 @@ SYSCALL_DEFINE3(flistxattr, int, fd, char __user *, list, size_t, size) * Extended attribute REMOVE operations */ static long -removexattr(struct dentry *d, const char __user *name) +removexattr(struct user_namespace *mnt_userns, struct dentry *d, const char __user *name) { int error; char kname[XATTR_NAME_MAX + 1]; @@ -785,7 +805,7 @@ removexattr(struct dentry *d, const char __user *name) if (error < 0) return error; - return vfs_removexattr(d, kname); + return vfs_removexattr(mnt_userns, d, kname); } static int path_removexattr(const char __user *pathname, @@ -799,7 +819,9 @@ static int path_removexattr(const char __user *pathname, return error; error = mnt_want_write(path.mnt); if (!error) { - error = removexattr(path.dentry, name); + struct user_namespace *mnt_userns = mnt_user_ns(path.mnt); + + error = removexattr(mnt_userns, path.dentry, name); mnt_drop_write(path.mnt); } path_put(&path); @@ -832,7 +854,9 @@ SYSCALL_DEFINE2(fremovexattr, int, fd, const char __user *, name) audit_file(f.file); error = mnt_want_write_file(f.file); if (!error) { - error = removexattr(f.file->f_path.dentry, name); + struct user_namespace *mnt_userns = mnt_user_ns(f.file->f_path.mnt); + + error = removexattr(mnt_userns, f.file->f_path.dentry, name); mnt_drop_write_file(f.file); } fdput(f); diff --git a/include/linux/xattr.h b/include/linux/xattr.h index d3c7ad273392..26d490512db7 100644 --- a/include/linux/xattr.h +++ b/include/linux/xattr.h @@ -16,6 +16,7 @@ #include #include #include +#include #include struct inode; @@ -48,18 +49,23 @@ struct xattr { }; ssize_t __vfs_getxattr(struct dentry *, struct inode *, const char *, void *, size_t); -ssize_t vfs_getxattr(struct dentry *, const char *, void *, size_t); +ssize_t vfs_getxattr(struct user_namespace *, struct dentry *, const char *, void *, size_t); ssize_t vfs_listxattr(struct dentry *d, char *list, size_t size); -int __vfs_setxattr(struct dentry *, struct inode *, const char *, const void *, size_t, int); -int __vfs_setxattr_noperm(struct dentry *, const char *, const void *, size_t, int); -int __vfs_setxattr_locked(struct dentry *, const char *, const void *, size_t, int, struct inode **); -int vfs_setxattr(struct dentry *, const char *, const void *, size_t, int); -int __vfs_removexattr(struct dentry *, const char *); -int __vfs_removexattr_locked(struct dentry *, const char *, struct inode **); -int vfs_removexattr(struct dentry *, const char *); +int __vfs_setxattr(struct user_namespace *, struct dentry *, struct inode *, + const char *, const void *, size_t, int); +int __vfs_setxattr_noperm(struct user_namespace *, struct dentry *, + const char *, const void *, size_t, int); +int __vfs_setxattr_locked(struct user_namespace *, struct dentry *, + const char *, const void *, size_t, int, + struct inode **); +int vfs_setxattr(struct user_namespace *, struct dentry *, const char *, const void *, size_t, int); +int __vfs_removexattr(struct user_namespace *, struct dentry *, const char *); +int __vfs_removexattr_locked(struct user_namespace *, struct dentry *, const char *, struct inode **); +int vfs_removexattr(struct user_namespace *, struct dentry *, const char *); ssize_t generic_listxattr(struct dentry *dentry, char *buffer, size_t buffer_size); -ssize_t vfs_getxattr_alloc(struct dentry *dentry, const char *name, +ssize_t vfs_getxattr_alloc(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, char **xattr_value, size_t size, gfp_t flags); int xattr_supported_namespace(struct inode *inode, const char *prefix); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index f919ebd042fd..16f184bc48de 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -324,8 +324,8 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, d = bprm->file->f_path.dentry; for (i = 0; i < profile->xattr_count; i++) { - size = vfs_getxattr_alloc(d, profile->xattrs[i], &value, - value_size, GFP_KERNEL); + size = vfs_getxattr_alloc(&init_user_ns, d, profile->xattrs[i], + &value, value_size, GFP_KERNEL); if (size >= 0) { u32 perm; diff --git a/security/commoncap.c b/security/commoncap.c index ba53eb26c1dc..8e16ec0b3c4b 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -313,7 +313,7 @@ int cap_inode_killpriv(struct dentry *dentry) { int error; - error = __vfs_removexattr(dentry, XATTR_NAME_CAPS); + error = __vfs_removexattr(&init_user_ns, dentry, XATTR_NAME_CAPS); if (error == -EOPNOTSUPP) error = 0; return error; @@ -386,8 +386,8 @@ int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer, return -EINVAL; size = sizeof(struct vfs_ns_cap_data); - ret = (int) vfs_getxattr_alloc(dentry, XATTR_NAME_CAPS, - &tmpbuf, size, GFP_NOFS); + ret = (int)vfs_getxattr_alloc(&init_user_ns, dentry, XATTR_NAME_CAPS, + &tmpbuf, size, GFP_NOFS); dput(dentry); if (ret < 0) diff --git a/security/integrity/evm/evm_crypto.c b/security/integrity/evm/evm_crypto.c index 168c3b78ac47..f720f78cbbb1 100644 --- a/security/integrity/evm/evm_crypto.c +++ b/security/integrity/evm/evm_crypto.c @@ -222,7 +222,7 @@ static int evm_calc_hmac_or_hash(struct dentry *dentry, ima_present = true; continue; } - size = vfs_getxattr_alloc(dentry, xattr->name, + size = vfs_getxattr_alloc(&init_user_ns, dentry, xattr->name, &xattr_value, xattr_size, GFP_NOFS); if (size == -ENOMEM) { error = -ENOMEM; @@ -275,8 +275,8 @@ static int evm_is_immutable(struct dentry *dentry, struct inode *inode) return 1; /* Do this the hard way */ - rc = vfs_getxattr_alloc(dentry, XATTR_NAME_EVM, (char **)&xattr_data, 0, - GFP_NOFS); + rc = vfs_getxattr_alloc(&init_user_ns, dentry, XATTR_NAME_EVM, + (char **)&xattr_data, 0, GFP_NOFS); if (rc <= 0) { if (rc == -ENODATA) return 0; @@ -319,11 +319,12 @@ int evm_update_evmxattr(struct dentry *dentry, const char *xattr_name, xattr_value_len, &data); if (rc == 0) { data.hdr.xattr.sha1.type = EVM_XATTR_HMAC; - rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_EVM, + rc = __vfs_setxattr_noperm(&init_user_ns, dentry, + XATTR_NAME_EVM, &data.hdr.xattr.data[1], SHA1_DIGEST_SIZE + 1, 0); } else if (rc == -ENODATA && (inode->i_opflags & IOP_XATTR)) { - rc = __vfs_removexattr(dentry, XATTR_NAME_EVM); + rc = __vfs_removexattr(&init_user_ns, dentry, XATTR_NAME_EVM); } return rc; } diff --git a/security/integrity/evm/evm_main.c b/security/integrity/evm/evm_main.c index 76d19146d74b..0de367aaa2d3 100644 --- a/security/integrity/evm/evm_main.c +++ b/security/integrity/evm/evm_main.c @@ -146,8 +146,8 @@ static enum integrity_status evm_verify_hmac(struct dentry *dentry, /* if status is not PASS, try to check again - against -ENOMEM */ /* first need to know the sig type */ - rc = vfs_getxattr_alloc(dentry, XATTR_NAME_EVM, (char **)&xattr_data, 0, - GFP_NOFS); + rc = vfs_getxattr_alloc(&init_user_ns, dentry, XATTR_NAME_EVM, + (char **)&xattr_data, 0, GFP_NOFS); if (rc <= 0) { evm_status = INTEGRITY_FAIL; if (rc == -ENODATA) { diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 3dd8c2e4314e..3492f0b2da1c 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -89,7 +89,7 @@ static int ima_fix_xattr(struct dentry *dentry, iint->ima_hash->xattr.ng.type = IMA_XATTR_DIGEST_NG; iint->ima_hash->xattr.ng.algo = algo; } - rc = __vfs_setxattr_noperm(dentry, XATTR_NAME_IMA, + rc = __vfs_setxattr_noperm(&init_user_ns, dentry, XATTR_NAME_IMA, &iint->ima_hash->xattr.data[offset], (sizeof(iint->ima_hash->xattr) - offset) + iint->ima_hash->length, 0); @@ -210,8 +210,8 @@ int ima_read_xattr(struct dentry *dentry, { ssize_t ret; - ret = vfs_getxattr_alloc(dentry, XATTR_NAME_IMA, (char **)xattr_value, - 0, GFP_NOFS); + ret = vfs_getxattr_alloc(&init_user_ns, dentry, XATTR_NAME_IMA, + (char **)xattr_value, 0, GFP_NOFS); if (ret == -EOPNOTSUPP) ret = 0; return ret; @@ -515,7 +515,7 @@ void ima_inode_post_setattr(struct dentry *dentry) action = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); if (!action) - __vfs_removexattr(dentry, XATTR_NAME_IMA); + __vfs_removexattr(&init_user_ns, dentry, XATTR_NAME_IMA); iint = integrity_iint_find(inode); if (iint) { set_bit(IMA_CHANGE_ATTR, &iint->atomic_flags); diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 14a195fa55eb..3f42950feed0 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -6518,7 +6518,7 @@ static int selinux_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen */ static int selinux_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) { - return __vfs_setxattr_noperm(dentry, XATTR_NAME_SELINUX, ctx, ctxlen, 0); + return __vfs_setxattr_noperm(&init_user_ns, dentry, XATTR_NAME_SELINUX, ctx, ctxlen, 0); } static int selinux_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 5c90b9fa4d40..11003244b18b 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -3425,7 +3425,7 @@ static void smack_d_instantiate(struct dentry *opt_dentry, struct inode *inode) */ if (isp->smk_flags & SMK_INODE_CHANGED) { isp->smk_flags &= ~SMK_INODE_CHANGED; - rc = __vfs_setxattr(dp, inode, + rc = __vfs_setxattr(&init_user_ns, dp, inode, XATTR_NAME_SMACKTRANSMUTE, TRANS_TRUE, TRANS_TRUE_SIZE, 0); @@ -4603,7 +4603,7 @@ static int smack_inode_notifysecctx(struct inode *inode, void *ctx, u32 ctxlen) static int smack_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) { - return __vfs_setxattr_noperm(dentry, XATTR_NAME_SMACK, ctx, ctxlen, 0); + return __vfs_setxattr_noperm(&init_user_ns, dentry, XATTR_NAME_SMACK, ctx, ctxlen, 0); } static int smack_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) From patchwork Thu Dec 3 23:57:12 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950111 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 04967C3CFC1 for ; Fri, 4 Dec 2020 00:03:14 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CB27822224 for ; Fri, 4 Dec 2020 00:03:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388087AbgLDACm (ORCPT ); Thu, 3 Dec 2020 19:02:42 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41808 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388861AbgLDACg (ORCPT ); Thu, 3 Dec 2020 19:02:36 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXf-0007ka-O2; Fri, 04 Dec 2020 00:01:43 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 16/40] commoncap: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:12 +0100 Message-Id: <20201203235736.3528991-17-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org When interacting with user namespace and non-user namespace aware filesystem capabilities the vfs will perform various security checks to determine whether or not the filesystem capabilities can be used by the caller (e.g. during exec), or even whether they need to be removed. The main infrastructure for this resides in the capability codepaths but they are called through the LSM security infrastructure even though they are not technically an LSM or optional. This extends the existing security hooks security_inode_removexattr(), security_inode_killpriv(), security_inode_getsecurity() to pass down the mount's user namespace and makes them aware of idmapped mounts. In order to actually get filesystem capabilities from disk the capability infrastructure exposes the get_vfs_caps_from_disk() helper. For user namespace aware filesystem capabilities a root uid is stored alongside the capabilities. In order to determine whether the caller can make use of the filesystem capability or whether it needs to be ignored it is translated according to the superblock's user namespace. If it can be translated to uid 0 according to that id mapping the caller can use the filesystem capabilities stored on disk. If we are accessing the inode that holds the filesystem capabilities through an idmapped mount we need to map the root uid according to the mount's user namespace. Afterwards the checks are identical to non-idmapped mounts. Reading filesystem caps from disk enforces that the root uid associated with the filesystem capability must have a mapping in the superblock's user namespace and that the caller is either in the same user namespace or is a descendant of the superblock's user namespace. For filesystems that are mountable inside user namespace the caller can just mount the filesystem and won't usually need to idmap it. If they do want to idmap it they can create an idmapped mount and mark it with a user namespace they created and which is thus a descendant of s_user_ns. For filesystems that are not mountable inside user namespaces the descendant rule is trivially true because the s_user_ns will be the initial user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ - Paul Moore : - All audit logging is currently based on init_user_ns and will remain so until other work unrelated to this patchset has landed so always pass &init_user_ns in audit. /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/attr.c | 2 +- fs/xattr.c | 14 ++++---- include/linux/capability.h | 4 ++- include/linux/lsm_hook_defs.h | 15 +++++---- include/linux/lsm_hooks.h | 1 + include/linux/security.h | 44 +++++++++++++++--------- kernel/auditsc.c | 5 +-- security/commoncap.c | 63 ++++++++++++++++++++++++++--------- security/security.c | 25 ++++++++------ security/selinux/hooks.c | 20 ++++++----- security/smack/smack_lsm.c | 14 ++++---- 11 files changed, 136 insertions(+), 71 deletions(-) diff --git a/fs/attr.c b/fs/attr.c index 939402dea6f7..3ee0b1b0f207 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -143,7 +143,7 @@ int setattr_prepare(struct user_namespace *mnt_userns, struct dentry *dentry, if (ia_valid & ATTR_KILL_PRIV) { int error; - error = security_inode_killpriv(dentry); + error = security_inode_killpriv(mnt_userns, dentry); if (error) return error; } diff --git a/fs/xattr.c b/fs/xattr.c index 8ce654e56b3a..0d1bfeacfa23 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -259,7 +259,7 @@ __vfs_setxattr_locked(struct user_namespace *mnt_userns, struct dentry *dentry, if (error) return error; - error = security_inode_setxattr(dentry, name, value, size, flags); + error = security_inode_setxattr(mnt_userns, dentry, name, value, size, flags); if (error) goto out; @@ -298,18 +298,18 @@ vfs_setxattr(struct user_namespace *mnt_userns, struct dentry *dentry, EXPORT_SYMBOL_GPL(vfs_setxattr); static ssize_t -xattr_getsecurity(struct inode *inode, const char *name, void *value, - size_t size) +xattr_getsecurity(struct user_namespace *mnt_userns, struct inode *inode, + const char *name, void *value, size_t size) { void *buffer = NULL; ssize_t len; if (!value || !size) { - len = security_inode_getsecurity(inode, name, &buffer, false); + len = security_inode_getsecurity(mnt_userns, inode, name, &buffer, false); goto out_noalloc; } - len = security_inode_getsecurity(inode, name, &buffer, true); + len = security_inode_getsecurity(mnt_userns, inode, name, &buffer, true); if (len < 0) return len; if (size < len) { @@ -399,7 +399,7 @@ vfs_getxattr(struct user_namespace *mnt_userns, struct dentry *dentry, if (!strncmp(name, XATTR_SECURITY_PREFIX, XATTR_SECURITY_PREFIX_LEN)) { const char *suffix = name + XATTR_SECURITY_PREFIX_LEN; - int ret = xattr_getsecurity(inode, suffix, value, size); + int ret = xattr_getsecurity(mnt_userns, inode, suffix, value, size); /* * Only overwrite the return value if a security module * is actually active. @@ -468,7 +468,7 @@ __vfs_removexattr_locked(struct user_namespace *mnt_userns, struct dentry *dentr if (error) return error; - error = security_inode_removexattr(dentry, name); + error = security_inode_removexattr(mnt_userns, dentry, name); if (error) goto out; diff --git a/include/linux/capability.h b/include/linux/capability.h index 61e632677358..09a6a5ffb415 100644 --- a/include/linux/capability.h +++ b/include/linux/capability.h @@ -271,7 +271,9 @@ static inline bool checkpoint_restore_ns_capable(struct user_namespace *ns) } /* audit system wants to get cap info from files as well */ -extern int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps); +extern int get_vfs_caps_from_disk(struct user_namespace *mnt_userns, + const struct dentry *dentry, + struct cpu_vfs_cap_data *cpu_caps); extern int cap_convert_nscap(struct user_namespace *mnt_userns, struct dentry *dentry, void **ivalue, size_t size); diff --git a/include/linux/lsm_hook_defs.h b/include/linux/lsm_hook_defs.h index 32a940117e7a..d418b6feaeeb 100644 --- a/include/linux/lsm_hook_defs.h +++ b/include/linux/lsm_hook_defs.h @@ -133,17 +133,20 @@ LSM_HOOK(int, 0, inode_follow_link, struct dentry *dentry, struct inode *inode, LSM_HOOK(int, 0, inode_permission, struct inode *inode, int mask) LSM_HOOK(int, 0, inode_setattr, struct dentry *dentry, struct iattr *attr) LSM_HOOK(int, 0, inode_getattr, const struct path *path) -LSM_HOOK(int, 0, inode_setxattr, struct dentry *dentry, const char *name, - const void *value, size_t size, int flags) +LSM_HOOK(int, 0, inode_setxattr, struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, const void *value, + size_t size, int flags) LSM_HOOK(void, LSM_RET_VOID, inode_post_setxattr, struct dentry *dentry, const char *name, const void *value, size_t size, int flags) LSM_HOOK(int, 0, inode_getxattr, struct dentry *dentry, const char *name) LSM_HOOK(int, 0, inode_listxattr, struct dentry *dentry) -LSM_HOOK(int, 0, inode_removexattr, struct dentry *dentry, const char *name) +LSM_HOOK(int, 0, inode_removexattr, struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name) LSM_HOOK(int, 0, inode_need_killpriv, struct dentry *dentry) -LSM_HOOK(int, 0, inode_killpriv, struct dentry *dentry) -LSM_HOOK(int, -EOPNOTSUPP, inode_getsecurity, struct inode *inode, - const char *name, void **buffer, bool alloc) +LSM_HOOK(int, 0, inode_killpriv, struct user_namespace *mnt_userns, + struct dentry *dentry) +LSM_HOOK(int, -EOPNOTSUPP, inode_getsecurity, struct user_namespace *mnt_userns, + struct inode *inode, const char *name, void **buffer, bool alloc) LSM_HOOK(int, -EOPNOTSUPP, inode_setsecurity, struct inode *inode, const char *name, const void *value, size_t size, int flags) LSM_HOOK(int, 0, inode_listsecurity, struct inode *inode, char *buffer, diff --git a/include/linux/lsm_hooks.h b/include/linux/lsm_hooks.h index c503f7ab8afb..94bbac093797 100644 --- a/include/linux/lsm_hooks.h +++ b/include/linux/lsm_hooks.h @@ -444,6 +444,7 @@ * @inode_killpriv: * The setuid bit is being removed. Remove similar security labels. * Called with the dentry->d_inode->i_mutex held. + * @mnt_userns: user namespace of the mount * @dentry is the dentry being changed. * Return 0 on success. If error is returned, then the operation * causing setuid bit removal is failed. diff --git a/include/linux/security.h b/include/linux/security.h index bc2725491560..79230352a1aa 100644 --- a/include/linux/security.h +++ b/include/linux/security.h @@ -146,10 +146,13 @@ extern int cap_capset(struct cred *new, const struct cred *old, extern int cap_bprm_creds_from_file(struct linux_binprm *bprm, struct file *file); extern int cap_inode_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); -extern int cap_inode_removexattr(struct dentry *dentry, const char *name); +extern int cap_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name); extern int cap_inode_need_killpriv(struct dentry *dentry); -extern int cap_inode_killpriv(struct dentry *dentry); -extern int cap_inode_getsecurity(struct inode *inode, const char *name, +extern int cap_inode_killpriv(struct user_namespace *mnt_userns, + struct dentry *dentry); +extern int cap_inode_getsecurity(struct user_namespace *mnt_userns, + struct inode *inode, const char *name, void **buffer, bool alloc); extern int cap_mmap_addr(unsigned long addr); extern int cap_mmap_file(struct file *file, unsigned long reqprot, @@ -344,16 +347,21 @@ int security_inode_follow_link(struct dentry *dentry, struct inode *inode, int security_inode_permission(struct inode *inode, int mask); int security_inode_setattr(struct dentry *dentry, struct iattr *attr); int security_inode_getattr(const struct path *path); -int security_inode_setxattr(struct dentry *dentry, const char *name, +int security_inode_setxattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, const void *value, size_t size, int flags); void security_inode_post_setxattr(struct dentry *dentry, const char *name, const void *value, size_t size, int flags); int security_inode_getxattr(struct dentry *dentry, const char *name); int security_inode_listxattr(struct dentry *dentry); -int security_inode_removexattr(struct dentry *dentry, const char *name); +int security_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name); int security_inode_need_killpriv(struct dentry *dentry); -int security_inode_killpriv(struct dentry *dentry); -int security_inode_getsecurity(struct inode *inode, const char *name, void **buffer, bool alloc); +int security_inode_killpriv(struct user_namespace *mnt_userns, + struct dentry *dentry); +int security_inode_getsecurity(struct user_namespace *mnt_userns, + struct inode *inode, const char *name, + void **buffer, bool alloc); int security_inode_setsecurity(struct inode *inode, const char *name, const void *value, size_t size, int flags); int security_inode_listsecurity(struct inode *inode, char *buffer, size_t buffer_size); void security_inode_getsecid(struct inode *inode, u32 *secid); @@ -830,8 +838,9 @@ static inline int security_inode_getattr(const struct path *path) return 0; } -static inline int security_inode_setxattr(struct dentry *dentry, - const char *name, const void *value, size_t size, int flags) +static inline int security_inode_setxattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, const void *value, + size_t size, int flags) { return cap_inode_setxattr(dentry, name, value, size, flags); } @@ -851,10 +860,11 @@ static inline int security_inode_listxattr(struct dentry *dentry) return 0; } -static inline int security_inode_removexattr(struct dentry *dentry, - const char *name) +static inline int security_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, + const char *name) { - return cap_inode_removexattr(dentry, name); + return cap_inode_removexattr(mnt_userns, dentry, name); } static inline int security_inode_need_killpriv(struct dentry *dentry) @@ -862,12 +872,16 @@ static inline int security_inode_need_killpriv(struct dentry *dentry) return cap_inode_need_killpriv(dentry); } -static inline int security_inode_killpriv(struct dentry *dentry) +static inline int security_inode_killpriv(struct user_namespace *mnt_userns, + struct dentry *dentry) { - return cap_inode_killpriv(dentry); + return cap_inode_killpriv(mnt_userns, dentry); } -static inline int security_inode_getsecurity(struct inode *inode, const char *name, void **buffer, bool alloc) +static inline int security_inode_getsecurity(struct user_namespace *mnt_userns, + struct inode *inode, + const char *name, void **buffer, + bool alloc) { return -EOPNOTSUPP; } diff --git a/kernel/auditsc.c b/kernel/auditsc.c index 8dba8f0983b5..383dcc1a54a4 100644 --- a/kernel/auditsc.c +++ b/kernel/auditsc.c @@ -1944,7 +1944,7 @@ static inline int audit_copy_fcaps(struct audit_names *name, if (!dentry) return 0; - rc = get_vfs_caps_from_disk(dentry, &caps); + rc = get_vfs_caps_from_disk(&init_user_ns, dentry, &caps); if (rc) return rc; @@ -2495,7 +2495,8 @@ int __audit_log_bprm_fcaps(struct linux_binprm *bprm, ax->d.next = context->aux; context->aux = (void *)ax; - get_vfs_caps_from_disk(bprm->file->f_path.dentry, &vcaps); + get_vfs_caps_from_disk(&init_user_ns, + bprm->file->f_path.dentry, &vcaps); ax->fcap.permitted = vcaps.permitted; ax->fcap.inheritable = vcaps.inheritable; diff --git a/security/commoncap.c b/security/commoncap.c index 8e16ec0b3c4b..d91118752e0a 100644 --- a/security/commoncap.c +++ b/security/commoncap.c @@ -303,17 +303,25 @@ int cap_inode_need_killpriv(struct dentry *dentry) /** * cap_inode_killpriv - Erase the security markings on an inode - * @dentry: The inode/dentry to alter + * + * @mnt_userns: user namespace of the mount the inode was found from + * @dentry: The inode/dentry to alter * * Erase the privilege-enhancing security markings on an inode. * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * Returns 0 if successful, -ve on error. */ -int cap_inode_killpriv(struct dentry *dentry) +int cap_inode_killpriv(struct user_namespace *mnt_userns, struct dentry *dentry) { int error; - error = __vfs_removexattr(&init_user_ns, dentry, XATTR_NAME_CAPS); + error = __vfs_removexattr(mnt_userns, dentry, XATTR_NAME_CAPS); if (error == -EOPNOTSUPP) error = 0; return error; @@ -366,8 +374,8 @@ static bool is_v3header(size_t size, const struct vfs_cap_data *cap) * by the integrity subsystem, which really wants the unconverted values - * so that's good. */ -int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer, - bool alloc) +int cap_inode_getsecurity(struct user_namespace *mnt_userns, struct inode *inode, + const char *name, void **buffer, bool alloc) { int size, ret; kuid_t kroot; @@ -386,7 +394,7 @@ int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer, return -EINVAL; size = sizeof(struct vfs_ns_cap_data); - ret = (int)vfs_getxattr_alloc(&init_user_ns, dentry, XATTR_NAME_CAPS, + ret = (int)vfs_getxattr_alloc(mnt_userns, dentry, XATTR_NAME_CAPS, &tmpbuf, size, GFP_NOFS); dput(dentry); @@ -412,6 +420,9 @@ int cap_inode_getsecurity(struct inode *inode, const char *name, void **buffer, root = le32_to_cpu(nscap->rootid); kroot = make_kuid(fs_ns, root); + /* If this is an idmapped mount shift the kuid. */ + kroot = kuid_into_mnt(mnt_userns, kroot); + /* If the root kuid maps to a valid uid in current ns, then return * this as a nscap. */ mappedroot = from_kuid(current_user_ns(), kroot); @@ -552,7 +563,6 @@ int cap_convert_nscap(struct user_namespace *mnt_userns, struct dentry *dentry, *ivalue = nscap; return newsize; } -EXPORT_SYMBOL(cap_convert_nscap); /* * Calculate the new process capability sets from the capability sets attached @@ -598,10 +608,24 @@ static inline int bprm_caps_from_vfs_caps(struct cpu_vfs_cap_data *caps, return *effective ? ret : 0; } -/* +/** + * get_vfs_caps_from_disk - retrieve vfs caps from disk + * + * @mnt_userns: user namespace of the mount the inode was found from + * @dentry: dentry from which @inode is retrieved + * @cpu_caps: vfs capabilities + * * Extract the on-exec-apply capability sets for an executable file. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. */ -int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data *cpu_caps) +int get_vfs_caps_from_disk(struct user_namespace *mnt_userns, + const struct dentry *dentry, + struct cpu_vfs_cap_data *cpu_caps) { struct inode *inode = d_backing_inode(dentry); __u32 magic_etc; @@ -657,6 +681,7 @@ int get_vfs_caps_from_disk(const struct dentry *dentry, struct cpu_vfs_cap_data /* Limit the caps to the mounter of the filesystem * or the more limited uid specified in the xattr. */ + rootkuid = kuid_into_mnt(mnt_userns, rootkuid); if (!rootid_owns_currentns(rootkuid)) return -ENODATA; @@ -702,7 +727,7 @@ static int get_file_caps(struct linux_binprm *bprm, struct file *file, if (!current_in_userns(file->f_path.mnt->mnt_sb->s_user_ns)) return 0; - rc = get_vfs_caps_from_disk(file->f_path.dentry, &vcaps); + rc = get_vfs_caps_from_disk(mnt_user_ns(file->f_path.mnt), file->f_path.dentry, &vcaps); if (rc < 0) { if (rc == -EINVAL) printk(KERN_NOTICE "Invalid argument reading file caps for %s\n", @@ -967,16 +992,25 @@ int cap_inode_setxattr(struct dentry *dentry, const char *name, /** * cap_inode_removexattr - Determine whether an xattr may be removed - * @dentry: The inode/dentry being altered - * @name: The name of the xattr to be changed + * + * @mnt_userns: User namespace of the mount the inode was found from + * @dentry: The inode/dentry being altered + * @name: The name of the xattr to be changed * * Determine whether an xattr may be removed from an inode, returning 0 if * permission is granted, -ve if denied. * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * This is used to make sure security xattrs don't get removed by those who * aren't privileged to remove them. */ -int cap_inode_removexattr(struct dentry *dentry, const char *name) +int cap_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name) { struct user_namespace *user_ns = dentry->d_sb->s_user_ns; @@ -990,8 +1024,7 @@ int cap_inode_removexattr(struct dentry *dentry, const char *name) struct inode *inode = d_backing_inode(dentry); if (!inode) return -EINVAL; - if (!capable_wrt_inode_uidgid(&init_user_ns, inode, - CAP_SETFCAP)) + if (!capable_wrt_inode_uidgid(mnt_userns, inode, CAP_SETFCAP)) return -EPERM; return 0; } diff --git a/security/security.c b/security/security.c index a28045dc9e7f..66eaa43e386d 100644 --- a/security/security.c +++ b/security/security.c @@ -1279,7 +1279,8 @@ int security_inode_getattr(const struct path *path) return call_int_hook(inode_getattr, 0, path); } -int security_inode_setxattr(struct dentry *dentry, const char *name, +int security_inode_setxattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { int ret; @@ -1290,8 +1291,8 @@ int security_inode_setxattr(struct dentry *dentry, const char *name, * SELinux and Smack integrate the cap call, * so assume that all LSMs supplying this call do so. */ - ret = call_int_hook(inode_setxattr, 1, dentry, name, value, size, - flags); + ret = call_int_hook(inode_setxattr, 1, mnt_userns, dentry, name, value, + size, flags); if (ret == 1) ret = cap_inode_setxattr(dentry, name, value, size, flags); @@ -1326,7 +1327,8 @@ int security_inode_listxattr(struct dentry *dentry) return call_int_hook(inode_listxattr, 0, dentry); } -int security_inode_removexattr(struct dentry *dentry, const char *name) +int security_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name) { int ret; @@ -1336,9 +1338,9 @@ int security_inode_removexattr(struct dentry *dentry, const char *name) * SELinux and Smack integrate the cap call, * so assume that all LSMs supplying this call do so. */ - ret = call_int_hook(inode_removexattr, 1, dentry, name); + ret = call_int_hook(inode_removexattr, 1, mnt_userns, dentry, name); if (ret == 1) - ret = cap_inode_removexattr(dentry, name); + ret = cap_inode_removexattr(mnt_userns, dentry, name); if (ret) return ret; ret = ima_inode_removexattr(dentry, name); @@ -1352,12 +1354,15 @@ int security_inode_need_killpriv(struct dentry *dentry) return call_int_hook(inode_need_killpriv, 0, dentry); } -int security_inode_killpriv(struct dentry *dentry) +int security_inode_killpriv(struct user_namespace *mnt_userns, + struct dentry *dentry) { - return call_int_hook(inode_killpriv, 0, dentry); + return call_int_hook(inode_killpriv, 0, mnt_userns, dentry); } -int security_inode_getsecurity(struct inode *inode, const char *name, void **buffer, bool alloc) +int security_inode_getsecurity(struct user_namespace *mnt_userns, + struct inode *inode, const char *name, + void **buffer, bool alloc) { struct security_hook_list *hp; int rc; @@ -1368,7 +1373,7 @@ int security_inode_getsecurity(struct inode *inode, const char *name, void **buf * Only one module will provide an attribute with a given name. */ hlist_for_each_entry(hp, &security_hook_heads.inode_getsecurity, list) { - rc = hp->hook.inode_getsecurity(inode, name, buffer, alloc); + rc = hp->hook.inode_getsecurity(mnt_userns, inode, name, buffer, alloc); if (rc != LSM_RET_DEFAULT(inode_getsecurity)) return rc; } diff --git a/security/selinux/hooks.c b/security/selinux/hooks.c index 3f42950feed0..7635bb11a8f4 100644 --- a/security/selinux/hooks.c +++ b/security/selinux/hooks.c @@ -3112,7 +3112,8 @@ static bool has_cap_mac_admin(bool audit) return true; } -static int selinux_inode_setxattr(struct dentry *dentry, const char *name, +static int selinux_inode_setxattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct inode *inode = d_backing_inode(dentry); @@ -3133,13 +3134,13 @@ static int selinux_inode_setxattr(struct dentry *dentry, const char *name, } if (!selinux_initialized(&selinux_state)) - return (inode_owner_or_capable(&init_user_ns, inode) ? 0 : -EPERM); + return (inode_owner_or_capable(mnt_userns, inode) ? 0 : -EPERM); sbsec = inode->i_sb->s_security; if (!(sbsec->flags & SBLABEL_MNT)) return -EOPNOTSUPP; - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EPERM; ad.type = LSM_AUDIT_DATA_DENTRY; @@ -3260,10 +3261,11 @@ static int selinux_inode_listxattr(struct dentry *dentry) return dentry_has_perm(cred, dentry, FILE__GETATTR); } -static int selinux_inode_removexattr(struct dentry *dentry, const char *name) +static int selinux_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name) { if (strcmp(name, XATTR_NAME_SELINUX)) { - int rc = cap_inode_removexattr(dentry, name); + int rc = cap_inode_removexattr(mnt_userns, dentry, name); if (rc) return rc; @@ -3329,7 +3331,9 @@ static int selinux_path_notify(const struct path *path, u64 mask, * * Permission check is handled by selinux_inode_getxattr hook. */ -static int selinux_inode_getsecurity(struct inode *inode, const char *name, void **buffer, bool alloc) +static int selinux_inode_getsecurity(struct user_namespace *mnt_userns, + struct inode *inode, const char *name, + void **buffer, bool alloc) { u32 size; int error; @@ -6524,8 +6528,8 @@ static int selinux_inode_setsecctx(struct dentry *dentry, void *ctx, u32 ctxlen) static int selinux_inode_getsecctx(struct inode *inode, void **ctx, u32 *ctxlen) { int len = 0; - len = selinux_inode_getsecurity(inode, XATTR_SELINUX_SUFFIX, - ctx, true); + len = selinux_inode_getsecurity(&init_user_ns, inode, + XATTR_SELINUX_SUFFIX, ctx, true); if (len < 0) return len; *ctxlen = len; diff --git a/security/smack/smack_lsm.c b/security/smack/smack_lsm.c index 11003244b18b..66471f81e518 100644 --- a/security/smack/smack_lsm.c +++ b/security/smack/smack_lsm.c @@ -1240,7 +1240,8 @@ static int smack_inode_getattr(const struct path *path) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_setxattr(struct dentry *dentry, const char *name, +static int smack_inode_setxattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name, const void *value, size_t size, int flags) { struct smk_audit_info ad; @@ -1362,7 +1363,8 @@ static int smack_inode_getxattr(struct dentry *dentry, const char *name) * * Returns 0 if access is permitted, an error code otherwise */ -static int smack_inode_removexattr(struct dentry *dentry, const char *name) +static int smack_inode_removexattr(struct user_namespace *mnt_userns, + struct dentry *dentry, const char *name) { struct inode_smack *isp; struct smk_audit_info ad; @@ -1377,7 +1379,7 @@ static int smack_inode_removexattr(struct dentry *dentry, const char *name) if (!smack_privileged(CAP_MAC_ADMIN)) rc = -EPERM; } else - rc = cap_inode_removexattr(dentry, name); + rc = cap_inode_removexattr(mnt_userns, dentry, name); if (rc != 0) return rc; @@ -1420,9 +1422,9 @@ static int smack_inode_removexattr(struct dentry *dentry, const char *name) * * Returns the size of the attribute or an error code */ -static int smack_inode_getsecurity(struct inode *inode, - const char *name, void **buffer, - bool alloc) +static int smack_inode_getsecurity(struct user_namespace *mnt_userns, + struct inode *inode, const char *name, + void **buffer, bool alloc) { struct socket_smack *ssp; struct socket *sock; From patchwork Thu Dec 3 23:57:13 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950107 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 51960C352A2 for ; Fri, 4 Dec 2020 00:03:13 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2A91E22224 for ; Fri, 4 Dec 2020 00:03:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389451AbgLDACl (ORCPT ); Thu, 3 Dec 2020 19:02:41 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41818 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2388205AbgLDACf (ORCPT ); Thu, 3 Dec 2020 19:02:35 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXh-0007ka-Hs; Fri, 04 Dec 2020 00:01:45 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 17/40] stat: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:13 +0100 Message-Id: <20201203235736.3528991-18-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The generic_fillattr() helper fills in the basic attributes associated with an inode. Enable it to handle idmapped mounts. If the inode is accessed through an idmapped mount we need to map it according to the mount's user namespace before we store the uid and gid. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ unchanged /* v4 */ unchanged --- fs/9p/vfs_inode.c | 4 ++-- fs/9p/vfs_inode_dotl.c | 4 ++-- fs/afs/inode.c | 2 +- fs/btrfs/inode.c | 2 +- fs/ceph/inode.c | 2 +- fs/cifs/inode.c | 2 +- fs/coda/inode.c | 2 +- fs/ecryptfs/inode.c | 4 ++-- fs/erofs/inode.c | 2 +- fs/exfat/file.c | 2 +- fs/ext2/inode.c | 2 +- fs/ext4/inode.c | 2 +- fs/f2fs/file.c | 2 +- fs/fat/file.c | 2 +- fs/fuse/dir.c | 2 +- fs/gfs2/inode.c | 2 +- fs/hfsplus/inode.c | 2 +- fs/kernfs/inode.c | 2 +- fs/libfs.c | 4 ++-- fs/minix/inode.c | 2 +- fs/nfs/inode.c | 2 +- fs/nfs/namespace.c | 2 +- fs/ocfs2/file.c | 2 +- fs/orangefs/inode.c | 2 +- fs/proc/base.c | 4 ++-- fs/proc/generic.c | 2 +- fs/proc/proc_net.c | 2 +- fs/proc/proc_sysctl.c | 2 +- fs/proc/root.c | 2 +- fs/stat.c | 20 ++++++++++++++------ fs/sysv/itree.c | 2 +- fs/ubifs/dir.c | 2 +- fs/udf/symlink.c | 2 +- fs/vboxsf/utils.c | 2 +- include/linux/fs.h | 2 +- mm/shmem.c | 2 +- 36 files changed, 54 insertions(+), 46 deletions(-) diff --git a/fs/9p/vfs_inode.c b/fs/9p/vfs_inode.c index 1b2028be2e08..ca3ac5a8daa8 100644 --- a/fs/9p/vfs_inode.c +++ b/fs/9p/vfs_inode.c @@ -1006,7 +1006,7 @@ v9fs_vfs_getattr(const struct path *path, struct kstat *stat, p9_debug(P9_DEBUG_VFS, "dentry: %p\n", dentry); v9ses = v9fs_dentry2v9ses(dentry); if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { - generic_fillattr(d_inode(dentry), stat); + generic_fillattr(&init_user_ns, d_inode(dentry), stat); return 0; } fid = v9fs_fid_lookup(dentry); @@ -1018,7 +1018,7 @@ v9fs_vfs_getattr(const struct path *path, struct kstat *stat, return PTR_ERR(st); v9fs_stat2inode(st, d_inode(dentry), dentry->d_sb, 0); - generic_fillattr(d_inode(dentry), stat); + generic_fillattr(&init_user_ns, d_inode(dentry), stat); p9stat_free(st); kfree(st); diff --git a/fs/9p/vfs_inode_dotl.c b/fs/9p/vfs_inode_dotl.c index 282ec5cb45dc..8f3c1daf72ba 100644 --- a/fs/9p/vfs_inode_dotl.c +++ b/fs/9p/vfs_inode_dotl.c @@ -466,7 +466,7 @@ v9fs_vfs_getattr_dotl(const struct path *path, struct kstat *stat, p9_debug(P9_DEBUG_VFS, "dentry: %p\n", dentry); v9ses = v9fs_dentry2v9ses(dentry); if (v9ses->cache == CACHE_LOOSE || v9ses->cache == CACHE_FSCACHE) { - generic_fillattr(d_inode(dentry), stat); + generic_fillattr(&init_user_ns, d_inode(dentry), stat); return 0; } fid = v9fs_fid_lookup(dentry); @@ -482,7 +482,7 @@ v9fs_vfs_getattr_dotl(const struct path *path, struct kstat *stat, return PTR_ERR(st); v9fs_stat2inode_dotl(st, d_inode(dentry), 0); - generic_fillattr(d_inode(dentry), stat); + generic_fillattr(&init_user_ns, d_inode(dentry), stat); /* Change block size to what the server returned */ stat->blksize = st->st_blksize; diff --git a/fs/afs/inode.c b/fs/afs/inode.c index b0d7b892090d..795ee5cb3817 100644 --- a/fs/afs/inode.c +++ b/fs/afs/inode.c @@ -745,7 +745,7 @@ int afs_getattr(const struct path *path, struct kstat *stat, do { read_seqbegin_or_lock(&vnode->cb_lock, &seq); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); if (test_bit(AFS_VNODE_SILLY_DELETED, &vnode->flags) && stat->nlink > 0) stat->nlink -= 1; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 502b7db93894..694644bbd2d9 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -8841,7 +8841,7 @@ static int btrfs_getattr(const struct path *path, struct kstat *stat, STATX_ATTR_IMMUTABLE | STATX_ATTR_NODUMP); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->dev = BTRFS_I(inode)->root->anon_dev; spin_lock(&BTRFS_I(inode)->lock); diff --git a/fs/ceph/inode.c b/fs/ceph/inode.c index 19ec845ba5ec..decfa5e1ec4c 100644 --- a/fs/ceph/inode.c +++ b/fs/ceph/inode.c @@ -2380,7 +2380,7 @@ int ceph_getattr(const struct path *path, struct kstat *stat, return err; } - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->ino = ceph_present_inode(inode); /* diff --git a/fs/cifs/inode.c b/fs/cifs/inode.c index 4d69b786e403..4fb3b6961096 100644 --- a/fs/cifs/inode.c +++ b/fs/cifs/inode.c @@ -2400,7 +2400,7 @@ int cifs_getattr(const struct path *path, struct kstat *stat, return rc; } - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->blksize = cifs_sb->bsize; stat->ino = CIFS_I(inode)->uniqueid; diff --git a/fs/coda/inode.c b/fs/coda/inode.c index b1c70e2b9b1e..4d113e191cb8 100644 --- a/fs/coda/inode.c +++ b/fs/coda/inode.c @@ -256,7 +256,7 @@ int coda_getattr(const struct path *path, struct kstat *stat, { int err = coda_revalidate_inode(d_inode(path->dentry)); if (!err) - generic_fillattr(d_inode(path->dentry), stat); + generic_fillattr(&init_user_ns, d_inode(path->dentry), stat); return err; } diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 7de9fda25dad..0a218d7c3023 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -975,7 +975,7 @@ static int ecryptfs_getattr_link(const struct path *path, struct kstat *stat, mount_crypt_stat = &ecryptfs_superblock_to_private( dentry->d_sb)->mount_crypt_stat; - generic_fillattr(d_inode(dentry), stat); + generic_fillattr(&init_user_ns, d_inode(dentry), stat); if (mount_crypt_stat->flags & ECRYPTFS_GLOBAL_ENCRYPT_FILENAMES) { char *target; size_t targetsiz; @@ -1003,7 +1003,7 @@ static int ecryptfs_getattr(const struct path *path, struct kstat *stat, if (!rc) { fsstack_copy_attr_all(d_inode(dentry), ecryptfs_inode_to_lower(d_inode(dentry))); - generic_fillattr(d_inode(dentry), stat); + generic_fillattr(&init_user_ns, d_inode(dentry), stat); stat->blocks = lower_stat.blocks; } return rc; diff --git a/fs/erofs/inode.c b/fs/erofs/inode.c index 3e21c0e8adae..083818063ac6 100644 --- a/fs/erofs/inode.c +++ b/fs/erofs/inode.c @@ -343,7 +343,7 @@ int erofs_getattr(const struct path *path, struct kstat *stat, stat->attributes_mask |= (STATX_ATTR_COMPRESSED | STATX_ATTR_IMMUTABLE); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); return 0; } diff --git a/fs/exfat/file.c b/fs/exfat/file.c index ace35aa8e64b..e9705b3295d3 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -273,7 +273,7 @@ int exfat_getattr(const struct path *path, struct kstat *stat, struct inode *inode = d_backing_inode(path->dentry); struct exfat_inode_info *ei = EXFAT_I(inode); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); exfat_truncate_atime(&stat->atime); stat->result_mask |= STATX_BTIME; stat->btime.tv_sec = ei->i_crtime.tv_sec; diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index 5ff1e5e3c0fe..7fcda76094ff 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -1659,7 +1659,7 @@ int ext2_getattr(const struct path *path, struct kstat *stat, STATX_ATTR_IMMUTABLE | STATX_ATTR_NODUMP); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); return 0; } diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index f0487795afd1..d992fef2e7c2 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -5558,7 +5558,7 @@ int ext4_getattr(const struct path *path, struct kstat *stat, STATX_ATTR_NODUMP | STATX_ATTR_VERITY); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); return 0; } diff --git a/fs/f2fs/file.c b/fs/f2fs/file.c index e4a13570deff..712e1eaff4d5 100644 --- a/fs/f2fs/file.c +++ b/fs/f2fs/file.c @@ -814,7 +814,7 @@ int f2fs_getattr(const struct path *path, struct kstat *stat, STATX_ATTR_NODUMP | STATX_ATTR_VERITY); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); /* we need to show initial sectors used for inline_data/dentries */ if ((S_ISREG(inode->i_mode) && f2fs_has_inline_data(inode)) || diff --git a/fs/fat/file.c b/fs/fat/file.c index 805b501467e9..f7e04f533d31 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -398,7 +398,7 @@ int fat_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct inode *inode = d_inode(path->dentry); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->blksize = MSDOS_SB(inode->i_sb)->cluster_size; if (MSDOS_SB(inode->i_sb)->options.nfs == FAT_NFS_NOSTALE_RO) { diff --git a/fs/fuse/dir.c b/fs/fuse/dir.c index dc6e8cc565d2..0750755685cd 100644 --- a/fs/fuse/dir.c +++ b/fs/fuse/dir.c @@ -1064,7 +1064,7 @@ static int fuse_update_get_attr(struct inode *inode, struct file *file, forget_all_cached_acls(inode); err = fuse_do_getattr(inode, stat, file); } else if (stat) { - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->mode = fi->orig_i_mode; stat->ino = fi->orig_ino; } diff --git a/fs/gfs2/inode.c b/fs/gfs2/inode.c index a29b66116c01..f1841667bcd9 100644 --- a/fs/gfs2/inode.c +++ b/fs/gfs2/inode.c @@ -2048,7 +2048,7 @@ static int gfs2_getattr(const struct path *path, struct kstat *stat, STATX_ATTR_IMMUTABLE | STATX_ATTR_NODUMP); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); if (gfs2_holder_initialized(&gh)) gfs2_glock_dq_uninit(&gh); diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c index ffa137f8234e..642e067d8fe8 100644 --- a/fs/hfsplus/inode.c +++ b/fs/hfsplus/inode.c @@ -286,7 +286,7 @@ int hfsplus_getattr(const struct path *path, struct kstat *stat, stat->attributes_mask |= STATX_ATTR_APPEND | STATX_ATTR_IMMUTABLE | STATX_ATTR_NODUMP; - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); return 0; } diff --git a/fs/kernfs/inode.c b/fs/kernfs/inode.c index 7e44052b42e1..032d3d7546d8 100644 --- a/fs/kernfs/inode.c +++ b/fs/kernfs/inode.c @@ -193,7 +193,7 @@ int kernfs_iop_getattr(const struct path *path, struct kstat *stat, kernfs_refresh_inode(kn, inode); mutex_unlock(&kernfs_mutex); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); return 0; } diff --git a/fs/libfs.c b/fs/libfs.c index a0ca5c9f946d..ea9d53d68ca5 100644 --- a/fs/libfs.c +++ b/fs/libfs.c @@ -31,7 +31,7 @@ int simple_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->blocks = inode->i_mapping->nrpages << (PAGE_SHIFT - 9); return 0; } @@ -1304,7 +1304,7 @@ static int empty_dir_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { struct inode *inode = d_inode(path->dentry); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); return 0; } diff --git a/fs/minix/inode.c b/fs/minix/inode.c index 34f546404aa1..91c81d2fc90d 100644 --- a/fs/minix/inode.c +++ b/fs/minix/inode.c @@ -658,7 +658,7 @@ int minix_getattr(const struct path *path, struct kstat *stat, struct super_block *sb = path->dentry->d_sb; struct inode *inode = d_inode(path->dentry); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); if (INODE_VERSION(inode) == MINIX_V1) stat->blocks = (BLOCK_SIZE / 512) * V1_minix_blocks(stat->size, sb); else diff --git a/fs/nfs/inode.c b/fs/nfs/inode.c index aa6493905bbe..8d74974d7992 100644 --- a/fs/nfs/inode.c +++ b/fs/nfs/inode.c @@ -858,7 +858,7 @@ int nfs_getattr(const struct path *path, struct kstat *stat, /* Only return attributes that were revalidated. */ stat->result_mask &= request_mask; out_no_update: - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->ino = nfs_compat_user_ino64(NFS_FILEID(inode)); if (S_ISDIR(inode->i_mode)) stat->blksize = NFS_SERVER(inode)->dtsize; diff --git a/fs/nfs/namespace.c b/fs/nfs/namespace.c index 2bcbe38afe2e..55fc711e368b 100644 --- a/fs/nfs/namespace.c +++ b/fs/nfs/namespace.c @@ -213,7 +213,7 @@ nfs_namespace_getattr(const struct path *path, struct kstat *stat, { if (NFS_FH(d_inode(path->dentry))->size != 0) return nfs_getattr(path, stat, request_mask, query_flags); - generic_fillattr(d_inode(path->dentry), stat); + generic_fillattr(&init_user_ns, d_inode(path->dentry), stat); return 0; } diff --git a/fs/ocfs2/file.c b/fs/ocfs2/file.c index cabf355b148f..a070d4c9b6ed 100644 --- a/fs/ocfs2/file.c +++ b/fs/ocfs2/file.c @@ -1313,7 +1313,7 @@ int ocfs2_getattr(const struct path *path, struct kstat *stat, goto bail; } - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); /* * If there is inline data in the inode, the inode will normally not * have data blocks allocated (it may have an external xattr block). diff --git a/fs/orangefs/inode.c b/fs/orangefs/inode.c index 563fe9ab8eb2..b94032f77e61 100644 --- a/fs/orangefs/inode.c +++ b/fs/orangefs/inode.c @@ -903,7 +903,7 @@ int orangefs_getattr(const struct path *path, struct kstat *stat, ret = orangefs_inode_getattr(inode, request_mask & STATX_SIZE ? ORANGEFS_GETATTR_SIZE : 0); if (ret == 0) { - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); /* override block size reported to stat */ if (!(request_mask & STATX_SIZE)) diff --git a/fs/proc/base.c b/fs/proc/base.c index 266b54b75c4c..bf4b203103a7 100644 --- a/fs/proc/base.c +++ b/fs/proc/base.c @@ -1934,7 +1934,7 @@ int pid_getattr(const struct path *path, struct kstat *stat, struct proc_fs_info *fs_info = proc_sb_info(inode->i_sb); struct task_struct *task; - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->uid = GLOBAL_ROOT_UID; stat->gid = GLOBAL_ROOT_GID; @@ -3797,7 +3797,7 @@ static int proc_task_getattr(const struct path *path, struct kstat *stat, { struct inode *inode = d_inode(path->dentry); struct task_struct *p = get_proc_task(inode); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); if (p) { stat->nlink += get_nr_threads(p); diff --git a/fs/proc/generic.c b/fs/proc/generic.c index 896f2ebde75c..5c6345c68fc5 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -145,7 +145,7 @@ static int proc_getattr(const struct path *path, struct kstat *stat, } } - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); return 0; } diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c index ed8a6306990c..bc555b7da6dc 100644 --- a/fs/proc/proc_net.c +++ b/fs/proc/proc_net.c @@ -313,7 +313,7 @@ static int proc_tgid_net_getattr(const struct path *path, struct kstat *stat, net = get_proc_task_net(inode); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); if (net != NULL) { stat->nlink = net->proc_net->nlink; diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c index ec67dbc1f705..87c828348140 100644 --- a/fs/proc/proc_sysctl.c +++ b/fs/proc/proc_sysctl.c @@ -840,7 +840,7 @@ static int proc_sys_getattr(const struct path *path, struct kstat *stat, if (IS_ERR(head)) return PTR_ERR(head); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); if (table) stat->mode = (stat->mode & S_IFMT) | table->mode; diff --git a/fs/proc/root.c b/fs/proc/root.c index 5e444d4f9717..244e4b6f15ef 100644 --- a/fs/proc/root.c +++ b/fs/proc/root.c @@ -311,7 +311,7 @@ void __init proc_root_init(void) static int proc_root_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int query_flags) { - generic_fillattr(d_inode(path->dentry), stat); + generic_fillattr(&init_user_ns, d_inode(path->dentry), stat); stat->nlink = proc_root.nlink + nr_processes(); return 0; } diff --git a/fs/stat.c b/fs/stat.c index dacecdda2e79..8c58c37ef1ab 100644 --- a/fs/stat.c +++ b/fs/stat.c @@ -26,21 +26,29 @@ /** * generic_fillattr - Fill in the basic attributes from the inode struct - * @inode: Inode to use as the source - * @stat: Where to fill in the attributes + * @mnt_userns: user namespace of the mount the inode was found from + * @inode: Inode to use as the source + * @stat: Where to fill in the attributes * * Fill in the basic attributes in the kstat structure from data that's to be * found on the VFS inode structure. This is the default if no getattr inode * operation is supplied. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before filling in the uid and + * gid filds. On non-idmapped mounts or if permission checking is to be + * performed on the raw inode simply passs init_user_ns. */ -void generic_fillattr(struct inode *inode, struct kstat *stat) +void generic_fillattr(struct user_namespace *mnt_userns, struct inode *inode, + struct kstat *stat) { stat->dev = inode->i_sb->s_dev; stat->ino = inode->i_ino; stat->mode = inode->i_mode; stat->nlink = inode->i_nlink; - stat->uid = inode->i_uid; - stat->gid = inode->i_gid; + stat->uid = i_uid_into_mnt(mnt_userns, inode); + stat->gid = i_gid_into_mnt(mnt_userns, inode); stat->rdev = inode->i_rdev; stat->size = i_size_read(inode); stat->atime = inode->i_atime; @@ -87,7 +95,7 @@ int vfs_getattr_nosec(const struct path *path, struct kstat *stat, return inode->i_op->getattr(path, stat, request_mask, query_flags); - generic_fillattr(inode, stat); + generic_fillattr(mnt_user_ns(path->mnt), inode, stat); return 0; } EXPORT_SYMBOL(vfs_getattr_nosec); diff --git a/fs/sysv/itree.c b/fs/sysv/itree.c index bcb67b0cabe7..83cffab6955f 100644 --- a/fs/sysv/itree.c +++ b/fs/sysv/itree.c @@ -445,7 +445,7 @@ int sysv_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct super_block *s = path->dentry->d_sb; - generic_fillattr(d_inode(path->dentry), stat); + generic_fillattr(&init_user_ns, d_inode(path->dentry), stat); stat->blocks = (s->s_blocksize / 512) * sysv_nblocks(s, stat->size); stat->blksize = s->s_blocksize; return 0; diff --git a/fs/ubifs/dir.c b/fs/ubifs/dir.c index a2bbd7ed39e9..e61d6fa7ed18 100644 --- a/fs/ubifs/dir.c +++ b/fs/ubifs/dir.c @@ -1579,7 +1579,7 @@ int ubifs_getattr(const struct path *path, struct kstat *stat, STATX_ATTR_ENCRYPTED | STATX_ATTR_IMMUTABLE); - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); stat->blksize = UBIFS_BLOCK_SIZE; stat->size = ui->ui_size; diff --git a/fs/udf/symlink.c b/fs/udf/symlink.c index c973db239604..54a44d1f023c 100644 --- a/fs/udf/symlink.c +++ b/fs/udf/symlink.c @@ -159,7 +159,7 @@ static int udf_symlink_getattr(const struct path *path, struct kstat *stat, struct inode *inode = d_backing_inode(dentry); struct page *page; - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); page = read_mapping_page(inode->i_mapping, 0, NULL); if (IS_ERR(page)) return PTR_ERR(page); diff --git a/fs/vboxsf/utils.c b/fs/vboxsf/utils.c index 018057546067..d2cd1c99f48e 100644 --- a/fs/vboxsf/utils.c +++ b/fs/vboxsf/utils.c @@ -233,7 +233,7 @@ int vboxsf_getattr(const struct path *path, struct kstat *kstat, if (err) return err; - generic_fillattr(d_inode(dentry), kstat); + generic_fillattr(&init_user_ns, d_inode(dentry), kstat); return 0; } diff --git a/include/linux/fs.h b/include/linux/fs.h index fb634ea3ce05..e4d5a7c82136 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -3143,7 +3143,7 @@ extern int __page_symlink(struct inode *inode, const char *symname, int len, extern int page_symlink(struct inode *inode, const char *symname, int len); extern const struct inode_operations page_symlink_inode_operations; extern void kfree_link(void *); -extern void generic_fillattr(struct inode *, struct kstat *); +extern void generic_fillattr(struct user_namespace *, struct inode *, struct kstat *); extern int vfs_getattr_nosec(const struct path *, struct kstat *, u32, unsigned int); extern int vfs_getattr(const struct path *, struct kstat *, u32, unsigned int); void __inode_add_bytes(struct inode *inode, loff_t bytes); diff --git a/mm/shmem.c b/mm/shmem.c index da36ac107128..fe43360c5888 100644 --- a/mm/shmem.c +++ b/mm/shmem.c @@ -1072,7 +1072,7 @@ static int shmem_getattr(const struct path *path, struct kstat *stat, shmem_recalc_inode(inode); spin_unlock_irq(&info->lock); } - generic_fillattr(inode, stat); + generic_fillattr(&init_user_ns, inode, stat); if (is_huge_enabled(sb_info)) stat->blksize = HPAGE_PMD_SIZE; From patchwork Thu Dec 3 23:57:14 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950115 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 317C4C3CFC2 for ; Fri, 4 Dec 2020 00:03:14 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 0236B224D1 for ; Fri, 4 Dec 2020 00:03:13 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388423AbgLDACn (ORCPT ); Thu, 3 Dec 2020 19:02:43 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41848 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2389144AbgLDACi (ORCPT ); Thu, 3 Dec 2020 19:02:38 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXj-0007ka-DG; Fri, 04 Dec 2020 00:01:47 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 18/40] namei: handle idmapped mounts in may_*() helpers Date: Fri, 4 Dec 2020 00:57:14 +0100 Message-Id: <20201203235736.3528991-19-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The may_follow_link(), may_linkat(), may_lookup(), may_open(), may_o_create(), may_create_in_sticky(), may_delete(), and may_create() helpers determine whether the caller is privileged enough to perform the associated operations. Let them handle idmapped mounts by mapping the inode or fsids according to the mount's user namespace. Afterwards the checks are identical to non-idmapped inodes. The patch takes care to retrieve the mount's user namespace right before performing permission checks and passing it down into the fileystem so the user namespace can't change in between by someone idmapping a mount that is currently not idmapped. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/btrfs/ioctl.c | 5 +- fs/init.c | 2 +- fs/inode.c | 2 +- fs/internal.h | 2 +- fs/namei.c | 147 ++++++++++++++++++++++++++++----------------- fs/xattr.c | 2 +- include/linux/fs.h | 14 +++-- 7 files changed, 108 insertions(+), 66 deletions(-) diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c index f5d3ebca4c5f..114bd15304f2 100644 --- a/fs/btrfs/ioctl.c +++ b/fs/btrfs/ioctl.c @@ -915,8 +915,9 @@ static int btrfs_may_delete(struct inode *dir, struct dentry *victim, int isdir) return error; if (IS_APPEND(dir)) return -EPERM; - if (check_sticky(dir, d_inode(victim)) || IS_APPEND(d_inode(victim)) || - IS_IMMUTABLE(d_inode(victim)) || IS_SWAPFILE(d_inode(victim))) + if (check_sticky(&init_user_ns, dir, d_inode(victim)) || + IS_APPEND(d_inode(victim)) || IS_IMMUTABLE(d_inode(victim)) || + IS_SWAPFILE(d_inode(victim))) return -EPERM; if (isdir) { if (!d_is_dir(victim)) diff --git a/fs/init.c b/fs/init.c index 2b4842f4802b..dd3d698afcd8 100644 --- a/fs/init.c +++ b/fs/init.c @@ -184,7 +184,7 @@ int __init init_link(const char *oldname, const char *newname) error = -EXDEV; if (old_path.mnt != new_path.mnt) goto out_dput; - error = may_linkat(&old_path); + error = may_linkat(&init_user_ns, &old_path); if (unlikely(error)) goto out_dput; error = security_path_link(old_path.dentry, &new_path, new_dentry); diff --git a/fs/inode.c b/fs/inode.c index d96710878fba..159b55538347 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -1797,7 +1797,7 @@ bool atime_needs_update(const struct path *path, struct inode *inode) /* Atime updates will likely cause i_uid and i_gid to be written * back improprely if their true value is unknown to the vfs. */ - if (HAS_UNMAPPED_ID(inode)) + if (HAS_UNMAPPED_ID(mnt_user_ns(mnt), inode)) return false; if (IS_NOATIME(inode)) diff --git a/fs/internal.h b/fs/internal.h index a7cd0f64faa4..31d85bfbc3fd 100644 --- a/fs/internal.h +++ b/fs/internal.h @@ -77,7 +77,7 @@ extern int vfs_path_lookup(struct dentry *, struct vfsmount *, const char *, unsigned int, struct path *); long do_rmdir(int dfd, struct filename *name); long do_unlinkat(int dfd, struct filename *name); -int may_linkat(struct path *link); +int may_linkat(struct user_namespace *mnt_userns, struct path *link); /* * namespace.c diff --git a/fs/namei.c b/fs/namei.c index aed328571a6b..a023d35174bd 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -504,7 +504,7 @@ int inode_permission(struct user_namespace *mnt_userns, * written back improperly if their true value is unknown * to the vfs. */ - if (HAS_UNMAPPED_ID(inode)) + if (HAS_UNMAPPED_ID(mnt_userns, inode)) return -EACCES; } @@ -1002,11 +1002,16 @@ int sysctl_protected_regular __read_mostly; */ static inline int may_follow_link(struct nameidata *nd, const struct inode *inode) { + struct user_namespace *mnt_userns; + kuid_t i_uid; + if (!sysctl_protected_symlinks) return 0; + mnt_userns = mnt_user_ns(nd->path.mnt); + i_uid = i_uid_into_mnt(mnt_userns, inode); /* Allowed if owner and follower match. */ - if (uid_eq(current_cred()->fsuid, inode->i_uid)) + if (uid_eq(current_cred()->fsuid, i_uid)) return 0; /* Allowed if parent directory not sticky and world-writable. */ @@ -1014,7 +1019,7 @@ static inline int may_follow_link(struct nameidata *nd, const struct inode *inod return 0; /* Allowed if parent directory and link owner match. */ - if (uid_valid(nd->dir_uid) && uid_eq(nd->dir_uid, inode->i_uid)) + if (uid_valid(nd->dir_uid) && uid_eq(nd->dir_uid, i_uid)) return 0; if (nd->flags & LOOKUP_RCU) @@ -1027,6 +1032,7 @@ static inline int may_follow_link(struct nameidata *nd, const struct inode *inod /** * safe_hardlink_source - Check for safe hardlink conditions + * @mnt_userns: user namespace of the mount the inode was found from * @inode: the source inode to hardlink from * * Return false if at least one of the following conditions: @@ -1037,7 +1043,8 @@ static inline int may_follow_link(struct nameidata *nd, const struct inode *inod * * Otherwise returns true. */ -static bool safe_hardlink_source(struct inode *inode) +static bool safe_hardlink_source(struct user_namespace *mnt_userns, + struct inode *inode) { umode_t mode = inode->i_mode; @@ -1054,7 +1061,7 @@ static bool safe_hardlink_source(struct inode *inode) return false; /* Hardlinking to unreadable or unwritable sources is dangerous. */ - if (inode_permission(&init_user_ns, inode, MAY_READ | MAY_WRITE)) + if (inode_permission(mnt_userns, inode, MAY_READ | MAY_WRITE)) return false; return true; @@ -1062,6 +1069,7 @@ static bool safe_hardlink_source(struct inode *inode) /** * may_linkat - Check permissions for creating a hardlink + * @mnt_userns: user namespace of the mount the inode was found from * @link: the source to hardlink from * * Block hardlink when all of: @@ -1070,14 +1078,21 @@ static bool safe_hardlink_source(struct inode *inode) * - hardlink source is unsafe (see safe_hardlink_source() above) * - not CAP_FOWNER in a namespace with the inode owner uid mapped * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * Returns 0 if successful, -ve on error. */ -int may_linkat(struct path *link) +int may_linkat(struct user_namespace *mnt_userns, struct path *link) { struct inode *inode = link->dentry->d_inode; /* Inode writeback is not safe when the uid or gid are invalid. */ - if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid)) + if (!uid_valid(i_uid_into_mnt(mnt_userns, inode)) || + !gid_valid(i_gid_into_mnt(mnt_userns, inode))) return -EOVERFLOW; if (!sysctl_protected_hardlinks) @@ -1086,7 +1101,8 @@ int may_linkat(struct path *link) /* Source inode owner (or CAP_FOWNER) can hardlink all they like, * otherwise, it must be a safe source. */ - if (safe_hardlink_source(inode) || inode_owner_or_capable(&init_user_ns, inode)) + if (safe_hardlink_source(mnt_userns, inode) || + inode_owner_or_capable(mnt_userns, inode)) return 0; audit_log_path_denied(AUDIT_ANOM_LINK, "linkat"); @@ -1097,6 +1113,7 @@ int may_linkat(struct path *link) * may_create_in_sticky - Check whether an O_CREAT open in a sticky directory * should be allowed, or not, on files that already * exist. + * @mnt_userns: user namespace of the mount the inode was found from * @dir_mode: mode bits of directory * @dir_uid: owner of directory * @inode: the inode of the file to open @@ -1112,16 +1129,25 @@ int may_linkat(struct path *link) * the directory doesn't have to be world writable: being group writable will * be enough. * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + * * Returns 0 if the open is allowed, -ve on error. */ -static int may_create_in_sticky(umode_t dir_mode, kuid_t dir_uid, - struct inode * const inode) +static int may_create_in_sticky(struct user_namespace *mnt_userns, + struct nameidata *nd, struct inode *const inode) { + umode_t dir_mode = nd->dir_mode; + kuid_t dir_uid = nd->dir_uid; + if ((!sysctl_protected_fifos && S_ISFIFO(inode->i_mode)) || (!sysctl_protected_regular && S_ISREG(inode->i_mode)) || likely(!(dir_mode & S_ISVTX)) || - uid_eq(inode->i_uid, dir_uid) || - uid_eq(current_fsuid(), inode->i_uid)) + uid_eq(i_uid_into_mnt(mnt_userns, inode), dir_uid) || + uid_eq(current_fsuid(), i_uid_into_mnt(mnt_userns, inode))) return 0; if (likely(dir_mode & 0002) || @@ -1611,17 +1637,18 @@ static struct dentry *lookup_slow(const struct qstr *name, return res; } -static inline int may_lookup(struct nameidata *nd) +static inline int may_lookup(struct user_namespace *mnt_userns, + struct nameidata *nd) { if (nd->flags & LOOKUP_RCU) { - int err = inode_permission(&init_user_ns, nd->inode, + int err = inode_permission(mnt_userns, nd->inode, MAY_EXEC | MAY_NOT_BLOCK); if (err != -ECHILD) return err; if (unlazy_walk(nd)) return -ECHILD; } - return inode_permission(&init_user_ns, nd->inode, MAY_EXEC); + return inode_permission(mnt_userns, nd->inode, MAY_EXEC); } static int reserve_stack(struct nameidata *nd, struct path *link, unsigned seq) @@ -2172,7 +2199,7 @@ static int link_path_walk(const char *name, struct nameidata *nd) u64 hash_len; int type; - err = may_lookup(nd); + err = may_lookup(&init_user_ns, nd); if (err) return err; @@ -2220,7 +2247,7 @@ static int link_path_walk(const char *name, struct nameidata *nd) OK: /* pathname or trailing symlink, done */ if (!depth) { - nd->dir_uid = nd->inode->i_uid; + nd->dir_uid = i_uid_into_mnt(&init_user_ns, nd->inode); nd->dir_mode = nd->inode->i_mode; nd->flags &= ~LOOKUP_PARENT; return 0; @@ -2698,15 +2725,16 @@ int user_path_at_empty(int dfd, const char __user *name, unsigned flags, } EXPORT_SYMBOL(user_path_at_empty); -int __check_sticky(struct inode *dir, struct inode *inode) +int __check_sticky(struct user_namespace *mnt_userns, struct inode *dir, + struct inode *inode) { kuid_t fsuid = current_fsuid(); - if (uid_eq(inode->i_uid, fsuid)) + if (uid_eq(i_uid_into_mnt(mnt_userns, inode), fsuid)) return 0; - if (uid_eq(dir->i_uid, fsuid)) + if (uid_eq(i_uid_into_mnt(mnt_userns, dir), fsuid)) return 0; - return !capable_wrt_inode_uidgid(&init_user_ns, inode, CAP_FOWNER); + return !capable_wrt_inode_uidgid(mnt_userns, inode, CAP_FOWNER); } EXPORT_SYMBOL(__check_sticky); @@ -2730,7 +2758,8 @@ EXPORT_SYMBOL(__check_sticky); * 11. We don't allow removal of NFS sillyrenamed files; it's handled by * nfs_async_unlink(). */ -static int may_delete(struct inode *dir, struct dentry *victim, bool isdir) +static int may_delete(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *victim, bool isdir) { struct inode *inode = d_backing_inode(victim); int error; @@ -2742,19 +2771,21 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir) BUG_ON(victim->d_parent->d_inode != dir); /* Inode writeback is not safe when the uid or gid are invalid. */ - if (!uid_valid(inode->i_uid) || !gid_valid(inode->i_gid)) + if (!uid_valid(i_uid_into_mnt(mnt_userns, inode)) || + !gid_valid(i_gid_into_mnt(mnt_userns, inode))) return -EOVERFLOW; audit_inode_child(dir, victim, AUDIT_TYPE_CHILD_DELETE); - error = inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); + error = inode_permission(mnt_userns, dir, MAY_WRITE | MAY_EXEC); if (error) return error; if (IS_APPEND(dir)) return -EPERM; - if (check_sticky(dir, inode) || IS_APPEND(inode) || - IS_IMMUTABLE(inode) || IS_SWAPFILE(inode) || HAS_UNMAPPED_ID(inode)) + if (check_sticky(mnt_userns, dir, inode) || IS_APPEND(inode) || + IS_IMMUTABLE(inode) || IS_SWAPFILE(inode) || + HAS_UNMAPPED_ID(mnt_userns, inode)) return -EPERM; if (isdir) { if (!d_is_dir(victim)) @@ -2779,7 +2810,8 @@ static int may_delete(struct inode *dir, struct dentry *victim, bool isdir) * 4. We should have write and exec permissions on dir * 5. We can't do it if dir is immutable (done in permission()) */ -static inline int may_create(struct inode *dir, struct dentry *child) +static inline int may_create(struct user_namespace *mnt_userns, + struct inode *dir, struct dentry *child) { struct user_namespace *s_user_ns; audit_inode_child(dir, child, AUDIT_TYPE_CHILD_CREATE); @@ -2788,10 +2820,10 @@ static inline int may_create(struct inode *dir, struct dentry *child) if (IS_DEADDIR(dir)) return -ENOENT; s_user_ns = dir->i_sb->s_user_ns; - if (!kuid_has_mapping(s_user_ns, current_fsuid()) || - !kgid_has_mapping(s_user_ns, current_fsgid())) + if (!kuid_has_mapping(s_user_ns, fsuid_into_mnt(mnt_userns)) || + !kgid_has_mapping(s_user_ns, fsgid_into_mnt(mnt_userns))) return -EOVERFLOW; - return inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); + return inode_permission(mnt_userns, dir, MAY_WRITE | MAY_EXEC); } /* @@ -2841,7 +2873,7 @@ EXPORT_SYMBOL(unlock_rename); int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool want_excl) { - int error = may_create(dir, dentry); + int error = may_create(&init_user_ns, dir, dentry); if (error) return error; @@ -2864,7 +2896,7 @@ int vfs_mkobj(struct dentry *dentry, umode_t mode, void *arg) { struct inode *dir = dentry->d_parent->d_inode; - int error = may_create(dir, dentry); + int error = may_create(&init_user_ns, dir, dentry); if (error) return error; @@ -2886,7 +2918,8 @@ bool may_open_dev(const struct path *path) !(path->mnt->mnt_sb->s_iflags & SB_I_NODEV); } -static int may_open(const struct path *path, int acc_mode, int flag) +static int may_open(struct user_namespace *mnt_userns, const struct path *path, + int acc_mode, int flag) { struct dentry *dentry = path->dentry; struct inode *inode = dentry->d_inode; @@ -2921,7 +2954,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) break; } - error = inode_permission(&init_user_ns, inode, MAY_OPEN | acc_mode); + error = inode_permission(mnt_userns, inode, MAY_OPEN | acc_mode); if (error) return error; @@ -2936,7 +2969,7 @@ static int may_open(const struct path *path, int acc_mode, int flag) } /* O_NOATIME can only be set by the owner or superuser */ - if (flag & O_NOATIME && !inode_owner_or_capable(&init_user_ns, inode)) + if (flag & O_NOATIME && !inode_owner_or_capable(mnt_userns, inode)) return -EPERM; return 0; @@ -2971,7 +3004,9 @@ static inline int open_to_namei_flags(int flag) return flag; } -static int may_o_create(const struct path *dir, struct dentry *dentry, umode_t mode) +static int may_o_create(struct user_namespace *mnt_userns, + const struct path *dir, struct dentry *dentry, + umode_t mode) { struct user_namespace *s_user_ns; int error = security_path_mknod(dir, dentry, mode, 0); @@ -2979,11 +3014,11 @@ static int may_o_create(const struct path *dir, struct dentry *dentry, umode_t m return error; s_user_ns = dir->dentry->d_sb->s_user_ns; - if (!kuid_has_mapping(s_user_ns, current_fsuid()) || - !kgid_has_mapping(s_user_ns, current_fsgid())) + if (!kuid_has_mapping(s_user_ns, fsuid_into_mnt(mnt_userns)) || + !kgid_has_mapping(s_user_ns, fsgid_into_mnt(mnt_userns))) return -EOVERFLOW; - error = inode_permission(&init_user_ns, dir->dentry->d_inode, + error = inode_permission(mnt_userns, dir->dentry->d_inode, MAY_WRITE | MAY_EXEC); if (error) return error; @@ -3116,7 +3151,8 @@ static struct dentry *lookup_open(struct nameidata *nd, struct file *file, if (!IS_POSIXACL(dir->d_inode)) mode &= ~current_umask(); if (likely(got_write)) - create_error = may_o_create(&nd->path, dentry, mode); + create_error = may_o_create(&init_user_ns, &nd->path, + dentry, mode); else create_error = -EROFS; } @@ -3277,7 +3313,7 @@ static int do_open(struct nameidata *nd, return -EEXIST; if (d_is_dir(nd->path.dentry)) return -EISDIR; - error = may_create_in_sticky(nd->dir_mode, nd->dir_uid, + error = may_create_in_sticky(&init_user_ns, nd, d_backing_inode(nd->path.dentry)); if (unlikely(error)) return error; @@ -3297,7 +3333,7 @@ static int do_open(struct nameidata *nd, return error; do_truncate = true; } - error = may_open(&nd->path, acc_mode, open_flag); + error = may_open(&init_user_ns, &nd->path, acc_mode, open_flag); if (!error && !(file->f_mode & FMODE_OPENED)) error = vfs_open(&nd->path, file); if (!error) @@ -3372,7 +3408,7 @@ static int do_tmpfile(struct nameidata *nd, unsigned flags, path.dentry = child; audit_inode(nd->name, child, 0); /* Don't check for other permissions, the inode was just created */ - error = may_open(&path, 0, op->open_flag); + error = may_open(&init_user_ns, &path, 0, op->open_flag); if (error) goto out2; file->f_path.mnt = path.mnt; @@ -3579,7 +3615,7 @@ EXPORT_SYMBOL(user_path_create); int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { bool is_whiteout = S_ISCHR(mode) && dev == WHITEOUT_DEV; - int error = may_create(dir, dentry); + int error = may_create(&init_user_ns, dir, dentry); if (error) return error; @@ -3680,7 +3716,7 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { - int error = may_create(dir, dentry); + int error = may_create(&init_user_ns, dir, dentry); unsigned max_links = dir->i_sb->s_max_links; if (error) @@ -3741,7 +3777,7 @@ SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode) int vfs_rmdir(struct inode *dir, struct dentry *dentry) { - int error = may_delete(dir, dentry, 1); + int error = may_delete(&init_user_ns, dir, dentry, 1); if (error) return error; @@ -3863,7 +3899,7 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname) int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode) { struct inode *target = dentry->d_inode; - int error = may_delete(dir, dentry, 0); + int error = may_delete(&init_user_ns, dir, dentry, 0); if (error) return error; @@ -3995,7 +4031,7 @@ SYSCALL_DEFINE1(unlink, const char __user *, pathname) int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname) { - int error = may_create(dir, dentry); + int error = may_create(&init_user_ns, dir, dentry); if (error) return error; @@ -4084,7 +4120,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de if (!inode) return -ENOENT; - error = may_create(dir, new_dentry); + error = may_create(&init_user_ns, dir, new_dentry); if (error) return error; @@ -4101,7 +4137,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de * be writen back improperly if their true value is unknown to * the vfs. */ - if (HAS_UNMAPPED_ID(inode)) + if (HAS_UNMAPPED_ID(&init_user_ns, inode)) return -EPERM; if (!dir->i_op->link) return -EPERM; @@ -4183,7 +4219,7 @@ static int do_linkat(int olddfd, const char __user *oldname, int newdfd, error = -EXDEV; if (old_path.mnt != new_path.mnt) goto out_dput; - error = may_linkat(&old_path); + error = may_linkat(&init_user_ns, &old_path); if (unlikely(error)) goto out_dput; error = security_path_link(old_path.dentry, &new_path, new_dentry); @@ -4276,6 +4312,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct inode **delegated_inode, unsigned int flags) { int error; + struct user_namespace *mnt_userns = &init_user_ns; bool is_dir = d_is_dir(old_dentry); struct inode *source = old_dentry->d_inode; struct inode *target = new_dentry->d_inode; @@ -4286,19 +4323,19 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, if (source == target) return 0; - error = may_delete(old_dir, old_dentry, is_dir); + error = may_delete(mnt_userns, old_dir, old_dentry, is_dir); if (error) return error; if (!target) { - error = may_create(new_dir, new_dentry); + error = may_create(mnt_userns, new_dir, new_dentry); } else { new_is_dir = d_is_dir(new_dentry); if (!(flags & RENAME_EXCHANGE)) - error = may_delete(new_dir, new_dentry, is_dir); + error = may_delete(mnt_userns, new_dir, new_dentry, is_dir); else - error = may_delete(new_dir, new_dentry, new_is_dir); + error = may_delete(mnt_userns, new_dir, new_dentry, new_is_dir); } if (error) return error; diff --git a/fs/xattr.c b/fs/xattr.c index 0d1bfeacfa23..0424ab0912a3 100644 --- a/fs/xattr.c +++ b/fs/xattr.c @@ -98,7 +98,7 @@ xattr_permission(struct user_namespace *mnt_userns, struct inode *inode, * to be writen back improperly if their true value is * unknown to the vfs. */ - if (HAS_UNMAPPED_ID(inode)) + if (HAS_UNMAPPED_ID(mnt_userns, inode)) return -EPERM; } diff --git a/include/linux/fs.h b/include/linux/fs.h index e4d5a7c82136..db6a1137856d 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2085,9 +2085,11 @@ static inline bool sb_rdonly(const struct super_block *sb) { return sb->s_flags #define IS_WHITEOUT(inode) (S_ISCHR(inode->i_mode) && \ (inode)->i_rdev == WHITEOUT_DEV) -static inline bool HAS_UNMAPPED_ID(struct inode *inode) +static inline bool HAS_UNMAPPED_ID(struct user_namespace *mnt_userns, + struct inode *inode) { - return !uid_valid(inode->i_uid) || !gid_valid(inode->i_gid); + return !uid_valid(i_uid_into_mnt(mnt_userns, inode)) || + !gid_valid(i_gid_into_mnt(mnt_userns, inode)); } static inline enum rw_hint file_write_hint(struct file *file) @@ -2811,7 +2813,8 @@ extern int notify_change(struct user_namespace *, struct dentry *, struct iattr *, struct inode **); extern int inode_permission(struct user_namespace *, struct inode *, int); extern int generic_permission(struct user_namespace *, struct inode *, int); -extern int __check_sticky(struct inode *dir, struct inode *inode); +extern int __check_sticky(struct user_namespace *mnt_userns, struct inode *dir, + struct inode *inode); static inline bool execute_ok(struct inode *inode) { @@ -3432,12 +3435,13 @@ static inline bool is_sxid(umode_t mode) return (mode & S_ISUID) || ((mode & S_ISGID) && (mode & S_IXGRP)); } -static inline int check_sticky(struct inode *dir, struct inode *inode) +static inline int check_sticky(struct user_namespace *mnt_userns, + struct inode *dir, struct inode *inode) { if (!(dir->i_mode & S_ISVTX)) return 0; - return __check_sticky(dir, inode); + return __check_sticky(mnt_userns, dir, inode); } static inline void inode_has_no_xattr(struct inode *inode) From patchwork Thu Dec 3 23:57:15 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950103 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D6227C2BBCD for ; Fri, 4 Dec 2020 00:03:10 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id AC2D222285 for ; Fri, 4 Dec 2020 00:03:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389444AbgLDACk (ORCPT ); Thu, 3 Dec 2020 19:02:40 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:41851 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2389191AbgLDACe (ORCPT ); Thu, 3 Dec 2020 19:02:34 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXl-0007ka-7z; Fri, 04 Dec 2020 00:01:49 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 19/40] namei: introduce struct renamedata Date: Fri, 4 Dec 2020 00:57:15 +0100 Message-Id: <20201203235736.3528991-20-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org In order to handle idmapped mounts we will extend the vfs rename helper to take two new arguments in follow up patches. Since this operations already takes a bunch of arguments add a simple struct renamedata (based on struct nameidata) and make the current helper use it before we extend it. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. - Christoph Hellwig : - Declare variable on separate lines instead of chaining them and wrapping with weird indentation. --- fs/cachefiles/namei.c | 9 +++++++-- fs/ecryptfs/inode.c | 10 +++++++--- fs/namei.c | 21 +++++++++++++++------ fs/nfsd/vfs.c | 8 +++++++- fs/overlayfs/overlayfs.h | 9 ++++++++- include/linux/fs.h | 12 +++++++++++- 6 files changed, 55 insertions(+), 14 deletions(-) diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index ecc8ecbbfa5a..7b987de0babe 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -412,9 +412,14 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, if (ret < 0) { cachefiles_io_error(cache, "Rename security error %d", ret); } else { + struct renamedata rd = { + .old_dir = d_inode(dir), + .old_dentry = rep, + .new_dir = d_inode(cache->graveyard), + .new_dentry = grave, + }; trace_cachefiles_rename(object, rep, grave, why); - ret = vfs_rename(d_inode(dir), rep, - d_inode(cache->graveyard), grave, NULL, 0); + ret = vfs_rename(&rd); if (ret != 0 && ret != -ENOMEM) cachefiles_io_error(cache, "Rename failed with error %d", ret); diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 0a218d7c3023..0697480ee2eb 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -590,6 +590,7 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry, struct dentry *lower_new_dir_dentry; struct dentry *trap; struct inode *target_inode; + struct renamedata rd = {}; if (flags) return -EINVAL; @@ -619,9 +620,12 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry, rc = -ENOTEMPTY; goto out_lock; } - rc = vfs_rename(d_inode(lower_old_dir_dentry), lower_old_dentry, - d_inode(lower_new_dir_dentry), lower_new_dentry, - NULL, 0); + + rd.old_dir = d_inode(lower_old_dir_dentry); + rd.old_dentry = lower_old_dentry; + rd.new_dir = d_inode(lower_new_dir_dentry); + rd.new_dentry = lower_new_dentry; + rc = vfs_rename(&rd); if (rc) goto out_lock; if (target_inode) diff --git a/fs/namei.c b/fs/namei.c index a023d35174bd..453920b493b0 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4307,12 +4307,15 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname * ->i_mutex on parents, which works but leads to some truly excessive * locking]. */ -int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry, - struct inode **delegated_inode, unsigned int flags) +int vfs_rename(struct renamedata *rd) { int error; struct user_namespace *mnt_userns = &init_user_ns; + struct inode *old_dir = rd->old_dir, *new_dir = rd->new_dir; + struct dentry *old_dentry = rd->old_dentry; + struct dentry *new_dentry = rd->new_dentry; + struct inode **delegated_inode = rd->delegated_inode; + unsigned int flags = rd->flags; bool is_dir = d_is_dir(old_dentry); struct inode *source = old_dentry->d_inode; struct inode *target = new_dentry->d_inode; @@ -4436,6 +4439,7 @@ EXPORT_SYMBOL(vfs_rename); static int do_renameat2(int olddfd, const char __user *oldname, int newdfd, const char __user *newname, unsigned int flags) { + struct renamedata rd; struct dentry *old_dentry, *new_dentry; struct dentry *trap; struct path old_path, new_path; @@ -4541,9 +4545,14 @@ static int do_renameat2(int olddfd, const char __user *oldname, int newdfd, &new_path, new_dentry, flags); if (error) goto exit5; - error = vfs_rename(old_path.dentry->d_inode, old_dentry, - new_path.dentry->d_inode, new_dentry, - &delegated_inode, flags); + + rd.old_dir = old_path.dentry->d_inode; + rd.old_dentry = old_dentry; + rd.new_dir = new_path.dentry->d_inode; + rd.new_dentry = new_dentry; + rd.delegated_inode = &delegated_inode; + rd.flags = flags; + error = vfs_rename(&rd); exit5: dput(new_dentry); exit4: diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 9a0e0e5b34f5..3d7a8cd61098 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1787,7 +1787,13 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen, has_cached = true; goto out_dput_old; } else { - host_err = vfs_rename(fdir, odentry, tdir, ndentry, NULL, 0); + struct renamedata rd = { + .old_dir = fdir, + .old_dentry = odentry, + .new_dir = tdir, + .new_dentry = ndentry, + }; + host_err = vfs_rename(&rd); if (!host_err) { host_err = commit_metadata(tfhp); if (!host_err) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index bf26fb3fa2c1..73da8710b0f0 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -212,9 +212,16 @@ static inline int ovl_do_rename(struct inode *olddir, struct dentry *olddentry, unsigned int flags) { int err; + struct renamedata rd = { + .old_dir = olddir, + .old_dentry = olddentry, + .new_dir = newdir, + .new_dentry = newdentry, + .flags = flags, + }; pr_debug("rename(%pd2, %pd2, 0x%x)\n", olddentry, newdentry, flags); - err = vfs_rename(olddir, olddentry, newdir, newdentry, NULL, flags); + err = vfs_rename(&rd); if (err) { pr_debug("...rename(%pd2, %pd2, ...) = %i\n", olddentry, newdentry, err); diff --git a/include/linux/fs.h b/include/linux/fs.h index db6a1137856d..6f0fb7d8d2c4 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1776,7 +1776,17 @@ extern int vfs_symlink(struct inode *, struct dentry *, const char *); extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **); extern int vfs_rmdir(struct inode *, struct dentry *); extern int vfs_unlink(struct inode *, struct dentry *, struct inode **); -extern int vfs_rename(struct inode *, struct dentry *, struct inode *, struct dentry *, struct inode **, unsigned int); + +struct renamedata { + struct inode *old_dir; + struct dentry *old_dentry; + struct inode *new_dir; + struct dentry *new_dentry; + struct inode **delegated_inode; + unsigned int flags; +} __randomize_layout; + +extern int vfs_rename(struct renamedata *); static inline int vfs_whiteout(struct inode *dir, struct dentry *dentry) { From patchwork Thu Dec 3 23:57:16 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950209 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 15664C4167B for ; Fri, 4 Dec 2020 00:08:58 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CD1CF221E3 for ; Fri, 4 Dec 2020 00:08:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389493AbgLDAI5 (ORCPT ); Thu, 3 Dec 2020 19:08:57 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42644 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAI4 (ORCPT ); Thu, 3 Dec 2020 19:08:56 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXn-0007ka-1l; Fri, 04 Dec 2020 00:01:51 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 20/40] namei: prepare for idmapped mounts Date: Fri, 4 Dec 2020 00:57:16 +0100 Message-Id: <20201203235736.3528991-21-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The various vfs_*() helpers are called by filesystems or by the vfs itself to perform core operations create, link, mkdir, mknod, rename, rmdir, tmpfile and unlink. Enable them to handle idmapped mounts. If the inode is accessed through an idmapped mount it is mapped according to the mount's user namespace. Afterwards the checks and operations are identical to non-idmapped mounts. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Don't pollute the vfs with additional helpers simply extend the existing helpers with an additional argument and switch all callers. /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- drivers/base/devtmpfs.c | 8 +- fs/cachefiles/namei.c | 10 +- fs/ecryptfs/inode.c | 30 +++--- fs/init.c | 12 +-- fs/namei.c | 224 +++++++++++++++++++++++++++++++-------- fs/nfsd/nfs4recover.c | 6 +- fs/nfsd/vfs.c | 19 ++-- fs/overlayfs/dir.c | 4 +- fs/overlayfs/overlayfs.h | 20 ++-- include/linux/fs.h | 25 +++-- ipc/mqueue.c | 2 +- net/unix/af_unix.c | 2 +- 12 files changed, 259 insertions(+), 103 deletions(-) diff --git a/drivers/base/devtmpfs.c b/drivers/base/devtmpfs.c index 2e0c3cdb4184..fd4e86c58111 100644 --- a/drivers/base/devtmpfs.c +++ b/drivers/base/devtmpfs.c @@ -162,7 +162,7 @@ static int dev_mkdir(const char *name, umode_t mode) if (IS_ERR(dentry)) return PTR_ERR(dentry); - err = vfs_mkdir(d_inode(path.dentry), dentry, mode); + err = vfs_mkdir(&init_user_ns, d_inode(path.dentry), dentry, mode); if (!err) /* mark as kernel-created inode */ d_inode(dentry)->i_private = &thread; @@ -212,7 +212,7 @@ static int handle_create(const char *nodename, umode_t mode, kuid_t uid, if (IS_ERR(dentry)) return PTR_ERR(dentry); - err = vfs_mknod(d_inode(path.dentry), dentry, mode, dev->devt); + err = vfs_mknod(&init_user_ns, d_inode(path.dentry), dentry, mode, dev->devt); if (!err) { struct iattr newattrs; @@ -242,7 +242,7 @@ static int dev_rmdir(const char *name) return PTR_ERR(dentry); if (d_really_is_positive(dentry)) { if (d_inode(dentry)->i_private == &thread) - err = vfs_rmdir(d_inode(parent.dentry), dentry); + err = vfs_rmdir(&init_user_ns, d_inode(parent.dentry), dentry); else err = -EPERM; } else { @@ -330,7 +330,7 @@ static int handle_remove(const char *nodename, struct device *dev) inode_lock(d_inode(dentry)); notify_change(&init_user_ns, dentry, &newattrs, NULL); inode_unlock(d_inode(dentry)); - err = vfs_unlink(d_inode(parent.dentry), dentry, NULL); + err = vfs_unlink(&init_user_ns, d_inode(parent.dentry), dentry, NULL); if (!err || err == -ENOENT) deleted = 1; } diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c index 7b987de0babe..78e1deed3af0 100644 --- a/fs/cachefiles/namei.c +++ b/fs/cachefiles/namei.c @@ -311,7 +311,7 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, cachefiles_io_error(cache, "Unlink security error"); } else { trace_cachefiles_unlink(object, rep, why); - ret = vfs_unlink(d_inode(dir), rep, NULL); + ret = vfs_unlink(&init_user_ns, d_inode(dir), rep, NULL); if (preemptive) cachefiles_mark_object_buried(cache, rep, why); @@ -413,8 +413,10 @@ static int cachefiles_bury_object(struct cachefiles_cache *cache, cachefiles_io_error(cache, "Rename security error %d", ret); } else { struct renamedata rd = { + .old_mnt_userns = &init_user_ns, .old_dir = d_inode(dir), .old_dentry = rep, + .new_mnt_userns = &init_user_ns, .new_dir = d_inode(cache->graveyard), .new_dentry = grave, }; @@ -566,7 +568,7 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, if (ret < 0) goto create_error; start = jiffies; - ret = vfs_mkdir(d_inode(dir), next, 0); + ret = vfs_mkdir(&init_user_ns, d_inode(dir), next, 0); cachefiles_hist(cachefiles_mkdir_histogram, start); if (!key) trace_cachefiles_mkdir(object, next, ret); @@ -602,7 +604,7 @@ int cachefiles_walk_to_object(struct cachefiles_object *parent, if (ret < 0) goto create_error; start = jiffies; - ret = vfs_create(d_inode(dir), next, S_IFREG, true); + ret = vfs_create(&init_user_ns, d_inode(dir), next, S_IFREG, true); cachefiles_hist(cachefiles_create_histogram, start); trace_cachefiles_create(object, next, ret); if (ret < 0) @@ -796,7 +798,7 @@ struct dentry *cachefiles_get_directory(struct cachefiles_cache *cache, ret = security_path_mkdir(&path, subdir, 0700); if (ret < 0) goto mkdir_error; - ret = vfs_mkdir(d_inode(dir), subdir, 0700); + ret = vfs_mkdir(&init_user_ns, d_inode(dir), subdir, 0700); if (ret < 0) goto mkdir_error; diff --git a/fs/ecryptfs/inode.c b/fs/ecryptfs/inode.c index 0697480ee2eb..0c97a1c37e5d 100644 --- a/fs/ecryptfs/inode.c +++ b/fs/ecryptfs/inode.c @@ -141,7 +141,7 @@ static int ecryptfs_do_unlink(struct inode *dir, struct dentry *dentry, else if (d_unhashed(lower_dentry)) rc = -EINVAL; else - rc = vfs_unlink(lower_dir_inode, lower_dentry, NULL); + rc = vfs_unlink(&init_user_ns, lower_dir_inode, lower_dentry, NULL); if (rc) { printk(KERN_ERR "Error in vfs_unlink; rc = [%d]\n", rc); goto out_unlock; @@ -180,7 +180,8 @@ ecryptfs_do_create(struct inode *directory_inode, lower_dentry = ecryptfs_dentry_to_lower(ecryptfs_dentry); lower_dir_dentry = lock_parent(lower_dentry); - rc = vfs_create(d_inode(lower_dir_dentry), lower_dentry, mode, true); + rc = vfs_create(&init_user_ns, d_inode(lower_dir_dentry), lower_dentry, + mode, true); if (rc) { printk(KERN_ERR "%s: Failure to create dentry in lower fs; " "rc = [%d]\n", __func__, rc); @@ -190,7 +191,7 @@ ecryptfs_do_create(struct inode *directory_inode, inode = __ecryptfs_get_inode(d_inode(lower_dentry), directory_inode->i_sb); if (IS_ERR(inode)) { - vfs_unlink(d_inode(lower_dir_dentry), lower_dentry, NULL); + vfs_unlink(&init_user_ns, d_inode(lower_dir_dentry), lower_dentry, NULL); goto out_lock; } fsstack_copy_attr_times(directory_inode, d_inode(lower_dir_dentry)); @@ -436,8 +437,8 @@ static int ecryptfs_link(struct dentry *old_dentry, struct inode *dir, dget(lower_old_dentry); dget(lower_new_dentry); lower_dir_dentry = lock_parent(lower_new_dentry); - rc = vfs_link(lower_old_dentry, d_inode(lower_dir_dentry), - lower_new_dentry, NULL); + rc = vfs_link(lower_old_dentry, &init_user_ns, + d_inode(lower_dir_dentry), lower_new_dentry, NULL); if (rc || d_really_is_negative(lower_new_dentry)) goto out_lock; rc = ecryptfs_interpose(lower_new_dentry, new_dentry, dir->i_sb); @@ -481,7 +482,7 @@ static int ecryptfs_symlink(struct inode *dir, struct dentry *dentry, strlen(symname)); if (rc) goto out_lock; - rc = vfs_symlink(d_inode(lower_dir_dentry), lower_dentry, + rc = vfs_symlink(&init_user_ns, d_inode(lower_dir_dentry), lower_dentry, encoded_symname); kfree(encoded_symname); if (rc || d_really_is_negative(lower_dentry)) @@ -507,7 +508,7 @@ static int ecryptfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode lower_dentry = ecryptfs_dentry_to_lower(dentry); lower_dir_dentry = lock_parent(lower_dentry); - rc = vfs_mkdir(d_inode(lower_dir_dentry), lower_dentry, mode); + rc = vfs_mkdir(&init_user_ns, d_inode(lower_dir_dentry), lower_dentry, mode); if (rc || d_really_is_negative(lower_dentry)) goto out; rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb); @@ -541,7 +542,7 @@ static int ecryptfs_rmdir(struct inode *dir, struct dentry *dentry) else if (d_unhashed(lower_dentry)) rc = -EINVAL; else - rc = vfs_rmdir(lower_dir_inode, lower_dentry); + rc = vfs_rmdir(&init_user_ns, lower_dir_inode, lower_dentry); if (!rc) { clear_nlink(d_inode(dentry)); fsstack_copy_attr_times(dir, lower_dir_inode); @@ -563,7 +564,8 @@ ecryptfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev lower_dentry = ecryptfs_dentry_to_lower(dentry); lower_dir_dentry = lock_parent(lower_dentry); - rc = vfs_mknod(d_inode(lower_dir_dentry), lower_dentry, mode, dev); + rc = vfs_mknod(&init_user_ns, d_inode(lower_dir_dentry), lower_dentry, + mode, dev); if (rc || d_really_is_negative(lower_dentry)) goto out; rc = ecryptfs_interpose(lower_dentry, dentry, dir->i_sb); @@ -621,10 +623,12 @@ ecryptfs_rename(struct inode *old_dir, struct dentry *old_dentry, goto out_lock; } - rd.old_dir = d_inode(lower_old_dir_dentry); - rd.old_dentry = lower_old_dentry; - rd.new_dir = d_inode(lower_new_dir_dentry); - rd.new_dentry = lower_new_dentry; + rd.old_mnt_userns = &init_user_ns; + rd.old_dir = d_inode(lower_old_dir_dentry); + rd.old_dentry = lower_old_dentry; + rd.new_mnt_userns = &init_user_ns; + rd.new_dir = d_inode(lower_new_dir_dentry); + rd.new_dentry = lower_new_dentry; rc = vfs_rename(&rd); if (rc) goto out_lock; diff --git a/fs/init.c b/fs/init.c index dd3d698afcd8..06d8e52ce18e 100644 --- a/fs/init.c +++ b/fs/init.c @@ -160,8 +160,8 @@ int __init init_mknod(const char *filename, umode_t mode, unsigned int dev) mode &= ~current_umask(); error = security_path_mknod(&path, dentry, mode, dev); if (!error) - error = vfs_mknod(path.dentry->d_inode, dentry, mode, - new_decode_dev(dev)); + error = vfs_mknod(&init_user_ns, path.dentry->d_inode, dentry, + mode, new_decode_dev(dev)); done_path_create(&path, dentry); return error; } @@ -190,8 +190,8 @@ int __init init_link(const char *oldname, const char *newname) error = security_path_link(old_path.dentry, &new_path, new_dentry); if (error) goto out_dput; - error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, - NULL); + error = vfs_link(old_path.dentry, &init_user_ns, + new_path.dentry->d_inode, new_dentry, NULL); out_dput: done_path_create(&new_path, new_dentry); out: @@ -210,7 +210,7 @@ int __init init_symlink(const char *oldname, const char *newname) return PTR_ERR(dentry); error = security_path_symlink(&path, dentry, oldname); if (!error) - error = vfs_symlink(path.dentry->d_inode, dentry, oldname); + error = vfs_symlink(&init_user_ns, path.dentry->d_inode, dentry, oldname); done_path_create(&path, dentry); return error; } @@ -233,7 +233,7 @@ int __init init_mkdir(const char *pathname, umode_t mode) mode &= ~current_umask(); error = security_path_mkdir(&path, dentry, mode); if (!error) - error = vfs_mkdir(path.dentry->d_inode, dentry, mode); + error = vfs_mkdir(&init_user_ns, path.dentry->d_inode, dentry, mode); done_path_create(&path, dentry); return error; } diff --git a/fs/namei.c b/fs/namei.c index 453920b493b0..6cc655310bd9 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -2870,10 +2870,26 @@ void unlock_rename(struct dentry *p1, struct dentry *p2) } EXPORT_SYMBOL(unlock_rename); -int vfs_create(struct inode *dir, struct dentry *dentry, umode_t mode, - bool want_excl) +/** + * vfs_create - create new file + * @mnt_userns: user namespace of the mount the inode was found from + * @dir: inode of @dentry + * @dentry: pointer to dentry of the base directory + * @mode: mode of the new file + * @want_excl: whether the file must not yet exist + * + * Create a new file. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +int vfs_create(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, umode_t mode, bool want_excl) { - int error = may_create(&init_user_ns, dir, dentry); + int error = may_create(mnt_userns, dir, dentry); if (error) return error; @@ -3349,7 +3365,23 @@ static int do_open(struct nameidata *nd, return error; } -struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, int open_flag) +/** + * vfs_tmpfile - create tmpfile + * @mnt_userns: user namespace of the mount the inode was found from + * @dentry: pointer to dentry of the base directory + * @mode: mode of the new tmpfile + * @open_flags: flags + * + * Create a temporary file. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +struct dentry *vfs_tmpfile(struct user_namespace *mnt_userns, + struct dentry *dentry, umode_t mode, int open_flag) { struct dentry *child = NULL; struct inode *dir = dentry->d_inode; @@ -3357,7 +3389,7 @@ struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, int open_flag) int error; /* we want directory to be writable */ - error = inode_permission(&init_user_ns, dir, MAY_WRITE | MAY_EXEC); + error = inode_permission(mnt_userns, dir, MAY_WRITE | MAY_EXEC); if (error) goto out_err; error = -EOPNOTSUPP; @@ -3392,6 +3424,7 @@ static int do_tmpfile(struct nameidata *nd, unsigned flags, const struct open_flags *op, struct file *file) { + struct user_namespace *mnt_userns; struct dentry *child; struct path path; int error = path_lookupat(nd, flags | LOOKUP_DIRECTORY, &path); @@ -3400,7 +3433,8 @@ static int do_tmpfile(struct nameidata *nd, unsigned flags, error = mnt_want_write(path.mnt); if (unlikely(error)) goto out; - child = vfs_tmpfile(path.dentry, op->mode, op->open_flag); + mnt_userns = mnt_user_ns(path.mnt); + child = vfs_tmpfile(mnt_userns, path.dentry, op->mode, op->open_flag); error = PTR_ERR(child); if (IS_ERR(child)) goto out2; @@ -3612,10 +3646,27 @@ inline struct dentry *user_path_create(int dfd, const char __user *pathname, } EXPORT_SYMBOL(user_path_create); -int vfs_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) +/** + * vfs_mknod - create device node or file + * @mnt_userns: user namespace of the mount the inode was found from + * @dir: inode of @dentry + * @dentry: pointer to dentry of the base directory + * @mode: mode of the new device node or file + * @dev: device number of device to create + * + * Create a device node or file. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +int vfs_mknod(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, umode_t mode, dev_t dev) { bool is_whiteout = S_ISCHR(mode) && dev == WHITEOUT_DEV; - int error = may_create(&init_user_ns, dir, dentry); + int error = may_create(mnt_userns, dir, dentry); if (error) return error; @@ -3662,6 +3713,7 @@ static int may_mknod(umode_t mode) static long do_mknodat(int dfd, const char __user *filename, umode_t mode, unsigned int dev) { + struct user_namespace *mnt_userns; struct dentry *dentry; struct path path; int error; @@ -3680,18 +3732,22 @@ static long do_mknodat(int dfd, const char __user *filename, umode_t mode, error = security_path_mknod(&path, dentry, mode, dev); if (error) goto out; + + mnt_userns = mnt_user_ns(path.mnt); switch (mode & S_IFMT) { case 0: case S_IFREG: - error = vfs_create(path.dentry->d_inode,dentry,mode,true); + error = vfs_create(mnt_userns, path.dentry->d_inode, + dentry, mode, true); if (!error) ima_post_path_mknod(dentry); break; case S_IFCHR: case S_IFBLK: - error = vfs_mknod(path.dentry->d_inode,dentry,mode, - new_decode_dev(dev)); + error = vfs_mknod(mnt_userns, path.dentry->d_inode, + dentry, mode, new_decode_dev(dev)); break; case S_IFIFO: case S_IFSOCK: - error = vfs_mknod(path.dentry->d_inode,dentry,mode,0); + error = vfs_mknod(mnt_userns, path.dentry->d_inode, + dentry, mode, 0); break; } out: @@ -3714,9 +3770,25 @@ SYSCALL_DEFINE3(mknod, const char __user *, filename, umode_t, mode, unsigned, d return do_mknodat(AT_FDCWD, filename, mode, dev); } -int vfs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) +/** + * vfs_mkdir - create directory + * @mnt_userns: user namespace of the mount the inode was found from + * @dir: inode of @dentry + * @dentry: pointer to dentry of the base directory + * @mode: mode of the new directory + * + * Create a directory. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +int vfs_mkdir(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, umode_t mode) { - int error = may_create(&init_user_ns, dir, dentry); + int error = may_create(mnt_userns, dir, dentry); unsigned max_links = dir->i_sb->s_max_links; if (error) @@ -3755,8 +3827,11 @@ static long do_mkdirat(int dfd, const char __user *pathname, umode_t mode) if (!IS_POSIXACL(path.dentry->d_inode)) mode &= ~current_umask(); error = security_path_mkdir(&path, dentry, mode); - if (!error) - error = vfs_mkdir(path.dentry->d_inode, dentry, mode); + if (!error) { + struct user_namespace *mnt_userns; + mnt_userns = mnt_user_ns(path.mnt); + error = vfs_mkdir(mnt_userns, path.dentry->d_inode, dentry, mode); + } done_path_create(&path, dentry); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -3775,9 +3850,24 @@ SYSCALL_DEFINE2(mkdir, const char __user *, pathname, umode_t, mode) return do_mkdirat(AT_FDCWD, pathname, mode); } -int vfs_rmdir(struct inode *dir, struct dentry *dentry) +/** + * vfs_rmdir - remove directory + * @mnt_userns: user namespace of the mount the inode was found from + * @dir: inode of @dentry + * @dentry: pointer to dentry of the base directory + * + * Remove a directory. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +int vfs_rmdir(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry) { - int error = may_delete(&init_user_ns, dir, dentry, 1); + int error = may_delete(mnt_userns, dir, dentry, 1); if (error) return error; @@ -3817,6 +3907,7 @@ EXPORT_SYMBOL(vfs_rmdir); long do_rmdir(int dfd, struct filename *name) { + struct user_namespace *mnt_userns; int error = 0; struct dentry *dentry; struct path path; @@ -3857,7 +3948,8 @@ long do_rmdir(int dfd, struct filename *name) error = security_path_rmdir(&path, dentry); if (error) goto exit3; - error = vfs_rmdir(path.dentry->d_inode, dentry); + mnt_userns = mnt_user_ns(path.mnt); + error = vfs_rmdir(mnt_userns, path.dentry->d_inode, dentry); exit3: dput(dentry); exit2: @@ -3880,6 +3972,7 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname) /** * vfs_unlink - unlink a filesystem object + * @mnt_userns: user namespace of the mount the inode was found from * @dir: parent directory * @dentry: victim * @delegated_inode: returns victim inode, if the inode is delegated. @@ -3895,11 +3988,18 @@ SYSCALL_DEFINE1(rmdir, const char __user *, pathname) * Alternatively, a caller may pass NULL for delegated_inode. This may * be appropriate for callers that expect the underlying filesystem not * to be NFS exported. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. */ -int vfs_unlink(struct inode *dir, struct dentry *dentry, struct inode **delegated_inode) +int vfs_unlink(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, struct inode **delegated_inode) { struct inode *target = dentry->d_inode; - int error = may_delete(&init_user_ns, dir, dentry, 0); + int error = may_delete(mnt_userns, dir, dentry, 0); if (error) return error; @@ -3970,6 +4070,8 @@ long do_unlinkat(int dfd, struct filename *name) dentry = __lookup_hash(&last, path.dentry, lookup_flags); error = PTR_ERR(dentry); if (!IS_ERR(dentry)) { + struct user_namespace *mnt_userns; + /* Why not before? Because we want correct error value */ if (last.name[last.len]) goto slashes; @@ -3980,7 +4082,8 @@ long do_unlinkat(int dfd, struct filename *name) error = security_path_unlink(&path, dentry); if (error) goto exit2; - error = vfs_unlink(path.dentry->d_inode, dentry, &delegated_inode); + mnt_userns = mnt_user_ns(path.mnt); + error = vfs_unlink(mnt_userns, path.dentry->d_inode, dentry, &delegated_inode); exit2: dput(dentry); } @@ -4029,9 +4132,25 @@ SYSCALL_DEFINE1(unlink, const char __user *, pathname) return do_unlinkat(AT_FDCWD, getname(pathname)); } -int vfs_symlink(struct inode *dir, struct dentry *dentry, const char *oldname) +/** + * vfs_symlink - create symlink + * @mnt_userns: user namespace of the mount the inode was found from + * @dir: inode of @dentry + * @dentry: pointer to dentry of the base directory + * @oldname: name of the file to link to + * + * Create a symlink. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. + */ +int vfs_symlink(struct user_namespace *mnt_userns, struct inode *dir, + struct dentry *dentry, const char *oldname) { - int error = may_create(&init_user_ns, dir, dentry); + int error = may_create(mnt_userns, dir, dentry); if (error) return error; @@ -4069,8 +4188,13 @@ static long do_symlinkat(const char __user *oldname, int newdfd, goto out_putname; error = security_path_symlink(&path, dentry, from->name); - if (!error) - error = vfs_symlink(path.dentry->d_inode, dentry, from->name); + if (!error) { + struct user_namespace *mnt_userns; + + mnt_userns = mnt_user_ns(path.mnt); + error = vfs_symlink(mnt_userns, path.dentry->d_inode, dentry, + from->name); + } done_path_create(&path, dentry); if (retry_estale(error, lookup_flags)) { lookup_flags |= LOOKUP_REVAL; @@ -4095,6 +4219,7 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn /** * vfs_link - create a new link * @old_dentry: object to be linked + * @mnt_userns: the user namespace of the mount * @dir: new parent * @new_dentry: where to create the new link * @delegated_inode: returns inode needing a delegation break @@ -4110,8 +4235,16 @@ SYSCALL_DEFINE2(symlink, const char __user *, oldname, const char __user *, newn * Alternatively, a caller may pass NULL for delegated_inode. This may * be appropriate for callers that expect the underlying filesystem not * to be NFS exported. + * + * If the inode has been found through an idmapped mount the user namespace of + * the vfsmount must be passed through @mnt_userns. This function will then take + * care to map the inode according to @mnt_userns before checking permissions. On + * non-idmapped mounts or if permission checking is to be performed on the raw + * inode simply passs init_user_ns. */ -int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry, struct inode **delegated_inode) +int vfs_link(struct dentry *old_dentry, struct user_namespace *mnt_userns, + struct inode *dir, struct dentry *new_dentry, + struct inode **delegated_inode) { struct inode *inode = old_dentry->d_inode; unsigned max_links = dir->i_sb->s_max_links; @@ -4120,7 +4253,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de if (!inode) return -ENOENT; - error = may_create(&init_user_ns, dir, new_dentry); + error = may_create(mnt_userns, dir, new_dentry); if (error) return error; @@ -4137,7 +4270,7 @@ int vfs_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_de * be writen back improperly if their true value is unknown to * the vfs. */ - if (HAS_UNMAPPED_ID(&init_user_ns, inode)) + if (HAS_UNMAPPED_ID(mnt_userns, inode)) return -EPERM; if (!dir->i_op->link) return -EPERM; @@ -4184,6 +4317,7 @@ EXPORT_SYMBOL(vfs_link); static int do_linkat(int olddfd, const char __user *oldname, int newdfd, const char __user *newname, int flags) { + struct user_namespace *mnt_userns; struct dentry *new_dentry; struct path old_path, new_path; struct inode *delegated_inode = NULL; @@ -4225,7 +4359,9 @@ static int do_linkat(int olddfd, const char __user *oldname, int newdfd, error = security_path_link(old_path.dentry, &new_path, new_dentry); if (error) goto out_dput; - error = vfs_link(old_path.dentry, new_path.dentry->d_inode, new_dentry, &delegated_inode); + mnt_userns = mnt_user_ns(new_path.mnt); + error = vfs_link(old_path.dentry, mnt_userns, new_path.dentry->d_inode, + new_dentry, &delegated_inode); out_dput: done_path_create(&new_path, new_dentry); if (delegated_inode) { @@ -4259,12 +4395,14 @@ SYSCALL_DEFINE2(link, const char __user *, oldname, const char __user *, newname /** * vfs_rename - rename a filesystem object - * @old_dir: parent of source - * @old_dentry: source - * @new_dir: parent of destination - * @new_dentry: destination - * @delegated_inode: returns an inode needing a delegation break - * @flags: rename flags + * @old_mnt_userns: old user namespace of the mount the inode was found from + * @old_dir: parent of source + * @old_dentry: source + * @new_mnt_userns: new user namespace of the mount the inode was found from + * @new_dir: parent of destination + * @new_dentry: destination + * @delegated_inode: returns an inode needing a delegation break + * @flags: rename flags * * The caller must hold multiple mutexes--see lock_rename()). * @@ -4326,19 +4464,19 @@ int vfs_rename(struct renamedata *rd) if (source == target) return 0; - error = may_delete(mnt_userns, old_dir, old_dentry, is_dir); + error = may_delete(rd->old_mnt_userns, old_dir, old_dentry, is_dir); if (error) return error; if (!target) { - error = may_create(mnt_userns, new_dir, new_dentry); + error = may_create(rd->new_mnt_userns, new_dir, new_dentry); } else { new_is_dir = d_is_dir(new_dentry); if (!(flags & RENAME_EXCHANGE)) - error = may_delete(mnt_userns, new_dir, new_dentry, is_dir); + error = may_delete(rd->new_mnt_userns, new_dir, new_dentry, is_dir); else - error = may_delete(mnt_userns, new_dir, new_dentry, new_is_dir); + error = may_delete(rd->new_mnt_userns, new_dir, new_dentry, new_is_dir); } if (error) return error; @@ -4352,12 +4490,12 @@ int vfs_rename(struct renamedata *rd) */ if (new_dir != old_dir) { if (is_dir) { - error = inode_permission(&init_user_ns, source, MAY_WRITE); + error = inode_permission(rd->old_mnt_userns, source, MAY_WRITE); if (error) return error; } if ((flags & RENAME_EXCHANGE) && new_is_dir) { - error = inode_permission(&init_user_ns, target, MAY_WRITE); + error = inode_permission(rd->new_mnt_userns, target, MAY_WRITE); if (error) return error; } @@ -4548,8 +4686,10 @@ static int do_renameat2(int olddfd, const char __user *oldname, int newdfd, rd.old_dir = old_path.dentry->d_inode; rd.old_dentry = old_dentry; + rd.old_mnt_userns = mnt_user_ns(old_path.mnt); rd.new_dir = new_path.dentry->d_inode; rd.new_dentry = new_dentry; + rd.new_mnt_userns = mnt_user_ns(new_path.mnt); rd.delegated_inode = &delegated_inode; rd.flags = flags; error = vfs_rename(&rd); diff --git a/fs/nfsd/nfs4recover.c b/fs/nfsd/nfs4recover.c index 186fa2c2c6ba..891395c6c7d3 100644 --- a/fs/nfsd/nfs4recover.c +++ b/fs/nfsd/nfs4recover.c @@ -233,7 +233,7 @@ nfsd4_create_clid_dir(struct nfs4_client *clp) * as well be forgiving and just succeed silently. */ goto out_put; - status = vfs_mkdir(d_inode(dir), dentry, S_IRWXU); + status = vfs_mkdir(&init_user_ns, d_inode(dir), dentry, S_IRWXU); out_put: dput(dentry); out_unlock: @@ -353,7 +353,7 @@ nfsd4_unlink_clid_dir(char *name, int namlen, struct nfsd_net *nn) status = -ENOENT; if (d_really_is_negative(dentry)) goto out; - status = vfs_rmdir(d_inode(dir), dentry); + status = vfs_rmdir(&init_user_ns, d_inode(dir), dentry); out: dput(dentry); out_unlock: @@ -443,7 +443,7 @@ purge_old(struct dentry *parent, struct dentry *child, struct nfsd_net *nn) if (nfs4_has_reclaimed_state(name, nn)) goto out_free; - status = vfs_rmdir(d_inode(parent), child); + status = vfs_rmdir(&init_user_ns, d_inode(parent), child); if (status) printk("failed to remove client recovery directory %pd\n", child); diff --git a/fs/nfsd/vfs.c b/fs/nfsd/vfs.c index 3d7a8cd61098..00d181829fe0 100644 --- a/fs/nfsd/vfs.c +++ b/fs/nfsd/vfs.c @@ -1245,12 +1245,12 @@ nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp, host_err = 0; switch (type) { case S_IFREG: - host_err = vfs_create(dirp, dchild, iap->ia_mode, true); + host_err = vfs_create(&init_user_ns, dirp, dchild, iap->ia_mode, true); if (!host_err) nfsd_check_ignore_resizing(iap); break; case S_IFDIR: - host_err = vfs_mkdir(dirp, dchild, iap->ia_mode); + host_err = vfs_mkdir(&init_user_ns, dirp, dchild, iap->ia_mode); if (!host_err && unlikely(d_unhashed(dchild))) { struct dentry *d; d = lookup_one_len(dchild->d_name.name, @@ -1278,7 +1278,8 @@ nfsd_create_locked(struct svc_rqst *rqstp, struct svc_fh *fhp, case S_IFBLK: case S_IFIFO: case S_IFSOCK: - host_err = vfs_mknod(dirp, dchild, iap->ia_mode, rdev); + host_err = vfs_mknod(&init_user_ns, dirp, dchild, + iap->ia_mode, rdev); break; default: printk(KERN_WARNING "nfsd: bad file type %o in nfsd_create\n", @@ -1476,7 +1477,7 @@ do_nfsd_create(struct svc_rqst *rqstp, struct svc_fh *fhp, if (!IS_POSIXACL(dirp)) iap->ia_mode &= ~current_umask(); - host_err = vfs_create(dirp, dchild, iap->ia_mode, true); + host_err = vfs_create(&init_user_ns, dirp, dchild, iap->ia_mode, true); if (host_err < 0) { fh_drop_write(fhp); goto out_nfserr; @@ -1600,7 +1601,7 @@ nfsd_symlink(struct svc_rqst *rqstp, struct svc_fh *fhp, if (IS_ERR(dnew)) goto out_nfserr; - host_err = vfs_symlink(d_inode(dentry), dnew, path); + host_err = vfs_symlink(&init_user_ns, d_inode(dentry), dnew, path); err = nfserrno(host_err); if (!err) err = nfserrno(commit_metadata(fhp)); @@ -1668,7 +1669,7 @@ nfsd_link(struct svc_rqst *rqstp, struct svc_fh *ffhp, err = nfserr_noent; if (d_really_is_negative(dold)) goto out_dput; - host_err = vfs_link(dold, dirp, dnew, NULL); + host_err = vfs_link(dold, &init_user_ns, dirp, dnew, NULL); if (!host_err) { err = nfserrno(commit_metadata(ffhp)); if (!err) @@ -1788,8 +1789,10 @@ nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname, int flen, goto out_dput_old; } else { struct renamedata rd = { + .old_mnt_userns = &init_user_ns, .old_dir = fdir, .old_dentry = odentry, + .new_mnt_userns = &init_user_ns, .new_dir = tdir, .new_dentry = ndentry, }; @@ -1879,9 +1882,9 @@ nfsd_unlink(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, if (type != S_IFDIR) { nfsd_close_cached_files(rdentry); - host_err = vfs_unlink(dirp, rdentry, NULL); + host_err = vfs_unlink(&init_user_ns, dirp, rdentry, NULL); } else { - host_err = vfs_rmdir(dirp, rdentry); + host_err = vfs_rmdir(&init_user_ns, dirp, rdentry); } if (!host_err) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index d75c96cb18c3..6904cc2ed7bb 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -821,9 +821,9 @@ static int ovl_remove_upper(struct dentry *dentry, bool is_dir, goto out_dput_upper; if (is_dir) - err = vfs_rmdir(dir, upper); + err = vfs_rmdir(&init_user_ns, dir, upper); else - err = vfs_unlink(dir, upper, NULL); + err = vfs_unlink(&init_user_ns, dir, upper, NULL); ovl_dir_modified(dentry->d_parent, ovl_type_origin(dentry)); /* diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 73da8710b0f0..5556e85552c1 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -121,7 +121,7 @@ static inline const char *ovl_xattr(struct ovl_fs *ofs, enum ovl_xattr ox) static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) { - int err = vfs_rmdir(dir, dentry); + int err = vfs_rmdir(&init_user_ns, dir, dentry); pr_debug("rmdir(%pd2) = %i\n", dentry, err); return err; @@ -129,7 +129,7 @@ static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry) { - int err = vfs_unlink(dir, dentry, NULL); + int err = vfs_unlink(&init_user_ns, dir, dentry, NULL); pr_debug("unlink(%pd2) = %i\n", dentry, err); return err; @@ -138,7 +138,7 @@ static inline int ovl_do_unlink(struct inode *dir, struct dentry *dentry) static inline int ovl_do_link(struct dentry *old_dentry, struct inode *dir, struct dentry *new_dentry) { - int err = vfs_link(old_dentry, dir, new_dentry, NULL); + int err = vfs_link(old_dentry, &init_user_ns, dir, new_dentry, NULL); pr_debug("link(%pd2, %pd2) = %i\n", old_dentry, new_dentry, err); return err; @@ -147,7 +147,7 @@ static inline int ovl_do_link(struct dentry *old_dentry, struct inode *dir, static inline int ovl_do_create(struct inode *dir, struct dentry *dentry, umode_t mode) { - int err = vfs_create(dir, dentry, mode, true); + int err = vfs_create(&init_user_ns, dir, dentry, mode, true); pr_debug("create(%pd2, 0%o) = %i\n", dentry, mode, err); return err; @@ -156,7 +156,7 @@ static inline int ovl_do_create(struct inode *dir, struct dentry *dentry, static inline int ovl_do_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) { - int err = vfs_mkdir(dir, dentry, mode); + int err = vfs_mkdir(&init_user_ns, dir, dentry, mode); pr_debug("mkdir(%pd2, 0%o) = %i\n", dentry, mode, err); return err; } @@ -164,7 +164,7 @@ static inline int ovl_do_mkdir(struct inode *dir, struct dentry *dentry, static inline int ovl_do_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev) { - int err = vfs_mknod(dir, dentry, mode, dev); + int err = vfs_mknod(&init_user_ns, dir, dentry, mode, dev); pr_debug("mknod(%pd2, 0%o, 0%o) = %i\n", dentry, mode, dev, err); return err; @@ -173,7 +173,7 @@ static inline int ovl_do_mknod(struct inode *dir, struct dentry *dentry, static inline int ovl_do_symlink(struct inode *dir, struct dentry *dentry, const char *oldname) { - int err = vfs_symlink(dir, dentry, oldname); + int err = vfs_symlink(&init_user_ns, dir, dentry, oldname); pr_debug("symlink(\"%s\", %pd2) = %i\n", oldname, dentry, err); return err; @@ -213,8 +213,10 @@ static inline int ovl_do_rename(struct inode *olddir, struct dentry *olddentry, { int err; struct renamedata rd = { + .old_mnt_userns = &init_user_ns, .old_dir = olddir, .old_dentry = olddentry, + .new_mnt_userns = &init_user_ns, .new_dir = newdir, .new_dentry = newdentry, .flags = flags, @@ -231,14 +233,14 @@ static inline int ovl_do_rename(struct inode *olddir, struct dentry *olddentry, static inline int ovl_do_whiteout(struct inode *dir, struct dentry *dentry) { - int err = vfs_whiteout(dir, dentry); + int err = vfs_whiteout(&init_user_ns, dir, dentry); pr_debug("whiteout(%pd2) = %i\n", dentry, err); return err; } static inline struct dentry *ovl_do_tmpfile(struct dentry *dentry, umode_t mode) { - struct dentry *ret = vfs_tmpfile(dentry, mode, 0); + struct dentry *ret = vfs_tmpfile(&init_user_ns, dentry, mode, 0); int err = PTR_ERR_OR_ZERO(ret); pr_debug("tmpfile(%pd2, 0%o) = %i\n", dentry, mode, err); diff --git a/include/linux/fs.h b/include/linux/fs.h index 6f0fb7d8d2c4..c88dafc4152f 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -1769,17 +1769,20 @@ extern bool inode_owner_or_capable(struct user_namespace *mnt_userns, /* * VFS helper functions.. */ -extern int vfs_create(struct inode *, struct dentry *, umode_t, bool); -extern int vfs_mkdir(struct inode *, struct dentry *, umode_t); -extern int vfs_mknod(struct inode *, struct dentry *, umode_t, dev_t); -extern int vfs_symlink(struct inode *, struct dentry *, const char *); -extern int vfs_link(struct dentry *, struct inode *, struct dentry *, struct inode **); -extern int vfs_rmdir(struct inode *, struct dentry *); -extern int vfs_unlink(struct inode *, struct dentry *, struct inode **); +extern int vfs_create(struct user_namespace *, struct inode *, struct dentry *, umode_t, bool); +extern int vfs_mkdir(struct user_namespace *, struct inode *, struct dentry *, umode_t); +extern int vfs_mknod(struct user_namespace *, struct inode *, struct dentry *, umode_t, dev_t); +extern int vfs_symlink(struct user_namespace *, struct inode *, struct dentry *, const char *); +extern int vfs_link(struct dentry *, struct user_namespace *, struct inode *, + struct dentry *, struct inode **); +extern int vfs_rmdir(struct user_namespace *, struct inode *, struct dentry *); +extern int vfs_unlink(struct user_namespace *, struct inode *, struct dentry *, struct inode **); struct renamedata { + struct user_namespace *old_mnt_userns; struct inode *old_dir; struct dentry *old_dentry; + struct user_namespace *new_mnt_userns; struct inode *new_dir; struct dentry *new_dentry; struct inode **delegated_inode; @@ -1788,12 +1791,14 @@ struct renamedata { extern int vfs_rename(struct renamedata *); -static inline int vfs_whiteout(struct inode *dir, struct dentry *dentry) +static inline int vfs_whiteout(struct user_namespace *mnt_userns, + struct inode *dir, struct dentry *dentry) { - return vfs_mknod(dir, dentry, S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV); + return vfs_mknod(mnt_userns, dir, dentry, S_IFCHR | WHITEOUT_MODE, WHITEOUT_DEV); } -extern struct dentry *vfs_tmpfile(struct dentry *dentry, umode_t mode, +extern struct dentry *vfs_tmpfile(struct user_namespace *mnt_userns, + struct dentry *dentry, umode_t mode, int open_flag); int vfs_mkobj(struct dentry *, umode_t, diff --git a/ipc/mqueue.c b/ipc/mqueue.c index 693f01fe1216..8b8c300854db 100644 --- a/ipc/mqueue.c +++ b/ipc/mqueue.c @@ -965,7 +965,7 @@ SYSCALL_DEFINE1(mq_unlink, const char __user *, u_name) err = -ENOENT; } else { ihold(inode); - err = vfs_unlink(d_inode(dentry->d_parent), dentry, NULL); + err = vfs_unlink(&init_user_ns, d_inode(dentry->d_parent), dentry, NULL); } dput(dentry); diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index f568526d4a02..b4987805e5e5 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -996,7 +996,7 @@ static int unix_mknod(const char *sun_path, umode_t mode, struct path *res) */ err = security_path_mknod(&path, dentry, mode, 0); if (!err) { - err = vfs_mknod(d_inode(path.dentry), dentry, mode, 0); + err = vfs_mknod(&init_user_ns, d_inode(path.dentry), dentry, mode, 0); if (!err) { res->mnt = mntget(path.mnt); res->dentry = dget(dentry); From patchwork Thu Dec 3 23:57:17 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950203 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id D0F95C4167B for ; Fri, 4 Dec 2020 00:07:57 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 80415224B2 for ; Fri, 4 Dec 2020 00:07:57 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502003AbgLDAHl (ORCPT ); Thu, 3 Dec 2020 19:07:41 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42555 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAHl (ORCPT ); Thu, 3 Dec 2020 19:07:41 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXo-0007ka-V0; Fri, 04 Dec 2020 00:01:53 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 21/40] open: handle idmapped mounts in do_truncate() Date: Fri, 4 Dec 2020 00:57:17 +0100 Message-Id: <20201203235736.3528991-22-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org When truncating files the vfs will verify that the caller is privileged over the inode. Extend it to handle idmapped mounts. If the inode is accessed through an idmapped mount it is mapped according to the mount's user namespace. Afterwards the permissions checks are identical to non-idmapped mounts. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/coredump.c | 14 ++++++++++---- fs/inode.c | 13 +++++++++---- fs/namei.c | 6 +++--- fs/open.c | 21 +++++++++++++-------- include/linux/fs.h | 4 ++-- 5 files changed, 37 insertions(+), 21 deletions(-) diff --git a/fs/coredump.c b/fs/coredump.c index 0cd9056d79cc..478add03c554 100644 --- a/fs/coredump.c +++ b/fs/coredump.c @@ -703,6 +703,7 @@ void do_coredump(const kernel_siginfo_t *siginfo) goto close_fail; } } else { + struct user_namespace *mnt_userns; struct inode *inode; int open_flags = O_CREAT | O_RDWR | O_NOFOLLOW | O_LARGEFILE | O_EXCL; @@ -780,13 +781,14 @@ void do_coredump(const kernel_siginfo_t *siginfo) * a process dumps core while its cwd is e.g. on a vfat * filesystem. */ - if (!uid_eq(inode->i_uid, current_fsuid())) + mnt_userns = mnt_user_ns(cprm.file->f_path.mnt); + if (!uid_eq(i_uid_into_mnt(mnt_userns, inode), current_fsuid())) goto close_fail; if ((inode->i_mode & 0677) != 0600) goto close_fail; if (!(cprm.file->f_mode & FMODE_CAN_WRITE)) goto close_fail; - if (do_truncate(cprm.file->f_path.dentry, 0, 0, cprm.file)) + if (do_truncate(mnt_userns, cprm.file->f_path.dentry, 0, 0, cprm.file)) goto close_fail; } @@ -931,8 +933,12 @@ void dump_truncate(struct coredump_params *cprm) if (file->f_op->llseek && file->f_op->llseek != no_llseek) { offset = file->f_op->llseek(file, 0, SEEK_CUR); - if (i_size_read(file->f_mapping->host) < offset) - do_truncate(file->f_path.dentry, offset, 0, file); + if (i_size_read(file->f_mapping->host) < offset) { + struct user_namespace *mnt_userns; + + mnt_userns = mnt_user_ns(file->f_path.mnt); + do_truncate(mnt_userns, file->f_path.dentry, offset, 0, file); + } } } EXPORT_SYMBOL(dump_truncate); diff --git a/fs/inode.c b/fs/inode.c index 159b55538347..d7a6842cf2ad 100644 --- a/fs/inode.c +++ b/fs/inode.c @@ -1904,7 +1904,8 @@ int dentry_needs_remove_privs(struct dentry *dentry) return mask; } -static int __remove_privs(struct dentry *dentry, int kill) +static int __remove_privs(struct user_namespace *mnt_userns, struct dentry *dentry, + int kill) { struct iattr newattrs; @@ -1913,7 +1914,7 @@ static int __remove_privs(struct dentry *dentry, int kill) * Note we call this on write, so notify_change will not * encounter any conflicting delegations: */ - return notify_change(&init_user_ns, dentry, &newattrs, NULL); + return notify_change(mnt_userns, dentry, &newattrs, NULL); } /* @@ -1939,8 +1940,12 @@ int file_remove_privs(struct file *file) kill = dentry_needs_remove_privs(dentry); if (kill < 0) return kill; - if (kill) - error = __remove_privs(dentry, kill); + if (kill) { + struct user_namespace *mnt_userns; + + mnt_userns = mnt_user_ns(file->f_path.mnt); + error = __remove_privs(mnt_userns, dentry, kill); + } if (!error) inode_has_no_xattr(inode); diff --git a/fs/namei.c b/fs/namei.c index 6cc655310bd9..a2c4ecd26966 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3005,9 +3005,9 @@ static int handle_truncate(struct file *filp) if (!error) error = security_path_truncate(path); if (!error) { - error = do_truncate(path->dentry, 0, - ATTR_MTIME|ATTR_CTIME|ATTR_OPEN, - filp); + error = do_truncate(mnt_user_ns(filp->f_path.mnt), + path->dentry, 0, + ATTR_MTIME | ATTR_CTIME | ATTR_OPEN, filp); } put_write_access(inode); return error; diff --git a/fs/open.c b/fs/open.c index 2dc94689a7dc..648477aa7e0b 100644 --- a/fs/open.c +++ b/fs/open.c @@ -35,8 +35,8 @@ #include "internal.h" -int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, - struct file *filp) +int do_truncate(struct user_namespace *mnt_userns, struct dentry *dentry, + loff_t length, unsigned int time_attrs, struct file *filp) { int ret; struct iattr newattrs; @@ -61,13 +61,14 @@ int do_truncate(struct dentry *dentry, loff_t length, unsigned int time_attrs, inode_lock(dentry->d_inode); /* Note any delegations or leases have already been broken: */ - ret = notify_change(&init_user_ns, dentry, &newattrs, NULL); + ret = notify_change(mnt_userns, dentry, &newattrs, NULL); inode_unlock(dentry->d_inode); return ret; } long vfs_truncate(const struct path *path, loff_t length) { + struct user_namespace *mnt_userns; struct inode *inode; long error; @@ -83,7 +84,8 @@ long vfs_truncate(const struct path *path, loff_t length) if (error) goto out; - error = inode_permission(&init_user_ns, inode, MAY_WRITE); + mnt_userns = mnt_user_ns(path->mnt); + error = inode_permission(mnt_userns, inode, MAY_WRITE); if (error) goto mnt_drop_write_and_out; @@ -107,7 +109,7 @@ long vfs_truncate(const struct path *path, loff_t length) if (!error) error = security_path_truncate(path); if (!error) - error = do_truncate(path->dentry, length, 0, NULL); + error = do_truncate(mnt_userns, path->dentry, length, 0, NULL); put_write_and_out: put_write_access(inode); @@ -186,13 +188,16 @@ long do_sys_ftruncate(unsigned int fd, loff_t length, int small) /* Check IS_APPEND on real upper inode */ if (IS_APPEND(file_inode(f.file))) goto out_putf; - sb_start_write(inode->i_sb); error = locks_verify_truncate(inode, f.file, length); if (!error) error = security_path_truncate(&f.file->f_path); - if (!error) - error = do_truncate(dentry, length, ATTR_MTIME|ATTR_CTIME, f.file); + if (!error) { + struct user_namespace *mnt_userns; + + mnt_userns = mnt_user_ns(f.file->f_path.mnt); + error = do_truncate(mnt_userns, dentry, length, ATTR_MTIME | ATTR_CTIME, f.file); + } sb_end_write(inode->i_sb); out_putf: fdput(f); diff --git a/include/linux/fs.h b/include/linux/fs.h index c88dafc4152f..616aeee07d69 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2586,8 +2586,8 @@ struct filename { static_assert(offsetof(struct filename, iname) % sizeof(long) == 0); extern long vfs_truncate(const struct path *, loff_t); -extern int do_truncate(struct dentry *, loff_t start, unsigned int time_attrs, - struct file *filp); +extern int do_truncate(struct user_namespace *, struct dentry *, loff_t start, + unsigned int time_attrs, struct file *filp); extern int vfs_fallocate(struct file *file, int mode, loff_t offset, loff_t len); extern long do_sys_open(int dfd, const char __user *filename, int flags, From patchwork Thu Dec 3 23:57:18 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950213 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B7BDCC4361A for ; Fri, 4 Dec 2020 00:10:09 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 767FE22224 for ; Fri, 4 Dec 2020 00:10:09 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1731634AbgLDAJy (ORCPT ); Thu, 3 Dec 2020 19:09:54 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42740 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAJx (ORCPT ); Thu, 3 Dec 2020 19:09:53 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXq-0007ka-OM; Fri, 04 Dec 2020 00:01:54 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 22/40] open: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:18 +0100 Message-Id: <20201203235736.3528991-23-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org For core file operations such as changing directories or chrooting, determining file access, changing mode or ownership the vfs will verify that the caller is privileged over the inode. Extend the various helpers to handle idmapped mounts. If the inode is accessed through an idmapped mount it is mapped according to the mount's user namespace. Afterwards the permissions checks are identical to non-idmapped mounts. When changing file ownership we need to map the uid and gid from the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ - David Howells : - Remove mnt_idmapped() check after removing mnt_idmapped() helper in earlier patches. /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/open.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/fs/open.c b/fs/open.c index 648477aa7e0b..c928bc2dab0f 100644 --- a/fs/open.c +++ b/fs/open.c @@ -401,6 +401,7 @@ static const struct cred *access_override_creds(void) static long do_faccessat(int dfd, const char __user *filename, int mode, int flags) { + struct user_namespace *mnt_userns; struct path path; struct inode *inode; int res; @@ -441,7 +442,8 @@ static long do_faccessat(int dfd, const char __user *filename, int mode, int fla goto out_path_release; } - res = inode_permission(&init_user_ns, inode, mode | MAY_ACCESS); + mnt_userns = mnt_user_ns(path.mnt); + res = inode_permission(mnt_userns, inode, mode | MAY_ACCESS); /* SuS v2 requires we report a read only fs too */ if (res || !(mode & S_IWOTH) || special_file(inode->i_mode)) goto out_path_release; @@ -489,6 +491,7 @@ SYSCALL_DEFINE2(access, const char __user *, filename, int, mode) SYSCALL_DEFINE1(chdir, const char __user *, filename) { + struct user_namespace *mnt_userns; struct path path; int error; unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY; @@ -497,7 +500,8 @@ SYSCALL_DEFINE1(chdir, const char __user *, filename) if (error) goto out; - error = inode_permission(&init_user_ns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); + mnt_userns = mnt_user_ns(path.mnt); + error = inode_permission(mnt_userns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); if (error) goto dput_and_out; @@ -515,6 +519,7 @@ SYSCALL_DEFINE1(chdir, const char __user *, filename) SYSCALL_DEFINE1(fchdir, unsigned int, fd) { + struct user_namespace *mnt_userns; struct fd f = fdget_raw(fd); int error; @@ -526,7 +531,8 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd) if (!d_can_lookup(f.file->f_path.dentry)) goto out_putf; - error = inode_permission(&init_user_ns, file_inode(f.file), MAY_EXEC | MAY_CHDIR); + mnt_userns = mnt_user_ns(f.file->f_path.mnt); + error = inode_permission(mnt_userns, file_inode(f.file), MAY_EXEC | MAY_CHDIR); if (!error) set_fs_pwd(current->fs, &f.file->f_path); out_putf: @@ -537,6 +543,7 @@ SYSCALL_DEFINE1(fchdir, unsigned int, fd) SYSCALL_DEFINE1(chroot, const char __user *, filename) { + struct user_namespace *mnt_userns; struct path path; int error; unsigned int lookup_flags = LOOKUP_FOLLOW | LOOKUP_DIRECTORY; @@ -545,7 +552,8 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename) if (error) goto out; - error = inode_permission(&init_user_ns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); + mnt_userns = mnt_user_ns(path.mnt); + error = inode_permission(mnt_userns, path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); if (error) goto dput_and_out; @@ -570,6 +578,7 @@ SYSCALL_DEFINE1(chroot, const char __user *, filename) int chmod_common(const struct path *path, umode_t mode) { + struct user_namespace *mnt_userns; struct inode *inode = path->dentry->d_inode; struct inode *delegated_inode = NULL; struct iattr newattrs; @@ -585,7 +594,8 @@ int chmod_common(const struct path *path, umode_t mode) goto out_unlock; newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; - error = notify_change(&init_user_ns, path->dentry, &newattrs, &delegated_inode); + mnt_userns = mnt_user_ns(path->mnt); + error = notify_change(mnt_userns, path->dentry, &newattrs, &delegated_inode); out_unlock: inode_unlock(inode); if (delegated_inode) { @@ -646,6 +656,7 @@ SYSCALL_DEFINE2(chmod, const char __user *, filename, umode_t, mode) int chown_common(const struct path *path, uid_t user, gid_t group) { + struct user_namespace *mnt_userns; struct inode *inode = path->dentry->d_inode; struct inode *delegated_inode = NULL; int error; @@ -656,6 +667,10 @@ int chown_common(const struct path *path, uid_t user, gid_t group) uid = make_kuid(current_user_ns(), user); gid = make_kgid(current_user_ns(), group); + mnt_userns = mnt_user_ns(path->mnt); + uid = kuid_from_mnt(mnt_userns, uid); + gid = kgid_from_mnt(mnt_userns, gid); + retry_deleg: newattrs.ia_valid = ATTR_CTIME; if (user != (uid_t) -1) { @@ -676,7 +691,7 @@ int chown_common(const struct path *path, uid_t user, gid_t group) inode_lock(inode); error = security_path_chown(path, uid, gid); if (!error) - error = notify_change(&init_user_ns, path->dentry, &newattrs, &delegated_inode); + error = notify_change(mnt_userns, path->dentry, &newattrs, &delegated_inode); inode_unlock(inode); if (delegated_inode) { error = break_deleg_wait(&delegated_inode); @@ -1133,7 +1148,7 @@ struct file *filp_open(const char *filename, int flags, umode_t mode) { struct filename *name = getname_kernel(filename); struct file *file = ERR_CAST(name); - + if (!IS_ERR(name)) { file = file_open_name(name, flags, mode); putname(name); From patchwork Thu Dec 3 23:57:19 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950201 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id A358EC4167B for ; Fri, 4 Dec 2020 00:07:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5B59B224F9 for ; Fri, 4 Dec 2020 00:07:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2501996AbgLDAHH (ORCPT ); Thu, 3 Dec 2020 19:07:07 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42504 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2501936AbgLDAHG (ORCPT ); Thu, 3 Dec 2020 19:07:06 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXs-0007ka-HE; Fri, 04 Dec 2020 00:01:56 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 23/40] af_unix: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:19 +0100 Message-Id: <20201203235736.3528991-24-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org When binding a non-abstract AF_UNIX socket it will gain a representation in the filesystem. Enable the socket infrastructure to handle idmapped mounts by passing down the user namespace of the mount the socket will be created from. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ unchanged --- net/unix/af_unix.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c index b4987805e5e5..4be33240e9cc 100644 --- a/net/unix/af_unix.c +++ b/net/unix/af_unix.c @@ -996,7 +996,7 @@ static int unix_mknod(const char *sun_path, umode_t mode, struct path *res) */ err = security_path_mknod(&path, dentry, mode, 0); if (!err) { - err = vfs_mknod(&init_user_ns, d_inode(path.dentry), dentry, mode, 0); + err = vfs_mknod(mnt_user_ns(path.mnt), d_inode(path.dentry), dentry, mode, 0); if (!err) { res->mnt = mntget(path.mnt); res->dentry = dget(dentry); From patchwork Thu Dec 3 23:57:20 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950117 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id ACBB4C19425 for ; Fri, 4 Dec 2020 00:03:51 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6E80D222B6 for ; Fri, 4 Dec 2020 00:03:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389463AbgLDADv (ORCPT ); Thu, 3 Dec 2020 19:03:51 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42387 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2387545AbgLDADu (ORCPT ); Thu, 3 Dec 2020 19:03:50 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXu-0007ka-J6; Fri, 04 Dec 2020 00:01:58 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 24/40] utimes: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:20 +0100 Message-Id: <20201203235736.3528991-25-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Enable the vfs_utimes() helper to handle idmapped mounts by passing down the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/utimes.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/fs/utimes.c b/fs/utimes.c index 1a4130bee157..33fa753bab3a 100644 --- a/fs/utimes.c +++ b/fs/utimes.c @@ -22,6 +22,7 @@ int vfs_utimes(const struct path *path, struct timespec64 *times) struct iattr newattrs; struct inode *inode = path->dentry->d_inode; struct inode *delegated_inode = NULL; + struct user_namespace *mnt_userns; if (times) { if (!nsec_valid(times[0].tv_nsec) || @@ -61,8 +62,9 @@ int vfs_utimes(const struct path *path, struct timespec64 *times) newattrs.ia_valid |= ATTR_TOUCH; } retry_deleg: + mnt_userns = mnt_user_ns(path->mnt); inode_lock(inode); - error = notify_change(&init_user_ns, path->dentry, &newattrs, &delegated_inode); + error = notify_change(mnt_userns, path->dentry, &newattrs, &delegated_inode); inode_unlock(inode); if (delegated_inode) { error = break_deleg_wait(&delegated_inode); From patchwork Thu Dec 3 23:57:21 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950261 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 491ABC193FE for ; Fri, 4 Dec 2020 00:11:55 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id EA177222B6 for ; Fri, 4 Dec 2020 00:11:54 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502122AbgLDAL2 (ORCPT ); Thu, 3 Dec 2020 19:11:28 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:43168 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2502102AbgLDAL2 (ORCPT ); Thu, 3 Dec 2020 19:11:28 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXw-0007ka-BC; Fri, 04 Dec 2020 00:02:00 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 25/40] fcntl: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:21 +0100 Message-Id: <20201203235736.3528991-26-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Enable the setfl() helper to handle idmapped mounts by passing down the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced /* v3 */ unchanged /* v4 */ unchanged --- fs/fcntl.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/fcntl.c b/fs/fcntl.c index df091d435603..ed330fa91438 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -46,7 +47,7 @@ static int setfl(int fd, struct file * filp, unsigned long arg) /* O_NOATIME can only be set by the owner or superuser */ if ((arg & O_NOATIME) && !(filp->f_flags & O_NOATIME)) - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_user_ns(filp->f_path.mnt), inode)) return -EPERM; /* required for strict SunOS emulation */ From patchwork Thu Dec 3 23:57:22 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950263 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 08F05C4361A for ; Fri, 4 Dec 2020 00:12:19 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id BA04E222B6 for ; Fri, 4 Dec 2020 00:12:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389495AbgLDAMR (ORCPT ); Thu, 3 Dec 2020 19:12:17 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:43254 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727916AbgLDAMR (ORCPT ); Thu, 3 Dec 2020 19:12:17 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyXy-0007ka-9G; Fri, 04 Dec 2020 00:02:02 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 26/40] notify: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:22 +0100 Message-Id: <20201203235736.3528991-27-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Enable notify implementations to handle idmapped mounts by passing down the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced /* v3 */ unchanged /* v4 */ unchanged --- fs/notify/fanotify/fanotify_user.c | 2 +- fs/notify/inotify/inotify_user.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/notify/fanotify/fanotify_user.c b/fs/notify/fanotify/fanotify_user.c index de4d01bb1d8d..e3b2cb6a9d81 100644 --- a/fs/notify/fanotify/fanotify_user.c +++ b/fs/notify/fanotify/fanotify_user.c @@ -702,7 +702,7 @@ static int fanotify_find_path(int dfd, const char __user *filename, } /* you can only watch an inode if you have read permissions on it */ - ret = inode_permission(&init_user_ns, path->dentry->d_inode, MAY_READ); + ret = inode_permission(mnt_user_ns(path->mnt), path->dentry->d_inode, MAY_READ); if (ret) { path_put(path); goto out; diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index e995fd4e4e53..f39f5b81f2b3 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c @@ -31,6 +31,7 @@ #include #include #include +#include #include "inotify.h" #include "../fdinfo.h" @@ -343,7 +344,7 @@ static int inotify_find_inode(const char __user *dirname, struct path *path, if (error) return error; /* you can only watch an inode if you have read permissions on it */ - error = inode_permission(&init_user_ns, path->dentry->d_inode, MAY_READ); + error = inode_permission(mnt_user_ns(path->mnt), path->dentry->d_inode, MAY_READ); if (error) { path_put(path); return error; From patchwork Thu Dec 3 23:57:23 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950219 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 56078C4361A for ; Fri, 4 Dec 2020 00:10:45 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 1361E22224 for ; Fri, 4 Dec 2020 00:10:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389521AbgLDAKo (ORCPT ); Thu, 3 Dec 2020 19:10:44 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42836 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAKn (ORCPT ); Thu, 3 Dec 2020 19:10:43 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyY0-0007ka-3F; Fri, 04 Dec 2020 00:02:04 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 27/40] init: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:23 +0100 Message-Id: <20201203235736.3528991-28-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Enable the init helpers to handle idmapped mounts by passing down the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced /* v3 */ unchanged /* v4 */ unchanged --- fs/init.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/fs/init.c b/fs/init.c index 06d8e52ce18e..1db2b392e4e6 100644 --- a/fs/init.c +++ b/fs/init.c @@ -49,7 +49,7 @@ int __init init_chdir(const char *filename) error = kern_path(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path); if (error) return error; - error = inode_permission(&init_user_ns, path.dentry->d_inode, + error = inode_permission(mnt_user_ns(path.mnt), path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); if (!error) set_fs_pwd(current->fs, &path); @@ -65,7 +65,7 @@ int __init init_chroot(const char *filename) error = kern_path(filename, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path); if (error) return error; - error = inode_permission(&init_user_ns, path.dentry->d_inode, + error = inode_permission(mnt_user_ns(path.mnt), path.dentry->d_inode, MAY_EXEC | MAY_CHDIR); if (error) goto dput_and_out; @@ -120,7 +120,7 @@ int __init init_eaccess(const char *filename) error = kern_path(filename, LOOKUP_FOLLOW, &path); if (error) return error; - error = inode_permission(&init_user_ns, d_inode(path.dentry), + error = inode_permission(mnt_user_ns(path.mnt), d_inode(path.dentry), MAY_ACCESS); path_put(&path); return error; @@ -160,8 +160,8 @@ int __init init_mknod(const char *filename, umode_t mode, unsigned int dev) mode &= ~current_umask(); error = security_path_mknod(&path, dentry, mode, dev); if (!error) - error = vfs_mknod(&init_user_ns, path.dentry->d_inode, dentry, - mode, new_decode_dev(dev)); + error = vfs_mknod(mnt_user_ns(path.mnt), path.dentry->d_inode, + dentry, mode, new_decode_dev(dev)); done_path_create(&path, dentry); return error; } @@ -170,6 +170,7 @@ int __init init_link(const char *oldname, const char *newname) { struct dentry *new_dentry; struct path old_path, new_path; + struct user_namespace *mnt_userns; int error; error = kern_path(oldname, 0, &old_path); @@ -184,14 +185,15 @@ int __init init_link(const char *oldname, const char *newname) error = -EXDEV; if (old_path.mnt != new_path.mnt) goto out_dput; - error = may_linkat(&init_user_ns, &old_path); + mnt_userns = mnt_user_ns(new_path.mnt); + error = may_linkat(mnt_userns, &old_path); if (unlikely(error)) goto out_dput; error = security_path_link(old_path.dentry, &new_path, new_dentry); if (error) goto out_dput; - error = vfs_link(old_path.dentry, &init_user_ns, - new_path.dentry->d_inode, new_dentry, NULL); + error = vfs_link(old_path.dentry, mnt_userns, new_path.dentry->d_inode, + new_dentry, NULL); out_dput: done_path_create(&new_path, new_dentry); out: @@ -210,7 +212,8 @@ int __init init_symlink(const char *oldname, const char *newname) return PTR_ERR(dentry); error = security_path_symlink(&path, dentry, oldname); if (!error) - error = vfs_symlink(&init_user_ns, path.dentry->d_inode, dentry, oldname); + error = vfs_symlink(mnt_user_ns(path.mnt), path.dentry->d_inode, + dentry, oldname); done_path_create(&path, dentry); return error; } @@ -233,7 +236,8 @@ int __init init_mkdir(const char *pathname, umode_t mode) mode &= ~current_umask(); error = security_path_mkdir(&path, dentry, mode); if (!error) - error = vfs_mkdir(&init_user_ns, path.dentry->d_inode, dentry, mode); + error = vfs_mkdir(mnt_user_ns(path.mnt), path.dentry->d_inode, + dentry, mode); done_path_create(&path, dentry); return error; } From patchwork Thu Dec 3 23:57:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950265 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 75345C4361B for ; Fri, 4 Dec 2020 00:13:04 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 27E6C224B8 for ; Fri, 4 Dec 2020 00:13:04 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502140AbgLDAND (ORCPT ); Thu, 3 Dec 2020 19:13:03 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:43368 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2502022AbgLDANC (ORCPT ); Thu, 3 Dec 2020 19:13:02 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyY1-0007ka-UC; Fri, 04 Dec 2020 00:02:06 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 28/40] ioctl: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:24 +0100 Message-Id: <20201203235736.3528991-29-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Enable generic ioctls to handle idmapped mounts by passing down the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/remap_range.c | 7 +++++-- fs/verity/enable.c | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/fs/remap_range.c b/fs/remap_range.c index 9e5b27641756..26759ee83a22 100644 --- a/fs/remap_range.c +++ b/fs/remap_range.c @@ -432,13 +432,16 @@ EXPORT_SYMBOL(vfs_clone_file_range); /* Check whether we are allowed to dedupe the destination file */ static bool allow_file_dedupe(struct file *file) { + struct user_namespace *mnt_userns = mnt_user_ns(file->f_path.mnt); + struct inode *inode = file_inode(file); + if (capable(CAP_SYS_ADMIN)) return true; if (file->f_mode & FMODE_WRITE) return true; - if (uid_eq(current_fsuid(), file_inode(file)->i_uid)) + if (uid_eq(current_fsuid(), i_uid_into_mnt(mnt_userns, inode))) return true; - if (!inode_permission(&init_user_ns, file_inode(file), MAY_WRITE)) + if (!inode_permission(mnt_userns, inode, MAY_WRITE)) return true; return false; } diff --git a/fs/verity/enable.c b/fs/verity/enable.c index 7449ef0050f4..8b9ea0f0850f 100644 --- a/fs/verity/enable.c +++ b/fs/verity/enable.c @@ -369,7 +369,7 @@ int fsverity_ioctl_enable(struct file *filp, const void __user *uarg) * has verity enabled, and to stabilize the data being hashed. */ - err = inode_permission(&init_user_ns, inode, MAY_WRITE); + err = inode_permission(mnt_user_ns(filp->f_path.mnt), inode, MAY_WRITE); if (err) return err; From patchwork Thu Dec 3 23:57:25 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950123 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 4AB45C4361B for ; Fri, 4 Dec 2020 00:06:51 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id F177B224B8 for ; Fri, 4 Dec 2020 00:06:50 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388270AbgLDAGu (ORCPT ); Thu, 3 Dec 2020 19:06:50 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42487 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAGt (ORCPT ); Thu, 3 Dec 2020 19:06:49 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyY3-0007ka-OD; Fri, 04 Dec 2020 00:02:07 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 29/40] would_dump: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:25 +0100 Message-Id: <20201203235736.3528991-30-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org When determining whether or not to create a coredump the vfs will verify that the caller is privileged over the inode. Make the would_dump() helper handle idmapped mounts by passing down the mount's user namespace of the exec file. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/exec.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index b499a1a03934..1efcda495708 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1391,14 +1391,15 @@ EXPORT_SYMBOL(begin_new_exec); void would_dump(struct linux_binprm *bprm, struct file *file) { struct inode *inode = file_inode(file); - if (inode_permission(&init_user_ns, inode, MAY_READ) < 0) { + struct user_namespace *mnt_userns = mnt_user_ns(file->f_path.mnt); + if (inode_permission(mnt_userns, inode, MAY_READ) < 0) { struct user_namespace *old, *user_ns; bprm->interp_flags |= BINPRM_FLAGS_ENFORCE_NONDUMP; /* Ensure mm->user_ns contains the executable */ user_ns = old = bprm->mm->user_ns; while ((user_ns != &init_user_ns) && - !privileged_wrt_inode_uidgid(user_ns, &init_user_ns, inode)) + !privileged_wrt_inode_uidgid(user_ns, mnt_userns, inode)) user_ns = user_ns->parent; if (old != user_ns) { From patchwork Thu Dec 3 23:57:26 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950267 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B03C3C4167B for ; Fri, 4 Dec 2020 00:13:12 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 6D62922224 for ; Fri, 4 Dec 2020 00:13:12 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502154AbgLDANL (ORCPT ); Thu, 3 Dec 2020 19:13:11 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:43386 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2502147AbgLDANL (ORCPT ); Thu, 3 Dec 2020 19:13:11 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyY5-0007ka-I8; Fri, 04 Dec 2020 00:02:09 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 30/40] exec: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:26 +0100 Message-Id: <20201203235736.3528991-31-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org When executing a setuid binary the kernel will verify in bprm_fill_uid() that the inode has a mapping in the caller's user namespace before setting the callers uid and gid. Let bprm_fill_uid() handle idmapped mounts. If the inode is accessed through an idmapped mount it is mapped according to the mount's user namespace. Afterwards the checks are identical to non-idmapped mounts. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/exec.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/fs/exec.c b/fs/exec.c index 1efcda495708..05d5009eeba7 100644 --- a/fs/exec.c +++ b/fs/exec.c @@ -1567,6 +1567,7 @@ static void check_unsafe_exec(struct linux_binprm *bprm) static void bprm_fill_uid(struct linux_binprm *bprm, struct file *file) { /* Handle suid and sgid on files */ + struct user_namespace *mnt_userns; struct inode *inode; unsigned int mode; kuid_t uid; @@ -1583,13 +1584,15 @@ static void bprm_fill_uid(struct linux_binprm *bprm, struct file *file) if (!(mode & (S_ISUID|S_ISGID))) return; + mnt_userns = mnt_user_ns(file->f_path.mnt); + /* Be careful if suid/sgid is set */ inode_lock(inode); /* reload atomically mode/uid/gid now that lock held */ mode = inode->i_mode; - uid = inode->i_uid; - gid = inode->i_gid; + uid = i_uid_into_mnt(mnt_userns, inode); + gid = i_gid_into_mnt(mnt_userns, inode); inode_unlock(inode); /* We ignore suid/sgid if there are no mappings for them in the ns */ From patchwork Thu Dec 3 23:57:28 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950221 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 89FC0C4361A for ; Fri, 4 Dec 2020 00:11:21 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 2D5B122224 for ; Fri, 4 Dec 2020 00:11:21 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502077AbgLDALU (ORCPT ); Thu, 3 Dec 2020 19:11:20 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:43147 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDALU (ORCPT ); Thu, 3 Dec 2020 19:11:20 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyY9-0007ka-Bg; Fri, 04 Dec 2020 00:02:13 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner Subject: [PATCH v4 32/40] apparmor: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:28 +0100 Message-Id: <20201203235736.3528991-33-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org The i_uid and i_gid are only ever used when logging for AppArmor. This is already broken in a bunch of places where the global root id is reported instead of the i_uid or i_gid of the file. Nonetheless, be kind and log the mapped inode if we're coming from an idmapped mount. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- security/apparmor/domain.c | 9 ++++++--- security/apparmor/file.c | 5 ++++- security/apparmor/lsm.c | 12 ++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 16f184bc48de..890aa0b43bab 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -10,12 +10,14 @@ #include #include +#include #include #include #include #include #include #include +#include #include "include/audit.h" #include "include/apparmorfs.h" @@ -858,8 +860,10 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) const char *info = NULL; int error = 0; bool unsafe = false; + struct user_namespace *mnt_userns = mnt_user_ns(bprm->file->f_path.mnt); + kuid_t i_uid = i_uid_into_mnt(mnt_userns, file_inode(bprm->file)); struct path_cond cond = { - file_inode(bprm->file)->i_uid, + i_uid, file_inode(bprm->file)->i_mode }; @@ -967,8 +971,7 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) error = fn_for_each(label, profile, aa_audit_file(profile, &nullperms, OP_EXEC, MAY_EXEC, bprm->filename, NULL, new, - file_inode(bprm->file)->i_uid, info, - error)); + i_uid, info, error)); aa_put_label(new); goto done; } diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 92acf9a49405..d70d09a5ce7d 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include "include/apparmor.h" #include "include/audit.h" @@ -508,8 +510,9 @@ static int __file_path_perm(const char *op, struct aa_label *label, { struct aa_profile *profile; struct aa_perms perms = {}; + struct user_namespace *mnt_userns = mnt_user_ns(file->f_path.mnt); struct path_cond cond = { - .uid = file_inode(file)->i_uid, + .uid = i_uid_into_mnt(mnt_userns, file_inode(file)), .mode = file_inode(file)->i_mode }; char *buffer; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index ffeaee5ed968..03260983b471 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -224,7 +224,8 @@ static int common_perm(const char *op, const struct path *path, u32 mask, */ static int common_perm_cond(const char *op, const struct path *path, u32 mask) { - struct path_cond cond = { d_backing_inode(path->dentry)->i_uid, + struct user_namespace *mnt_userns = mnt_user_ns(path->mnt); + struct path_cond cond = { i_uid_into_mnt(mnt_userns, d_backing_inode(path->dentry)), d_backing_inode(path->dentry)->i_mode }; @@ -266,12 +267,13 @@ static int common_perm_rm(const char *op, const struct path *dir, struct dentry *dentry, u32 mask) { struct inode *inode = d_backing_inode(dentry); + struct user_namespace *mnt_userns = mnt_user_ns(dir->mnt); struct path_cond cond = { }; if (!inode || !path_mediated_fs(dentry)) return 0; - cond.uid = inode->i_uid; + cond.uid = i_uid_into_mnt(mnt_userns, inode); cond.mode = inode->i_mode; return common_perm_dir_dentry(op, dir, dentry, mask, &cond); @@ -361,11 +363,12 @@ static int apparmor_path_rename(const struct path *old_dir, struct dentry *old_d label = begin_current_label_crit_section(); if (!unconfined(label)) { + struct user_namespace *mnt_userns = mnt_user_ns(old_dir->mnt); struct path old_path = { .mnt = old_dir->mnt, .dentry = old_dentry }; struct path new_path = { .mnt = new_dir->mnt, .dentry = new_dentry }; - struct path_cond cond = { d_backing_inode(old_dentry)->i_uid, + struct path_cond cond = { i_uid_into_mnt(mnt_userns, d_backing_inode(old_dentry)), d_backing_inode(old_dentry)->i_mode }; @@ -420,8 +423,9 @@ static int apparmor_file_open(struct file *file) label = aa_get_newest_cred_label(file->f_cred); if (!unconfined(label)) { + struct user_namespace *mnt_userns = mnt_user_ns(file->f_path.mnt); struct inode *inode = file_inode(file); - struct path_cond cond = { inode->i_uid, inode->i_mode }; + struct path_cond cond = { i_uid_into_mnt(mnt_userns, inode), inode->i_mode }; error = aa_path_perm(OP_OPEN, label, &file->f_path, 0, aa_map_file_to_perms(file), &cond); From patchwork Thu Dec 3 23:57:29 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950211 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 2B9F2C4361B for ; Fri, 4 Dec 2020 00:09:37 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id D15B5224B2 for ; Fri, 4 Dec 2020 00:09:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502074AbgLDAJe (ORCPT ); Thu, 3 Dec 2020 19:09:34 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42699 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2387475AbgLDAJd (ORCPT ); Thu, 3 Dec 2020 19:09:33 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyYB-0007ka-4o; Fri, 04 Dec 2020 00:02:15 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner Subject: [PATCH v4 33/40] ima: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:29 +0100 Message-Id: <20201203235736.3528991-34-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org IMA does sometimes access the inode's i_uid and compares it against the rules' fowner. Enable IMA to handle idmapped mounts by passing down the mount's user namespace. We simply make use of the helpers we introduced before. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/attr.c | 2 +- fs/namei.c | 4 +-- include/linux/ima.h | 17 ++++++++---- security/integrity/ima/ima.h | 19 ++++++++----- security/integrity/ima/ima_api.c | 10 ++++--- security/integrity/ima/ima_appraise.c | 14 ++++++---- security/integrity/ima/ima_asymmetric_keys.c | 2 +- security/integrity/ima/ima_main.c | 29 ++++++++++++-------- security/integrity/ima/ima_policy.c | 19 +++++++------ security/integrity/ima/ima_queue_keys.c | 2 +- 10 files changed, 71 insertions(+), 47 deletions(-) diff --git a/fs/attr.c b/fs/attr.c index 2a180598d8a5..9d7e9a7350a6 100644 --- a/fs/attr.c +++ b/fs/attr.c @@ -399,7 +399,7 @@ int notify_change(struct user_namespace *mnt_userns, struct dentry *dentry, if (!error) { fsnotify_change(dentry, ia_valid); - ima_inode_post_setattr(dentry); + ima_inode_post_setattr(mnt_userns, dentry); evm_inode_post_setattr(dentry, ia_valid); } diff --git a/fs/namei.c b/fs/namei.c index 5c31eafce18c..a8aeadb6c35d 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -3417,7 +3417,7 @@ struct dentry *vfs_tmpfile(struct user_namespace *mnt_userns, inode->i_state |= I_LINKABLE; spin_unlock(&inode->i_lock); } - ima_post_create_tmpfile(inode); + ima_post_create_tmpfile(mnt_userns, inode); return child; out_err: @@ -3745,7 +3745,7 @@ static long do_mknodat(int dfd, const char __user *filename, umode_t mode, error = vfs_create(mnt_userns, path.dentry->d_inode, dentry, mode, true); if (!error) - ima_post_path_mknod(dentry); + ima_post_path_mknod(mnt_userns, dentry); break; case S_IFCHR: case S_IFBLK: error = vfs_mknod(mnt_userns, path.dentry->d_inode, diff --git a/include/linux/ima.h b/include/linux/ima.h index 8fa7bcfb2da2..3d7fbc7de72e 100644 --- a/include/linux/ima.h +++ b/include/linux/ima.h @@ -16,7 +16,7 @@ struct linux_binprm; #ifdef CONFIG_IMA extern int ima_bprm_check(struct linux_binprm *bprm); extern int ima_file_check(struct file *file, int mask); -extern void ima_post_create_tmpfile(struct inode *inode); +extern void ima_post_create_tmpfile(struct user_namespace *mnt_userns, struct inode *inode); extern void ima_file_free(struct file *file); extern int ima_file_mmap(struct file *file, unsigned long prot); extern int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot); @@ -27,7 +27,8 @@ extern int ima_read_file(struct file *file, enum kernel_read_file_id id, bool contents); extern int ima_post_read_file(struct file *file, void *buf, loff_t size, enum kernel_read_file_id id); -extern void ima_post_path_mknod(struct dentry *dentry); +extern void ima_post_path_mknod(struct user_namespace *mnt_userns, + struct dentry *dentry); extern int ima_file_hash(struct file *file, char *buf, size_t buf_size); extern void ima_kexec_cmdline(int kernel_fd, const void *buf, int size); @@ -61,7 +62,8 @@ static inline int ima_file_check(struct file *file, int mask) return 0; } -static inline void ima_post_create_tmpfile(struct inode *inode) +static inline void ima_post_create_tmpfile(struct user_namespace *mnt_userns, + struct inode *inode) { } @@ -105,7 +107,8 @@ static inline int ima_post_read_file(struct file *file, void *buf, loff_t size, return 0; } -static inline void ima_post_path_mknod(struct dentry *dentry) +static inline void ima_post_path_mknod(struct user_namespace *mnt_userns, + struct dentry *dentry) { return; } @@ -141,7 +144,8 @@ static inline void ima_post_key_create_or_update(struct key *keyring, #ifdef CONFIG_IMA_APPRAISE extern bool is_ima_appraise_enabled(void); -extern void ima_inode_post_setattr(struct dentry *dentry); +extern void ima_inode_post_setattr(struct user_namespace *mnt_userns, + struct dentry *dentry); extern int ima_inode_setxattr(struct dentry *dentry, const char *xattr_name, const void *xattr_value, size_t xattr_value_len); extern int ima_inode_removexattr(struct dentry *dentry, const char *xattr_name); @@ -151,7 +155,8 @@ static inline bool is_ima_appraise_enabled(void) return 0; } -static inline void ima_inode_post_setattr(struct dentry *dentry) +static inline void ima_inode_post_setattr(struct user_namespace *mnt_userns, + struct dentry *dentry) { return; } diff --git a/security/integrity/ima/ima.h b/security/integrity/ima/ima.h index 6ebefec616e4..d78b1b2812fb 100644 --- a/security/integrity/ima/ima.h +++ b/security/integrity/ima/ima.h @@ -252,8 +252,9 @@ static inline void ima_process_queued_keys(void) {} #endif /* CONFIG_IMA_QUEUE_EARLY_BOOT_KEYS */ /* LIM API function definitions */ -int ima_get_action(struct inode *inode, const struct cred *cred, u32 secid, - int mask, enum ima_hooks func, int *pcr, +int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode, + const struct cred *cred, u32 secid, int mask, + enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, const char *keyring); int ima_must_measure(struct inode *inode, int mask, enum ima_hooks func); @@ -265,7 +266,8 @@ void ima_store_measurement(struct integrity_iint_cache *iint, struct file *file, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig, int pcr, struct ima_template_desc *template_desc); -void process_buffer_measurement(struct inode *inode, const void *buf, int size, +void process_buffer_measurement(struct user_namespace *mnt_userns, + struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *keyring); void ima_audit_measurement(struct integrity_iint_cache *iint, @@ -280,8 +282,9 @@ void ima_free_template_entry(struct ima_template_entry *entry); const char *ima_d_path(const struct path *path, char **pathbuf, char *filename); /* IMA policy related functions */ -int ima_match_policy(struct inode *inode, const struct cred *cred, u32 secid, - enum ima_hooks func, int mask, int flags, int *pcr, +int ima_match_policy(struct user_namespace *mnt_userns, struct inode *inode, + const struct cred *cred, u32 secid, enum ima_hooks func, + int mask, int flags, int *pcr, struct ima_template_desc **template_desc, const char *keyring); void ima_init_policy(void); @@ -312,7 +315,8 @@ int ima_appraise_measurement(enum ima_hooks func, struct file *file, const unsigned char *filename, struct evm_ima_xattr_data *xattr_value, int xattr_len, const struct modsig *modsig); -int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func); +int ima_must_appraise(struct user_namespace *mnt_userns, struct inode *inode, + int mask, enum ima_hooks func); void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file); enum integrity_status ima_get_cache_status(struct integrity_iint_cache *iint, enum ima_hooks func); @@ -339,7 +343,8 @@ static inline int ima_appraise_measurement(enum ima_hooks func, return INTEGRITY_UNKNOWN; } -static inline int ima_must_appraise(struct inode *inode, int mask, +static inline int ima_must_appraise(struct user_namespace *mnt_userns, + struct inode *inode, int mask, enum ima_hooks func) { return 0; diff --git a/security/integrity/ima/ima_api.c b/security/integrity/ima/ima_api.c index 4f39fb93f278..9ca02dd529c3 100644 --- a/security/integrity/ima/ima_api.c +++ b/security/integrity/ima/ima_api.c @@ -162,6 +162,7 @@ void ima_add_violation(struct file *file, const unsigned char *filename, /** * ima_get_action - appraise & measure decision based on policy. + * @mnt_userns: user namespace of the mount the inode was found from * @inode: pointer to the inode associated with the object being validated * @cred: pointer to credentials structure to validate * @secid: secid of the task being validated @@ -183,8 +184,9 @@ void ima_add_violation(struct file *file, const unsigned char *filename, * Returns IMA_MEASURE, IMA_APPRAISE mask. * */ -int ima_get_action(struct inode *inode, const struct cred *cred, u32 secid, - int mask, enum ima_hooks func, int *pcr, +int ima_get_action(struct user_namespace *mnt_userns, struct inode *inode, + const struct cred *cred, u32 secid, int mask, + enum ima_hooks func, int *pcr, struct ima_template_desc **template_desc, const char *keyring) { @@ -192,8 +194,8 @@ int ima_get_action(struct inode *inode, const struct cred *cred, u32 secid, flags &= ima_policy_flag; - return ima_match_policy(inode, cred, secid, func, mask, flags, pcr, - template_desc, keyring); + return ima_match_policy(mnt_userns, inode, cred, secid, func, mask, flags, + pcr, template_desc, keyring); } /* diff --git a/security/integrity/ima/ima_appraise.c b/security/integrity/ima/ima_appraise.c index 3492f0b2da1c..c1b463817db3 100644 --- a/security/integrity/ima/ima_appraise.c +++ b/security/integrity/ima/ima_appraise.c @@ -63,7 +63,8 @@ bool is_ima_appraise_enabled(void) * * Return 1 to appraise or hash */ -int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) +int ima_must_appraise(struct user_namespace *mnt_userns, struct inode *inode, + int mask, enum ima_hooks func) { u32 secid; @@ -71,8 +72,8 @@ int ima_must_appraise(struct inode *inode, int mask, enum ima_hooks func) return 0; security_task_getsecid(current, &secid); - return ima_match_policy(inode, current_cred(), secid, func, mask, - IMA_APPRAISE | IMA_HASH, NULL, NULL, NULL); + return ima_match_policy(mnt_userns, inode, current_cred(), secid, func, + mask, IMA_APPRAISE | IMA_HASH, NULL, NULL, NULL); } static int ima_fix_xattr(struct dentry *dentry, @@ -345,7 +346,7 @@ int ima_check_blacklist(struct integrity_iint_cache *iint, rc = is_binary_blacklisted(digest, digestsize); if ((rc == -EPERM) && (iint->flags & IMA_MEASURE)) - process_buffer_measurement(NULL, digest, digestsize, + process_buffer_measurement(NULL, NULL, digest, digestsize, "blacklisted-hash", NONE, pcr, NULL); } @@ -496,6 +497,7 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) /** * ima_inode_post_setattr - reflect file metadata changes + * @mnt_userns: user namespace of the mount the inode was found from * @dentry: pointer to the affected dentry * * Changes to a dentry's metadata might result in needing to appraise. @@ -503,7 +505,7 @@ void ima_update_xattr(struct integrity_iint_cache *iint, struct file *file) * This function is called from notify_change(), which expects the caller * to lock the inode's i_mutex. */ -void ima_inode_post_setattr(struct dentry *dentry) +void ima_inode_post_setattr(struct user_namespace *mnt_userns, struct dentry *dentry) { struct inode *inode = d_backing_inode(dentry); struct integrity_iint_cache *iint; @@ -513,7 +515,7 @@ void ima_inode_post_setattr(struct dentry *dentry) || !(inode->i_opflags & IOP_XATTR)) return; - action = ima_must_appraise(inode, MAY_ACCESS, POST_SETATTR); + action = ima_must_appraise(mnt_userns, inode, MAY_ACCESS, POST_SETATTR); if (!action) __vfs_removexattr(&init_user_ns, dentry, XATTR_NAME_IMA); iint = integrity_iint_find(inode); diff --git a/security/integrity/ima/ima_asymmetric_keys.c b/security/integrity/ima/ima_asymmetric_keys.c index 1c68c500c26f..9810f3bfa57f 100644 --- a/security/integrity/ima/ima_asymmetric_keys.c +++ b/security/integrity/ima/ima_asymmetric_keys.c @@ -58,7 +58,7 @@ void ima_post_key_create_or_update(struct key *keyring, struct key *key, * if the IMA policy is configured to measure a key linked * to the given keyring. */ - process_buffer_measurement(NULL, payload, payload_len, + process_buffer_measurement(NULL, NULL, payload, payload_len, keyring->description, KEY_CHECK, 0, keyring->description); } diff --git a/security/integrity/ima/ima_main.c b/security/integrity/ima/ima_main.c index 2d1af8899cab..f6dcf1d9eed3 100644 --- a/security/integrity/ima/ima_main.c +++ b/security/integrity/ima/ima_main.c @@ -218,8 +218,8 @@ static int process_measurement(struct file *file, const struct cred *cred, * bitmask based on the appraise/audit/measurement policy. * Included is the appraise submask. */ - action = ima_get_action(inode, cred, secid, mask, func, &pcr, - &template_desc, NULL); + action = ima_get_action(mnt_user_ns(file->f_path.mnt), inode, cred, + secid, mask, func, &pcr, &template_desc, NULL); violation_check = ((func == FILE_CHECK || func == MMAP_CHECK) && (ima_policy_flag & IMA_MEASURE)); if (!action && !violation_check) @@ -431,8 +431,9 @@ int ima_file_mprotect(struct vm_area_struct *vma, unsigned long prot) security_task_getsecid(current, &secid); inode = file_inode(vma->vm_file); - action = ima_get_action(inode, current_cred(), secid, MAY_EXEC, - MMAP_CHECK, &pcr, &template, 0); + action = ima_get_action(mnt_user_ns(vma->vm_file->f_path.mnt), inode, + current_cred(), secid, MAY_EXEC, MMAP_CHECK, + &pcr, &template, 0); /* Is the mmap'ed file in policy? */ if (!(action & (IMA_MEASURE | IMA_APPRAISE_SUBMASK))) @@ -562,18 +563,20 @@ EXPORT_SYMBOL_GPL(ima_file_hash); /** * ima_post_create_tmpfile - mark newly created tmpfile as new + * @mnt_userns: user namespace of the mount the inode was found from * @file : newly created tmpfile * * No measuring, appraising or auditing of newly created tmpfiles is needed. * Skip calling process_measurement(), but indicate which newly, created * tmpfiles are in policy. */ -void ima_post_create_tmpfile(struct inode *inode) +void ima_post_create_tmpfile(struct user_namespace *mnt_userns, + struct inode *inode) { struct integrity_iint_cache *iint; int must_appraise; - must_appraise = ima_must_appraise(inode, MAY_ACCESS, FILE_CHECK); + must_appraise = ima_must_appraise(mnt_userns, inode, MAY_ACCESS, FILE_CHECK); if (!must_appraise) return; @@ -589,18 +592,19 @@ void ima_post_create_tmpfile(struct inode *inode) /** * ima_post_path_mknod - mark as a new inode + * @mnt_userns: user namespace of the mount the inode was found from * @dentry: newly created dentry * * Mark files created via the mknodat syscall as new, so that the * file data can be written later. */ -void ima_post_path_mknod(struct dentry *dentry) +void ima_post_path_mknod(struct user_namespace *mnt_userns, struct dentry *dentry) { struct integrity_iint_cache *iint; struct inode *inode = dentry->d_inode; int must_appraise; - must_appraise = ima_must_appraise(inode, MAY_ACCESS, FILE_CHECK); + must_appraise = ima_must_appraise(mnt_userns, inode, MAY_ACCESS, FILE_CHECK); if (!must_appraise) return; @@ -780,6 +784,7 @@ int ima_post_load_data(char *buf, loff_t size, /* * process_buffer_measurement - Measure the buffer to ima log. + * @mnt_userns: user namespace of the mount the inode was found from * @inode: inode associated with the object being measured (NULL for KEY_CHECK) * @buf: pointer to the buffer that needs to be added to the log. * @size: size of buffer(in bytes). @@ -790,7 +795,8 @@ int ima_post_load_data(char *buf, loff_t size, * * Based on policy, the buffer is measured into the ima log. */ -void process_buffer_measurement(struct inode *inode, const void *buf, int size, +void process_buffer_measurement(struct user_namespace *mnt_userns, + struct inode *inode, const void *buf, int size, const char *eventname, enum ima_hooks func, int pcr, const char *keyring) { @@ -823,7 +829,7 @@ void process_buffer_measurement(struct inode *inode, const void *buf, int size, */ if (func) { security_task_getsecid(current, &secid); - action = ima_get_action(inode, current_cred(), secid, 0, func, + action = ima_get_action(mnt_userns, inode, current_cred(), secid, 0, func, &pcr, &template, keyring); if (!(action & IMA_MEASURE)) return; @@ -895,7 +901,8 @@ void ima_kexec_cmdline(int kernel_fd, const void *buf, int size) if (!f.file) return; - process_buffer_measurement(file_inode(f.file), buf, size, + process_buffer_measurement(mnt_user_ns(f.file->f_path.mnt), + file_inode(f.file), buf, size, "kexec-cmdline", KEXEC_CMDLINE, 0, NULL); fdput(f); } diff --git a/security/integrity/ima/ima_policy.c b/security/integrity/ima/ima_policy.c index 9b5adeaa47fc..2e8045ebb1b5 100644 --- a/security/integrity/ima/ima_policy.c +++ b/security/integrity/ima/ima_policy.c @@ -488,6 +488,7 @@ static bool ima_match_keyring(struct ima_rule_entry *rule, /** * ima_match_rules - determine whether an inode matches the policy rule. * @rule: a pointer to a rule + * @mnt_userns: user namespace of the mount the inode was found from * @inode: a pointer to an inode * @cred: a pointer to a credentials structure for user validation * @secid: the secid of the task to be validated @@ -497,10 +498,10 @@ static bool ima_match_keyring(struct ima_rule_entry *rule, * * Returns true on rule match, false on failure. */ -static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode, +static bool ima_match_rules(struct ima_rule_entry *rule, + struct user_namespace *mnt_userns, struct inode *inode, const struct cred *cred, u32 secid, - enum ima_hooks func, int mask, - const char *keyring) + enum ima_hooks func, int mask, const char *keyring) { int i; @@ -539,7 +540,7 @@ static bool ima_match_rules(struct ima_rule_entry *rule, struct inode *inode, } if ((rule->flags & IMA_FOWNER) && - !rule->fowner_op(inode->i_uid, rule->fowner)) + !rule->fowner_op(i_uid_into_mnt(mnt_userns, inode), rule->fowner)) return false; for (i = 0; i < MAX_LSM_RULES; i++) { int rc = 0; @@ -602,6 +603,7 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func) /** * ima_match_policy - decision based on LSM and other conditions + * @mnt_userns: user namespace of the mount the inode was found from * @inode: pointer to an inode for which the policy decision is being made * @cred: pointer to a credentials structure for which the policy decision is * being made @@ -620,8 +622,9 @@ static int get_subaction(struct ima_rule_entry *rule, enum ima_hooks func) * list when walking it. Reads are many orders of magnitude more numerous * than writes so ima_match_policy() is classical RCU candidate. */ -int ima_match_policy(struct inode *inode, const struct cred *cred, u32 secid, - enum ima_hooks func, int mask, int flags, int *pcr, +int ima_match_policy(struct user_namespace *mnt_userns, struct inode *inode, + const struct cred *cred, u32 secid, enum ima_hooks func, + int mask, int flags, int *pcr, struct ima_template_desc **template_desc, const char *keyring) { @@ -637,8 +640,8 @@ int ima_match_policy(struct inode *inode, const struct cred *cred, u32 secid, if (!(entry->action & actmask)) continue; - if (!ima_match_rules(entry, inode, cred, secid, func, mask, - keyring)) + if (!ima_match_rules(entry, mnt_userns, inode, cred, secid, func, + mask, keyring)) continue; action |= entry->flags & IMA_ACTION_FLAGS; diff --git a/security/integrity/ima/ima_queue_keys.c b/security/integrity/ima/ima_queue_keys.c index 69a8626a35c0..2bacc4f3e6ba 100644 --- a/security/integrity/ima/ima_queue_keys.c +++ b/security/integrity/ima/ima_queue_keys.c @@ -158,7 +158,7 @@ void ima_process_queued_keys(void) list_for_each_entry_safe(entry, tmp, &ima_keys, list) { if (!timer_expired) - process_buffer_measurement(NULL, entry->payload, + process_buffer_measurement(NULL, NULL, entry->payload, entry->payload_len, entry->keyring_name, KEY_CHECK, 0, From patchwork Thu Dec 3 23:57:30 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950269 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 93DF7C2BB3F for ; Fri, 4 Dec 2020 00:13:20 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 522B922224 for ; Fri, 4 Dec 2020 00:13:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502162AbgLDANT (ORCPT ); Thu, 3 Dec 2020 19:13:19 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:43400 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S2502147AbgLDANS (ORCPT ); Thu, 3 Dec 2020 19:13:18 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyYC-0007ka-U4; Fri, 04 Dec 2020 00:02:17 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 34/40] fat: handle idmapped mounts Date: Fri, 4 Dec 2020 00:57:30 +0100 Message-Id: <20201203235736.3528991-35-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Let fat handle idmapped mounts. This allows to have the same fat mount appear in multiple locations with different id mappings. This allows to expose a vfat formatted USB stick to multiple user with different ids on the host or in user namespaces allowing for dac permissions: mount -o uid=1000,gid=1000 /dev/sdb /mnt u1001@f2-vm:/lower1$ ls -ln /mnt/ total 4 -rwxr-xr-x 1 1000 1000 4 Oct 28 03:44 aaa -rwxr-xr-x 1 1000 1000 0 Oct 28 01:09 bbb -rwxr-xr-x 1 1000 1000 0 Oct 28 01:10 ccc -rwxr-xr-x 1 1000 1000 0 Oct 28 03:46 ddd -rwxr-xr-x 1 1000 1000 0 Oct 28 04:01 eee mount-idmapped --map-mount b:1000:1001:1 u1001@f2-vm:/lower1$ ls -ln /lower1/ total 4 -rwxr-xr-x 1 1001 1001 4 Oct 28 03:44 aaa -rwxr-xr-x 1 1001 1001 0 Oct 28 01:09 bbb -rwxr-xr-x 1 1001 1001 0 Oct 28 01:10 ccc -rwxr-xr-x 1 1001 1001 0 Oct 28 03:46 ddd -rwxr-xr-x 1 1001 1001 0 Oct 28 04:01 eee u1001@f2-vm:/lower1$ touch /lower1/fff u1001@f2-vm:/lower1$ ls -ln /lower1/fff -rwxr-xr-x 1 1001 1001 0 Oct 28 04:03 /lower1/fff u1001@f2-vm:/lower1$ ls -ln /mnt/fff -rwxr-xr-x 1 1000 1000 0 Oct 28 04:03 /mnt/fff Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Mauricio Vásquez Bernal : - Fix mount example in commit message. - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/fat/file.c | 15 ++++++++------- fs/fat/namei_msdos.c | 2 +- fs/fat/namei_vfat.c | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/fs/fat/file.c b/fs/fat/file.c index 73acd0843d92..6a618e9450c8 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -398,7 +398,7 @@ int fat_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags) { struct inode *inode = d_inode(path->dentry); - generic_fillattr(&init_user_ns, inode, stat); + generic_fillattr(mnt_userns, inode, stat); stat->blksize = MSDOS_SB(inode->i_sb)->cluster_size; if (MSDOS_SB(inode->i_sb)->options.nfs == FAT_NFS_NOSTALE_RO) { @@ -447,12 +447,13 @@ static int fat_sanitize_mode(const struct msdos_sb_info *sbi, return 0; } -static int fat_allow_set_time(struct msdos_sb_info *sbi, struct inode *inode) +static int fat_allow_set_time(struct user_namespace *mnt_userns, + struct msdos_sb_info *sbi, struct inode *inode) { umode_t allow_utime = sbi->options.allow_utime; - if (!uid_eq(current_fsuid(), inode->i_uid)) { - if (in_group_p(inode->i_gid)) + if (!uid_eq(current_fsuid(), i_uid_into_mnt(mnt_userns, inode))) { + if (in_group_p(i_gid_into_mnt(mnt_userns, inode))) allow_utime >>= 3; if (allow_utime & MAY_WRITE) return 1; @@ -477,11 +478,11 @@ int fat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, /* Check for setting the inode time. */ ia_valid = attr->ia_valid; if (ia_valid & TIMES_SET_FLAGS) { - if (fat_allow_set_time(sbi, inode)) + if (fat_allow_set_time(mnt_userns, sbi, inode)) attr->ia_valid &= ~TIMES_SET_FLAGS; } - error = setattr_prepare(&init_user_ns, dentry, attr); + error = setattr_prepare(mnt_userns, dentry, attr); attr->ia_valid = ia_valid; if (error) { if (sbi->options.quiet) @@ -551,7 +552,7 @@ int fat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, fat_truncate_time(inode, &attr->ia_mtime, S_MTIME); attr->ia_valid &= ~(ATTR_ATIME|ATTR_CTIME|ATTR_MTIME); - setattr_copy(&init_user_ns, inode, attr); + setattr_copy(mnt_userns, inode, attr); mark_inode_dirty(inode); out: return error; diff --git a/fs/fat/namei_msdos.c b/fs/fat/namei_msdos.c index a8f3375d9d10..efba301d68ae 100644 --- a/fs/fat/namei_msdos.c +++ b/fs/fat/namei_msdos.c @@ -667,7 +667,7 @@ static struct file_system_type msdos_fs_type = { .name = "msdos", .mount = msdos_mount, .kill_sb = kill_block_super, - .fs_flags = FS_REQUIRES_DEV, + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, }; MODULE_ALIAS_FS("msdos"); diff --git a/fs/fat/namei_vfat.c b/fs/fat/namei_vfat.c index 23936ecf79a5..5369d82e0bfb 100644 --- a/fs/fat/namei_vfat.c +++ b/fs/fat/namei_vfat.c @@ -1063,7 +1063,7 @@ static struct file_system_type vfat_fs_type = { .name = "vfat", .mount = vfat_mount, .kill_sb = kill_block_super, - .fs_flags = FS_REQUIRES_DEV, + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, }; MODULE_ALIAS_FS("vfat"); From patchwork Thu Dec 3 23:57:31 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950215 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1DFC6C1B0E3 for ; Fri, 4 Dec 2020 00:10:11 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id E3122223E4 for ; Fri, 4 Dec 2020 00:10:10 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1731902AbgLDAKG (ORCPT ); Thu, 3 Dec 2020 19:10:06 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42760 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAKF (ORCPT ); Thu, 3 Dec 2020 19:10:05 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyYE-0007ka-Mx; Fri, 04 Dec 2020 00:02:18 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 35/40] ext4: support idmapped mounts Date: Fri, 4 Dec 2020 00:57:31 +0100 Message-Id: <20201203235736.3528991-36-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org This enables ext4 to support idmapped mounts. All dedicated helpers we need for this exist. The vfs will have already made sure that the fsids can be translated after having been shifted if they are on an idmapped mount. This implements helpers for the new inode operations that we've added. The core change is the allocation of a new inode based on the mount's user namespace. If the initial user namespace is passed nothing changes so non-idmapped mounts will see identical behavior as before. It is also noteworthy that the idmapped mount implementation allows us to cleanly handle ioctls() too. I've kept this as a single patch for now since the change is overall fairly mechanical but I'm happy to split this. Let's create simple example where we idmap an ext4 filesystem: root@f2-vm:~# truncate -s 5G ext4.img root@f2-vm:~# mkfs.ext4 ./ext4.img mke2fs 1.45.5 (07-Jan-2020) Discarding device blocks: done Creating filesystem with 1310720 4k blocks and 327680 inodes Filesystem UUID: 3fd91794-c6ca-4b0f-9964-289a000919cf Superblock backups stored on blocks: 32768, 98304, 163840, 229376, 294912, 819200, 884736 Allocating group tables: done Writing inode tables: done Creating journal (16384 blocks): done Writing superblocks and filesystem accounting information: done root@f2-vm:~# losetup -f --show ./ext4.img /dev/loop0 root@f2-vm:~# mount /dev/loop0 /mnt root@f2-vm:~# ls -al /mnt/ total 24 drwxr-xr-x 3 root root 4096 Oct 28 13:34 . drwxr-xr-x 30 root root 4096 Oct 28 13:22 .. drwx------ 2 root root 16384 Oct 28 13:34 lost+found # Let's create an idmapped mount at /idmapped1 where we map uid and gid # 0 to uid and gid 1000 root@f2-vm:/# ./mount-idmapped --map-mount b:0:1000:1 /mnt/ /idmapped1/ root@f2-vm:/# ls -al /idmapped1/ total 24 drwxr-xr-x 3 ubuntu ubuntu 4096 Oct 28 13:34 . drwxr-xr-x 30 root root 4096 Oct 28 13:22 .. drwx------ 2 ubuntu ubuntu 16384 Oct 28 13:34 lost+found # Let's create an idmapped mount at /idmapped2 where we map uid and gid # 0 to uid and gid 2000 root@f2-vm:/# ./mount-idmapped --map-mount b:0:2000:1 /mnt/ /idmapped2/ root@f2-vm:/# ls -al /idmapped2/ total 24 drwxr-xr-x 3 2000 2000 4096 Oct 28 13:34 . drwxr-xr-x 31 root root 4096 Oct 28 13:39 .. drwx------ 2 2000 2000 16384 Oct 28 13:34 lost+found Let's create another example where we idmap the rootfs filesystem without a mapping for uid 0 and gid 0: # Create an idmapped mount of for a full POSIX range of rootfs under # /mnt but without a mapping for uid 0 to reduce attack surface root@f2-vm:/# ./mount-idmapped --map-mount b:1:1:65536 / /mnt/ # Since we don't have a mapping for uid and gid 0 all files owned by # uid and gid 0 should show up as uid and gid 65534: root@f2-vm:/# ls -al /mnt/ total 664 drwxr-xr-x 31 nobody nogroup 4096 Oct 28 13:39 . drwxr-xr-x 31 root root 4096 Oct 28 13:39 .. lrwxrwxrwx 1 nobody nogroup 7 Aug 25 07:44 bin -> usr/bin drwxr-xr-x 4 nobody nogroup 4096 Oct 28 13:17 boot drwxr-xr-x 2 nobody nogroup 4096 Aug 25 07:48 dev drwxr-xr-x 81 nobody nogroup 4096 Oct 28 04:00 etc drwxr-xr-x 4 nobody nogroup 4096 Oct 28 04:00 home lrwxrwxrwx 1 nobody nogroup 7 Aug 25 07:44 lib -> usr/lib lrwxrwxrwx 1 nobody nogroup 9 Aug 25 07:44 lib32 -> usr/lib32 lrwxrwxrwx 1 nobody nogroup 9 Aug 25 07:44 lib64 -> usr/lib64 lrwxrwxrwx 1 nobody nogroup 10 Aug 25 07:44 libx32 -> usr/libx32 drwx------ 2 nobody nogroup 16384 Aug 25 07:47 lost+found drwxr-xr-x 2 nobody nogroup 4096 Aug 25 07:44 media drwxr-xr-x 31 nobody nogroup 4096 Oct 28 13:39 mnt drwxr-xr-x 2 nobody nogroup 4096 Aug 25 07:44 opt drwxr-xr-x 2 nobody nogroup 4096 Apr 15 2020 proc drwx--x--x 6 nobody nogroup 4096 Oct 28 13:34 root drwxr-xr-x 2 nobody nogroup 4096 Aug 25 07:46 run lrwxrwxrwx 1 nobody nogroup 8 Aug 25 07:44 sbin -> usr/sbin drwxr-xr-x 2 nobody nogroup 4096 Aug 25 07:44 srv drwxr-xr-x 2 nobody nogroup 4096 Apr 15 2020 sys drwxrwxrwt 10 nobody nogroup 4096 Oct 28 13:19 tmp drwxr-xr-x 14 nobody nogroup 4096 Oct 20 13:00 usr drwxr-xr-x 12 nobody nogroup 4096 Aug 25 07:45 var # Since we do have a mapping for uid and gid 1000 all files owned by # uid and gid 1000 should simply show up as uid and gid 1000: root@f2-vm:/# ls -al /mnt/home/ubuntu/ total 40 drwxr-xr-x 3 ubuntu ubuntu 4096 Oct 28 00:43 . drwxr-xr-x 4 nobody nogroup 4096 Oct 28 04:00 .. -rw------- 1 ubuntu ubuntu 2936 Oct 28 12:26 .bash_history -rw-r--r-- 1 ubuntu ubuntu 220 Feb 25 2020 .bash_logout -rw-r--r-- 1 ubuntu ubuntu 3771 Feb 25 2020 .bashrc -rw-r--r-- 1 ubuntu ubuntu 807 Feb 25 2020 .profile -rw-r--r-- 1 ubuntu ubuntu 0 Oct 16 16:11 .sudo_as_admin_successful -rw------- 1 ubuntu ubuntu 1144 Oct 28 00:43 .viminfo Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ unchanged /* v3 */ unchanged /* v4 */ - Mauricio Vásquez Bernal : - Fix spelling mistake in ext4's new idmapped mount config option. - Christoph Hellwig : - Drop the config option. If ever want an administrator to disable this feature we can simply introduce a sysctl. - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/ext4/acl.c | 2 +- fs/ext4/ext4.h | 13 +++++++------ fs/ext4/ialloc.c | 7 ++++--- fs/ext4/inode.c | 11 ++++++----- fs/ext4/ioctl.c | 19 +++++++++++-------- fs/ext4/namei.c | 30 ++++++++++++++++-------------- fs/ext4/super.c | 2 +- 7 files changed, 46 insertions(+), 38 deletions(-) diff --git a/fs/ext4/acl.c b/fs/ext4/acl.c index 39f453ad05a0..c5eaffccecc3 100644 --- a/fs/ext4/acl.c +++ b/fs/ext4/acl.c @@ -246,7 +246,7 @@ ext4_set_acl(struct user_namespace *mnt_userns, struct inode *inode, ext4_fc_start_update(inode); if ((type == ACL_TYPE_ACCESS) && acl) { - error = posix_acl_update_mode(&init_user_ns, inode, &mode, &acl); + error = posix_acl_update_mode(mnt_userns, inode, &mode, &acl); if (error) goto out_stop; if (mode != inode->i_mode) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index d1512ba7be9e..9bb65e1e41b9 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -2725,18 +2725,19 @@ extern int ext4fs_dirhash(const struct inode *dir, const char *name, int len, /* ialloc.c */ extern int ext4_mark_inode_used(struct super_block *sb, int ino); -extern struct inode *__ext4_new_inode(handle_t *, struct inode *, umode_t, +extern struct inode *__ext4_new_inode(struct user_namespace *, handle_t *, + struct inode *, umode_t, const struct qstr *qstr, __u32 goal, uid_t *owner, __u32 i_flags, int handle_type, unsigned int line_no, int nblocks); -#define ext4_new_inode(handle, dir, mode, qstr, goal, owner, i_flags) \ - __ext4_new_inode((handle), (dir), (mode), (qstr), (goal), (owner), \ - i_flags, 0, 0, 0) -#define ext4_new_inode_start_handle(dir, mode, qstr, goal, owner, \ +#define ext4_new_inode(handle, dir, mode, qstr, goal, owner, i_flags) \ + __ext4_new_inode(&init_user_ns, (handle), (dir), (mode), (qstr), \ + (goal), (owner), i_flags, 0, 0, 0) +#define ext4_new_inode_start_handle(mnt_userns, dir, mode, qstr, goal, owner, \ type, nblocks) \ - __ext4_new_inode(NULL, (dir), (mode), (qstr), (goal), (owner), \ + __ext4_new_inode((mnt_userns), NULL, (dir), (mode), (qstr), (goal), (owner), \ 0, (type), __LINE__, (nblocks)) diff --git a/fs/ext4/ialloc.c b/fs/ext4/ialloc.c index 00c1ec6eee16..bf9028950a51 100644 --- a/fs/ext4/ialloc.c +++ b/fs/ext4/ialloc.c @@ -919,7 +919,8 @@ static int ext4_xattr_credits_for_new_inode(struct inode *dir, mode_t mode, * For other inodes, search forward from the parent directory's block * group to find a free inode. */ -struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir, +struct inode *__ext4_new_inode(struct user_namespace *mnt_userns, + handle_t *handle, struct inode *dir, umode_t mode, const struct qstr *qstr, __u32 goal, uid_t *owner, __u32 i_flags, int handle_type, unsigned int line_no, @@ -969,10 +970,10 @@ struct inode *__ext4_new_inode(handle_t *handle, struct inode *dir, i_gid_write(inode, owner[1]); } else if (test_opt(sb, GRPID)) { inode->i_mode = mode; - inode->i_uid = current_fsuid(); + inode->i_uid = fsuid_into_mnt(mnt_userns); inode->i_gid = dir->i_gid; } else - inode_init_owner(&init_user_ns, inode, dir, mode); + inode_init_owner(mnt_userns, inode, dir, mode); if (ext4_has_feature_project(sb) && ext4_test_inode_flag(dir, EXT4_INODE_PROJINHERIT)) diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index f9cf9e81fcb7..0bb19194627d 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -20,6 +20,7 @@ */ #include +#include #include #include #include @@ -5325,7 +5326,7 @@ int ext4_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, ATTR_GID | ATTR_TIMES_SET)))) return -EPERM; - error = setattr_prepare(&init_user_ns, dentry, attr); + error = setattr_prepare(mnt_userns, dentry, attr); if (error) return error; @@ -5500,7 +5501,7 @@ int ext4_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, } if (!error) { - setattr_copy(&init_user_ns, inode, attr); + setattr_copy(mnt_userns, inode, attr); mark_inode_dirty(inode); } @@ -5512,7 +5513,7 @@ int ext4_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, ext4_orphan_del(NULL, inode); if (!error && (ia_valid & ATTR_MODE)) - rc = posix_acl_chmod(&init_user_ns, inode, inode->i_mode); + rc = posix_acl_chmod(mnt_userns, inode, inode->i_mode); err_out: if (error) @@ -5559,7 +5560,7 @@ int ext4_getattr(struct user_namespace *mnt_userns, const struct path *path, STATX_ATTR_NODUMP | STATX_ATTR_VERITY); - generic_fillattr(&init_user_ns, inode, stat); + generic_fillattr(mnt_userns, inode, stat); return 0; } @@ -5570,7 +5571,7 @@ int ext4_file_getattr(struct user_namespace *mnt_userns, struct inode *inode = d_inode(path->dentry); u64 delalloc_blocks; - ext4_getattr(&init_user_ns, path, stat, request_mask, query_flags); + ext4_getattr(mnt_userns, path, stat, request_mask, query_flags); /* * If there is inline data in the inode, the inode will normally not diff --git a/fs/ext4/ioctl.c b/fs/ext4/ioctl.c index e35aba820254..36f53ab933f8 100644 --- a/fs/ext4/ioctl.c +++ b/fs/ext4/ioctl.c @@ -107,10 +107,12 @@ void ext4_reset_inode_seed(struct inode *inode) * important fields of the inodes. * * @sb: the super block of the filesystem + * @mnt_userns: user namespace of the mount the inode was found from * @inode: the inode to swap with EXT4_BOOT_LOADER_INO * */ static long swap_inode_boot_loader(struct super_block *sb, + struct user_namespace *mnt_userns, struct inode *inode) { handle_t *handle; @@ -139,7 +141,7 @@ static long swap_inode_boot_loader(struct super_block *sb, } if (IS_RDONLY(inode) || IS_APPEND(inode) || IS_IMMUTABLE(inode) || - !inode_owner_or_capable(&init_user_ns, inode) || !capable(CAP_SYS_ADMIN)) { + !inode_owner_or_capable(mnt_userns, inode) || !capable(CAP_SYS_ADMIN)) { err = -EPERM; goto journal_err_out; } @@ -814,6 +816,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) struct inode *inode = file_inode(filp); struct super_block *sb = inode->i_sb; struct ext4_inode_info *ei = EXT4_I(inode); + struct user_namespace *mnt_userns = mnt_user_ns(filp->f_path.mnt); unsigned int flags; ext4_debug("cmd = %u, arg = %lu\n", cmd, arg); @@ -829,7 +832,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case FS_IOC_SETFLAGS: { int err; - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EACCES; if (get_user(flags, (int __user *) arg)) @@ -871,7 +874,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) __u32 generation; int err; - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EPERM; if (ext4_has_metadata_csum(inode->i_sb)) { @@ -1010,7 +1013,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case EXT4_IOC_MIGRATE: { int err; - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EACCES; err = mnt_want_write_file(filp); @@ -1032,7 +1035,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case EXT4_IOC_ALLOC_DA_BLKS: { int err; - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EACCES; err = mnt_want_write_file(filp); @@ -1051,7 +1054,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) err = mnt_want_write_file(filp); if (err) return err; - err = swap_inode_boot_loader(sb, inode); + err = swap_inode_boot_loader(sb, mnt_userns, inode); mnt_drop_write_file(filp); return err; } @@ -1214,7 +1217,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) case EXT4_IOC_CLEAR_ES_CACHE: { - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EACCES; ext4_clear_inode_es(inode); return 0; @@ -1260,7 +1263,7 @@ static long __ext4_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) return -EFAULT; /* Make sure caller has proper permission */ - if (!inode_owner_or_capable(&init_user_ns, inode)) + if (!inode_owner_or_capable(mnt_userns, inode)) return -EACCES; if (fa.fsx_xflags & ~EXT4_SUPPORTED_FS_XFLAGS) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index f9560829c4f8..1628342fdecb 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -2616,8 +2616,8 @@ static int ext4_create(struct user_namespace *mnt_userns, struct inode *dir, credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3); retry: - inode = ext4_new_inode_start_handle(dir, mode, &dentry->d_name, 0, - NULL, EXT4_HT_DIR, credits); + inode = ext4_new_inode_start_handle(mnt_userns, dir, mode, &dentry->d_name, + 0, NULL, EXT4_HT_DIR, credits); handle = ext4_journal_current_handle(); err = PTR_ERR(inode); if (!IS_ERR(inode)) { @@ -2651,8 +2651,8 @@ static int ext4_mknod(struct user_namespace *mnt_userns, struct inode *dir, credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3); retry: - inode = ext4_new_inode_start_handle(dir, mode, &dentry->d_name, 0, - NULL, EXT4_HT_DIR, credits); + inode = ext4_new_inode_start_handle(mnt_userns, dir, mode, &dentry->d_name, + 0, NULL, EXT4_HT_DIR, credits); handle = ext4_journal_current_handle(); err = PTR_ERR(inode); if (!IS_ERR(inode)) { @@ -2683,7 +2683,7 @@ static int ext4_tmpfile(struct user_namespace *mnt_userns, struct inode *dir, return err; retry: - inode = ext4_new_inode_start_handle(dir, mode, + inode = ext4_new_inode_start_handle(mnt_userns, dir, mode, NULL, 0, NULL, EXT4_HT_DIR, EXT4_MAXQUOTAS_INIT_BLOCKS(dir->i_sb) + @@ -2798,7 +2798,7 @@ static int ext4_mkdir(struct user_namespace *mnt_userns, struct inode *dir, credits = (EXT4_DATA_TRANS_BLOCKS(dir->i_sb) + EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3); retry: - inode = ext4_new_inode_start_handle(dir, S_IFDIR | mode, + inode = ext4_new_inode_start_handle(mnt_userns, dir, S_IFDIR | mode, &dentry->d_name, 0, NULL, EXT4_HT_DIR, credits); handle = ext4_journal_current_handle(); @@ -3335,7 +3335,7 @@ static int ext4_symlink(struct user_namespace *mnt_userns, struct inode *dir, EXT4_INDEX_EXTRA_TRANS_BLOCKS + 3; } - inode = ext4_new_inode_start_handle(dir, S_IFLNK|S_IRWXUGO, + inode = ext4_new_inode_start_handle(mnt_userns, dir, S_IFLNK|S_IRWXUGO, &dentry->d_name, 0, NULL, EXT4_HT_DIR, credits); handle = ext4_journal_current_handle(); @@ -3667,7 +3667,8 @@ static void ext4_update_dir_count(handle_t *handle, struct ext4_renament *ent) } } -static struct inode *ext4_whiteout_for_rename(struct ext4_renament *ent, +static struct inode *ext4_whiteout_for_rename(struct user_namespace *mnt_userns, + struct ext4_renament *ent, int credits, handle_t **h) { struct inode *wh; @@ -3681,7 +3682,8 @@ static struct inode *ext4_whiteout_for_rename(struct ext4_renament *ent, credits += (EXT4_MAXQUOTAS_TRANS_BLOCKS(ent->dir->i_sb) + EXT4_XATTR_TRANS_BLOCKS + 4); retry: - wh = ext4_new_inode_start_handle(ent->dir, S_IFCHR | WHITEOUT_MODE, + wh = ext4_new_inode_start_handle(mnt_userns, ent->dir, + S_IFCHR | WHITEOUT_MODE, &ent->dentry->d_name, 0, NULL, EXT4_HT_DIR, credits); @@ -3708,9 +3710,9 @@ static struct inode *ext4_whiteout_for_rename(struct ext4_renament *ent, * while new_{dentry,inode) refers to the destination dentry/inode * This comes from rename(const char *oldpath, const char *newpath) */ -static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, - struct inode *new_dir, struct dentry *new_dentry, - unsigned int flags) +static int ext4_rename(struct user_namespace *mnt_userns, struct inode *old_dir, + struct dentry *old_dentry, struct inode *new_dir, + struct dentry *new_dentry, unsigned int flags) { handle_t *handle = NULL; struct ext4_renament old = { @@ -3794,7 +3796,7 @@ static int ext4_rename(struct inode *old_dir, struct dentry *old_dentry, goto end_rename; } } else { - whiteout = ext4_whiteout_for_rename(&old, credits, &handle); + whiteout = ext4_whiteout_for_rename(mnt_userns, &old, credits, &handle); if (IS_ERR(whiteout)) { retval = PTR_ERR(whiteout); whiteout = NULL; @@ -4109,7 +4111,7 @@ static int ext4_rename2(struct user_namespace *mnt_userns, new_dir, new_dentry); } - return ext4_rename(old_dir, old_dentry, new_dir, new_dentry, flags); + return ext4_rename(mnt_userns, old_dir, old_dentry, new_dir, new_dentry, flags); } /* diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 94472044f4c1..9009ab959c47 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -6627,7 +6627,7 @@ static struct file_system_type ext4_fs_type = { .name = "ext4", .mount = ext4_mount, .kill_sb = kill_block_super, - .fs_flags = FS_REQUIRES_DEV, + .fs_flags = FS_REQUIRES_DEV | FS_ALLOW_IDMAP, }; MODULE_ALIAS_FS("ext4"); From patchwork Thu Dec 3 23:57:32 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950207 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7AD78C4361A for ; Fri, 4 Dec 2020 00:08:36 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 27F5122224 for ; Fri, 4 Dec 2020 00:08:36 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502059AbgLDAIf (ORCPT ); Thu, 3 Dec 2020 19:08:35 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42629 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAIe (ORCPT ); Thu, 3 Dec 2020 19:08:34 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyYG-0007ka-IY; Fri, 04 Dec 2020 00:02:20 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 36/40] ecryptfs: do not mount on top of idmapped mounts Date: Fri, 4 Dec 2020 00:57:32 +0100 Message-Id: <20201203235736.3528991-37-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Prevent ecryptfs from being mounted on top of idmapped mounts until we have ported it to handle this case and added proper testing for it. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced /* v3 */ - David Howells : - Adapt check after removing mnt_idmapped() helper. /* v4 */ unchanged --- fs/ecryptfs/main.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/fs/ecryptfs/main.c b/fs/ecryptfs/main.c index e63259fdef28..cdf40a54a35d 100644 --- a/fs/ecryptfs/main.c +++ b/fs/ecryptfs/main.c @@ -531,6 +531,12 @@ static struct dentry *ecryptfs_mount(struct file_system_type *fs_type, int flags goto out_free; } + if (mnt_user_ns(path.mnt) != &init_user_ns) { + rc = -EINVAL; + printk(KERN_ERR "Mounting on idmapped mounts currently disallowed\n"); + goto out_free; + } + if (check_ruid && !uid_eq(d_inode(path.dentry)->i_uid, current_uid())) { rc = -EPERM; printk(KERN_ERR "Mount of device (uid: %d) not owned by " From patchwork Thu Dec 3 23:57:33 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950205 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 5D9E5C4361B for ; Fri, 4 Dec 2020 00:08:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 19D49224BD for ; Fri, 4 Dec 2020 00:08:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2502056AbgLDAH6 (ORCPT ); Thu, 3 Dec 2020 19:07:58 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42580 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAH5 (ORCPT ); Thu, 3 Dec 2020 19:07:57 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyYI-0007ka-DY; Fri, 04 Dec 2020 00:02:22 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 37/40] overlayfs: do not mount on top of idmapped mounts Date: Fri, 4 Dec 2020 00:57:33 +0100 Message-Id: <20201203235736.3528991-38-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Prevent overlayfs from being mounted on top of idmapped mounts until we have ported it to handle this case and added proper testing for it. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced /* v3 */ - Amir Goldstein : - Move check for idmapped lower layers into ovl_mount_dir_noesc(). - David Howells : - Adapt check after removing mnt_idmapped() helper. /* v4 */ unchanged --- fs/overlayfs/super.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index aed21e569390..2be17c769322 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -811,6 +811,10 @@ static int ovl_mount_dir_noesc(const char *name, struct path *path) pr_err("filesystem on '%s' not supported\n", name); goto out_put; } + if (mnt_user_ns(path->mnt) != &init_user_ns) { + pr_err("idmapped layers are currently not supported\n"); + goto out_put; + } if (!d_is_dir(path->dentry)) { pr_err("'%s' not a directory\n", name); goto out_put; From patchwork Thu Dec 3 23:57:34 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950121 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id BB523C4361B for ; Fri, 4 Dec 2020 00:06:17 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 5EEB4224BE for ; Fri, 4 Dec 2020 00:06:17 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2389481AbgLDAGB (ORCPT ); Thu, 3 Dec 2020 19:06:01 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42447 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727149AbgLDAGA (ORCPT ); Thu, 3 Dec 2020 19:06:00 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyYK-0007ka-6M; Fri, 04 Dec 2020 00:02:24 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig , =?utf-8?q?Mauricio_V=C3=A1squez_Bernal?= Subject: [PATCH v4 38/40] fs: introduce MOUNT_ATTR_IDMAP Date: Fri, 4 Dec 2020 00:57:34 +0100 Message-Id: <20201203235736.3528991-39-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Introduce a new mount bind mount property to allow idmapping mounts. The MOUNT_ATTR_IDMAP flag can be set via the new mount_setattr() syscall together with a file descriptor referring to a user namespace. The user namespace referenced by the namespace file descriptor will be attached to the bind mount. All interactions with the filesystem going through that mount will be idmapped according to the mapping specified in the user namespace attached to it. Using user namespaces to mark mounts means we can reuse all the existing infrastructure in the kernel that already exists to handle idmappings and can also use this for permission checking to allow unprivileged user to create idmapped mounts in the future. Idmapping a mount is decoupled from the caller's user and mount namespace. This means idmapped mounts can be created in the initial user namespace which is an important use-case for systemd-homed, portable usb-sticks between systems, sharing data between the initial user namespace and unprivileged containers, and other use-cases that have been brought up. For example, assume a home directory where all files are owned by uid and gid 1000 and the home directory is brought to a new laptop where the user has id 12345. The system administrator can simply create a mount of this home directory with a mapping of 1000:12345:1 and other mappings to indicate the ids should be kept. (With this it is e.g. also possible to create idmapped mounts on the host with an identity mapping 1:1:100000 where the root user is not mapped. A user with root access that e.g. has been pivot rooted into such a mount on the host will be not be able to execute, read, write, or create files as root.) Given that idmapping a mount is decoupled from the caller's user namespace a sufficiently privileged process such as a container manager can set up a shifted mount for the container and the container can simply pivot root to it. There's no need for the container to do anything. The mount will appear correctly mapped independent of the user namespace the container uses. This means we don't need to mark a mount as idmappable. In order to create an idmapped mount the caller must currently be privileged in the user namespace of the superblock the mount belongs to. Currently, once a mount has been idmapped we don't allow it to change its mapping. This can be changed in the future if the use-cases arise. Cc: Christoph Hellwig Cc: David Howells Cc: Mauricio Vásquez Bernal Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ - Christoph Hellwig : - Drop kconfig option to make vfs idmappings unconditional. - Move introduction of MOUNT_ATTR_IDMAP to the end of the series after all internal changes have been done. - Move MOUNT_ATTR_IDMAP handling from build_mount_kattr() to separate build_mount_idmapped() helper. - Move MNT_IDMAPPED handling from do_mount_setattr() into separate do_mount_idmap() helper. - Use more helpers instead of one big function for mount attribute changes. - Mauricio Vásquez Bernal : - Recalculate flags before checking can_change_locked_flags(). /* v3 */ - David Howells : - Use smp_load_acquire() and smp_store_release() in mnt_user_ns() and do_idmap_mount(). - Remove all references to MNT_IDMAPPED now that the flag has been removed. /* v4 */ - Serge Hallyn : - Use "mnt_userns" to refer to a vfsmount's userns everywhere to make terminology consistent. --- fs/namespace.c | 121 +++++++++++++++++++++++++++++++++++-- fs/proc_namespace.c | 3 + include/linux/fs.h | 2 +- include/linux/mount.h | 3 +- include/uapi/linux/mount.h | 3 + 5 files changed, 125 insertions(+), 7 deletions(-) diff --git a/fs/namespace.c b/fs/namespace.c index ac55f5c8b2de..12310f26e4ff 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,7 @@ struct mount_kattr { unsigned int propagation; unsigned int lookup_flags; bool recurse; + struct user_namespace *mnt_userns; }; /* /sys/fs */ @@ -3471,7 +3473,7 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name, (MOUNT_ATTR_RDONLY | MOUNT_ATTR_NOSUID | MOUNT_ATTR_NODEV | \ MOUNT_ATTR_NOEXEC | MOUNT_ATTR__ATIME | MOUNT_ATTR_NODIRATIME) -#define MOUNT_SETATTR_VALID_FLAGS FSMOUNT_VALID_FLAGS +#define MOUNT_SETATTR_VALID_FLAGS (FSMOUNT_VALID_FLAGS | MOUNT_ATTR_IDMAP) static unsigned int attr_flags_to_mnt_flags(u64 attr_flags) { @@ -3836,6 +3838,32 @@ static unsigned int recalc_flags(struct mount_kattr *kattr, struct mount *mnt) return flags; } +static int can_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt) +{ + struct vfsmount *m = &mnt->mnt; + + if (!kattr->mnt_userns) + return 0; + + /* + * Once a mount has been idmapped we don't allow it to change its + * mapping. It makes things simpler and callers can just create + * another bind-mount they can idmap if they want to. + */ + if (mnt_user_ns(m) != &init_user_ns) + return -EPERM; + + /* The underlying filesystem doesn't support idmapped mounts yet. */ + if (!(m->mnt_sb->s_type->fs_flags & FS_ALLOW_IDMAP)) + return -EINVAL; + + /* We're not controlling the superblock. */ + if (!ns_capable(m->mnt_sb->s_user_ns, CAP_SYS_ADMIN)) + return -EPERM; + + return 0; +} + static struct mount *mount_setattr_prepare(struct mount_kattr *kattr, struct mount *mnt, int *err) { @@ -3860,6 +3888,10 @@ static struct mount *mount_setattr_prepare(struct mount_kattr *kattr, goto out; } + *err = can_idmap_mount(kattr, m); + if (*err) + goto out; + last = m; if ((kattr->attr_set & MNT_READONLY) && @@ -3874,6 +3906,18 @@ static struct mount *mount_setattr_prepare(struct mount_kattr *kattr, return last; } +static void do_idmap_mount(const struct mount_kattr *kattr, struct mount *mnt) +{ + struct user_namespace *mnt_userns; + + if (!kattr->mnt_userns) + return; + + mnt_userns = get_user_ns(kattr->mnt_userns); + /* Pairs with smp_load_acquire() in mnt_user_ns(). */ + smp_store_release(&mnt->mnt.mnt_userns, mnt_userns); +} + static void mount_setattr_commit(struct mount_kattr *kattr, struct mount *mnt, struct mount *last, int err) @@ -3884,6 +3928,7 @@ static void mount_setattr_commit(struct mount_kattr *kattr, if (!err) { unsigned int flags; + do_idmap_mount(kattr, m); flags = recalc_flags(kattr, m); WRITE_ONCE(m->mnt.mnt_flags, flags); } @@ -3956,7 +4001,65 @@ static int do_mount_setattr(struct path *path, struct mount_kattr *kattr) return err; } -static int build_mount_kattr(const struct mount_attr *attr, +static int build_mount_idmapped(const struct mount_attr *attr, size_t usize, + struct mount_kattr *kattr, unsigned int flags) +{ + int err = 0; + struct ns_common *ns; + struct user_namespace *mnt_userns; + struct file *file; + + if (!((attr->attr_set | attr->attr_clr) & MOUNT_ATTR_IDMAP)) + return 0; + + /* + * We currently do not support clearing an idmapped mount. If this ever + * is a use-case we can revisit this but for now let's keep it simple + * and not allow it. + */ + if (attr->attr_clr & MOUNT_ATTR_IDMAP) + return -EINVAL; + + if (usize < MOUNT_ATTR_SIZE_VER1) + return -EINVAL; + + if (attr->userns_fd > INT_MAX) + return -EINVAL; + + file = fget(attr->userns_fd); + if (!file) + return -EBADF; + + if (!proc_ns_file(file)) { + err = -EINVAL; + goto out_fput; + } + + ns = get_proc_ns(file_inode(file)); + if (ns->ops->type != CLONE_NEWUSER) { + err = -EINVAL; + goto out_fput; + } + + /* + * The init_user_ns is used to indicate that a vfsmount is not idmapped. + * This is simpler than just having to treat NULL as unmapped. Users + * wanting to idmap a mount to init_user_ns can just use a namespace + * with an identity mapping. + */ + mnt_userns = container_of(ns, struct user_namespace, ns); + if (mnt_userns == &init_user_ns) { + err = -EPERM; + goto out_fput; + } + kattr->mnt_userns = get_user_ns(mnt_userns); + +out_fput: + fput(file); + return err; +} + +static int build_mount_kattr(const struct mount_attr *attr, size_t usize, struct mount_kattr *kattr, unsigned int flags) { unsigned int lookup_flags = LOOKUP_AUTOMOUNT | LOOKUP_FOLLOW; @@ -4034,7 +4137,13 @@ static int build_mount_kattr(const struct mount_attr *attr, return -EINVAL; } - return 0; + return build_mount_idmapped(attr, usize, kattr, flags); +} + +static void finish_mount_kattr(struct mount_kattr *kattr) +{ + put_user_ns(kattr->mnt_userns); + kattr->mnt_userns = NULL; } SYSCALL_DEFINE5(mount_setattr, int, dfd, const char __user *, path, @@ -4046,7 +4155,8 @@ SYSCALL_DEFINE5(mount_setattr, int, dfd, const char __user *, path, struct mount_attr attr; struct mount_kattr kattr; - BUILD_BUG_ON(sizeof(struct mount_attr) != MOUNT_ATTR_SIZE_VER0); + BUILD_BUG_ON(sizeof(struct mount_attr) < MOUNT_ATTR_SIZE_VER0); + BUILD_BUG_ON(sizeof(struct mount_attr) != MOUNT_ATTR_SIZE_VER1); if (flags & ~(AT_EMPTY_PATH | AT_RECURSIVE | @@ -4072,7 +4182,7 @@ SYSCALL_DEFINE5(mount_setattr, int, dfd, const char __user *, path, attr.propagation == 0) return 0; - err = build_mount_kattr(&attr, &kattr, flags); + err = build_mount_kattr(&attr, usize, &kattr, flags); if (err) return err; @@ -4081,6 +4191,7 @@ SYSCALL_DEFINE5(mount_setattr, int, dfd, const char __user *, path, return err; err = do_mount_setattr(&target, &kattr); + finish_mount_kattr(&kattr); path_put(&target); return err; } diff --git a/fs/proc_namespace.c b/fs/proc_namespace.c index e59d4bb3a89e..71ab9497feb3 100644 --- a/fs/proc_namespace.c +++ b/fs/proc_namespace.c @@ -79,6 +79,9 @@ static void show_mnt_opts(struct seq_file *m, struct vfsmount *mnt) if (mnt->mnt_flags & fs_infop->flag) seq_puts(m, fs_infop->str); } + + if (mnt_user_ns(mnt) != &init_user_ns) + seq_puts(m, ",idmapped"); } static inline void mangle(struct seq_file *m, const char *s) diff --git a/include/linux/fs.h b/include/linux/fs.h index 91666187afaa..341e7aa56088 100644 --- a/include/linux/fs.h +++ b/include/linux/fs.h @@ -2298,7 +2298,7 @@ struct file_system_type { #define FS_HAS_SUBTYPE 4 #define FS_USERNS_MOUNT 8 /* Can be mounted by userns root */ #define FS_DISALLOW_NOTIFY_PERM 16 /* Disable fanotify permission events */ -#define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */ +#define FS_ALLOW_IDMAP 32 /* FS has been updated to handle vfs idmappings. */ #define FS_THP_SUPPORT 8192 /* Remove once all fs converted */ #define FS_RENAME_DOES_D_MOVE 32768 /* FS will handle d_move() during rename() internally. */ int (*init_fs_context)(struct fs_context *); diff --git a/include/linux/mount.h b/include/linux/mount.h index 52de25e08319..161f4419db6c 100644 --- a/include/linux/mount.h +++ b/include/linux/mount.h @@ -77,7 +77,8 @@ struct vfsmount { static inline struct user_namespace *mnt_user_ns(const struct vfsmount *mnt) { - return mnt->mnt_userns; + /* Pairs with smp_store_release() in do_idmap_mount(). */ + return smp_load_acquire(&mnt->mnt_userns); } struct file; /* forward dec */ diff --git a/include/uapi/linux/mount.h b/include/uapi/linux/mount.h index 2255624e91c8..7f1a5ecba20a 100644 --- a/include/uapi/linux/mount.h +++ b/include/uapi/linux/mount.h @@ -119,6 +119,7 @@ enum fsconfig_command { #define MOUNT_ATTR_NOATIME 0x00000010 /* - Do not update access times. */ #define MOUNT_ATTR_STRICTATIME 0x00000020 /* - Always perform atime updates */ #define MOUNT_ATTR_NODIRATIME 0x00000080 /* Do not update directory access times */ +#define MOUNT_ATTR_IDMAP 0x00100000 /* Idmap this mount to @userns in mount_attr. */ /* * mount_setattr() @@ -127,9 +128,11 @@ struct mount_attr { __u64 attr_set; __u64 attr_clr; __u64 propagation; + __u64 userns_fd; }; /* List of all mount_attr versions. */ #define MOUNT_ATTR_SIZE_VER0 24 /* sizeof first published struct */ +#define MOUNT_ATTR_SIZE_VER1 32 /* sizeof second published struct */ #endif /* _UAPI_LINUX_MOUNT_H */ From patchwork Thu Dec 3 23:57:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Christian Brauner X-Patchwork-Id: 11950217 X-Patchwork-Delegate: paul@paul-moore.com Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.7 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS,URIBL_BLOCKED,USER_AGENT_GIT autolearn=unavailable autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id B3F2DC4361B for ; Fri, 4 Dec 2020 00:10:38 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 73CCC222B6 for ; Fri, 4 Dec 2020 00:10:38 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S2388721AbgLDAKh (ORCPT ); Thu, 3 Dec 2020 19:10:37 -0500 Received: from youngberry.canonical.com ([91.189.89.112]:42817 "EHLO youngberry.canonical.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1726619AbgLDAKg (ORCPT ); Thu, 3 Dec 2020 19:10:36 -0500 Received: from ip5f5af0a0.dynamic.kabel-deutschland.de ([95.90.240.160] helo=wittgenstein.fritz.box) by youngberry.canonical.com with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.86_2) (envelope-from ) id 1kkyYL-0007ka-VQ; Fri, 04 Dec 2020 00:02:26 +0000 From: Christian Brauner To: Alexander Viro , Christoph Hellwig , linux-fsdevel@vger.kernel.org Cc: John Johansen , James Morris , Mimi Zohar , Dmitry Kasatkin , Stephen Smalley , Casey Schaufler , Arnd Bergmann , Andreas Dilger , OGAWA Hirofumi , Geoffrey Thomas , Mrunal Patel , Josh Triplett , Andy Lutomirski , Theodore Tso , Alban Crequy , Tycho Andersen , David Howells , James Bottomley , Seth Forshee , =?utf-8?q?St=C3=A9phane_Graber?= , Aleksa Sarai , Lennart Poettering , "Eric W. Biederman" , smbarber@chromium.org, Phil Estes , Serge Hallyn , Kees Cook , Todd Kjos , Paul Moore , Jonathan Corbet , containers@lists.linux-foundation.org, linux-security-module@vger.kernel.org, linux-api@vger.kernel.org, linux-ext4@vger.kernel.org, linux-integrity@vger.kernel.org, selinux@vger.kernel.org, Christian Brauner , Christoph Hellwig Subject: [PATCH v4 39/40] tests: extend mount_setattr tests Date: Fri, 4 Dec 2020 00:57:35 +0100 Message-Id: <20201203235736.3528991-40-christian.brauner@ubuntu.com> X-Mailer: git-send-email 2.29.2 In-Reply-To: <20201203235736.3528991-1-christian.brauner@ubuntu.com> References: <20201203235736.3528991-1-christian.brauner@ubuntu.com> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: selinux@vger.kernel.org Extend the mount_setattr test-suite to include tests for idmapped mounts. Note, the main test-suite ist part of xfstests and is pretty huge. These tests here just make sure that the syscalls bits work correctly. Cc: Christoph Hellwig Cc: David Howells Cc: Al Viro Cc: linux-fsdevel@vger.kernel.org Signed-off-by: Christian Brauner --- /* v2 */ patch introduced /* v3 */ - Christoph Hellwig , Darrick J. Wong : - Port main test-suite to xfstests. /* v4 */ unchanged --- .../mount_setattr/mount_setattr_test.c | 483 ++++++++++++++++++ 1 file changed, 483 insertions(+) diff --git a/tools/testing/selftests/mount_setattr/mount_setattr_test.c b/tools/testing/selftests/mount_setattr/mount_setattr_test.c index 32fdb192629c..17986a0ca1dc 100644 --- a/tools/testing/selftests/mount_setattr/mount_setattr_test.c +++ b/tools/testing/selftests/mount_setattr/mount_setattr_test.c @@ -108,15 +108,57 @@ struct mount_attr { __u64 attr_set; __u64 attr_clr; __u64 propagation; + __u64 userns_fd; }; #endif +#ifndef __NR_open_tree + #if defined __alpha__ + #define __NR_open_tree 538 + #elif defined _MIPS_SIM + #if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */ + #define __NR_open_tree 4428 + #endif + #if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */ + #define __NR_open_tree 6428 + #endif + #if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */ + #define __NR_open_tree 5428 + #endif + #elif defined __ia64__ + #define __NR_open_tree (428 + 1024) + #else + #define __NR_open_tree 428 + #endif +#endif + +#ifndef MOUNT_ATTR_IDMAP +#define MOUNT_ATTR_IDMAP 0x00100000 +#endif + static inline int sys_mount_setattr(int dfd, const char *path, unsigned int flags, struct mount_attr *attr, size_t size) { return syscall(__NR_mount_setattr, dfd, path, flags, attr, size); } +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE 1 +#endif + +#ifndef OPEN_TREE_CLOEXEC +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#endif + +#ifndef AT_RECURSIVE +#define AT_RECURSIVE 0x8000 /* Apply to the entire subtree */ +#endif + +static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags) +{ + return syscall(__NR_open_tree, dfd, filename, flags); +} + static ssize_t write_nointr(int fd, const void *buf, size_t count) { ssize_t ret; @@ -938,4 +980,445 @@ TEST_F(mount_setattr, wrong_mount_namespace) ASSERT_EQ(errno, EINVAL); } +FIXTURE(mount_setattr_idmapped) { +}; + +FIXTURE_SETUP(mount_setattr_idmapped) +{ + int img_fd = -EBADF; + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + + ASSERT_EQ(mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0), 0); + + (void)umount2("/mnt", MNT_DETACH); + (void)umount2("/tmp", MNT_DETACH); + + ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/tmp/B", 0777), 0); + ASSERT_EQ(mknodat(-EBADF, "/tmp/B/b", S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown("/tmp/B/b", 0, 0), 0); + + ASSERT_EQ(mount("testing", "/tmp/B", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/tmp/B/BB", 0777), 0); + ASSERT_EQ(mknodat(-EBADF, "/tmp/B/BB/b", S_IFREG | 0644, 0), 0); + ASSERT_EQ(chown("/tmp/B/BB/b", 0, 0), 0); + + ASSERT_EQ(mount("testing", "/tmp/B/BB", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mount("testing", "/mnt", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/mnt/A", 0777), 0); + + ASSERT_EQ(mount("testing", "/mnt/A", "tmpfs", MS_NOATIME | MS_NODEV, + "size=100000,mode=700"), 0); + + ASSERT_EQ(mkdir("/mnt/A/AA", 0777), 0); + + ASSERT_EQ(mount("/tmp", "/mnt/A/AA", NULL, MS_BIND | MS_REC, NULL), 0); + + ASSERT_EQ(mkdir("/mnt/B", 0777), 0); + + ASSERT_EQ(mount("testing", "/mnt/B", "ramfs", + MS_NOATIME | MS_NODEV | MS_NOSUID, 0), 0); + + ASSERT_EQ(mkdir("/mnt/B/BB", 0777), 0); + + ASSERT_EQ(mount("testing", "/tmp/B/BB", "devpts", + MS_RELATIME | MS_NOEXEC | MS_RDONLY, 0), 0); + + ASSERT_EQ(mkdir("/mnt/C", 0777), 0); + ASSERT_EQ(mkdir("/mnt/D", 0777), 0); + img_fd = openat(-EBADF, "/mnt/C/ext4.img", O_CREAT | O_WRONLY, 0600); + ASSERT_GE(img_fd, 0); + ASSERT_EQ(ftruncate(img_fd, 1024 * 2048), 0); + ASSERT_EQ(system("mkfs.ext4 -q /mnt/C/ext4.img"), 0); + ASSERT_EQ(system("mount -o loop -t ext4 /mnt/C/ext4.img /mnt/D/"), 0); + ASSERT_EQ(close(img_fd), 0); +} + +FIXTURE_TEARDOWN(mount_setattr_idmapped) +{ + (void)umount2("/mnt/A", MNT_DETACH); + (void)umount2("/tmp", MNT_DETACH); +} + +/** + * Validate that negative fd values are rejected. + */ +TEST_F(mount_setattr_idmapped, invalid_fd_negative) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = -EBADF, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) { + TH_LOG("failure: created idmapped mount with negative fd"); + } +} + +/** + * Validate that excessively large fd values are rejected. + */ +TEST_F(mount_setattr_idmapped, invalid_fd_large) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + .userns_fd = INT64_MAX, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) { + TH_LOG("failure: created idmapped mount with too large fd value"); + } +} + +/** + * Validate that closed fd values are rejected. + */ +TEST_F(mount_setattr_idmapped, invalid_fd_closed) +{ + int fd; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + fd = open("/dev/null", O_RDONLY | O_CLOEXEC); + ASSERT_GE(fd, 0); + ASSERT_GE(close(fd), 0); + + attr.userns_fd = fd; + ASSERT_NE(sys_mount_setattr(-1, "/", 0, &attr, sizeof(attr)), 0) { + TH_LOG("failure: created idmapped mount with closed fd"); + } +} + +/** + * Validate that the initial user namespace is rejected. + */ +TEST_F(mount_setattr_idmapped, invalid_fd_initial_userns) +{ + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE); + ASSERT_GE(open_tree_fd, 0); + + attr.userns_fd = open("/proc/1/ns/user", O_RDONLY | O_CLOEXEC); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); + ASSERT_EQ(errno, EPERM); + ASSERT_EQ(close(attr.userns_fd), 0); + ASSERT_EQ(close(open_tree_fd), 0); +} + +static int map_ids(pid_t pid, unsigned long nsid, unsigned long hostid, + unsigned long range) +{ + char map[100], procfile[256]; + + snprintf(procfile, sizeof(procfile), "/proc/%d/uid_map", pid); + snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range); + if (write_file(procfile, map, strlen(map))) + return -1; + + + snprintf(procfile, sizeof(procfile), "/proc/%d/gid_map", pid); + snprintf(map, sizeof(map), "%lu %lu %lu", nsid, hostid, range); + if (write_file(procfile, map, strlen(map))) + return -1; + + return 0; +} + +#define __STACK_SIZE (8 * 1024 * 1024) +static pid_t do_clone(int (*fn)(void *), void *arg, int flags) +{ + void *stack; + + stack = malloc(__STACK_SIZE); + if (!stack) + return -ENOMEM; + +#ifdef __ia64__ + return __clone2(fn, stack, __STACK_SIZE, flags | SIGCHLD, arg, NULL); +#else + return clone(fn, stack + __STACK_SIZE, flags | SIGCHLD, arg, NULL); +#endif +} + +static int get_userns_fd_cb(void *data) +{ + return kill(getpid(), SIGSTOP); +} + +static int wait_for_pid(pid_t pid) +{ + int status, ret; + +again: + ret = waitpid(pid, &status, 0); + if (ret == -1) { + if (errno == EINTR) + goto again; + + return -1; + } + + if (!WIFEXITED(status)) + return -1; + + return WEXITSTATUS(status); +} + +static int get_userns_fd(unsigned long nsid, unsigned long hostid, unsigned long range) +{ + int ret; + pid_t pid; + char path[256]; + + pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER); + if (pid < 0) + return -errno; + + ret = map_ids(pid, nsid, hostid, range); + if (ret < 0) + return ret; + + snprintf(path, sizeof(path), "/proc/%d/ns/user", pid); + ret = open(path, O_RDONLY | O_CLOEXEC); + kill(pid, SIGKILL); + wait_for_pid(pid); + return ret; +} + +/** + * Validate that an attached mount in our mount namespace can be idmapped. + * (The kernel enforces that the mount's mount namespace and the caller's mount + * namespace match.) + */ +TEST_F(mount_setattr_idmapped, attached_mount_inside_current_mount_namespace) +{ + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC); + ASSERT_GE(open_tree_fd, 0); + + attr.userns_fd = get_userns_fd(0, 10000, 10000); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_EQ(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); + ASSERT_EQ(close(attr.userns_fd), 0); + ASSERT_EQ(close(open_tree_fd), 0); +} + +/** + * Validate that idmapping a mount is rejected if the mount's mount namespace + * and our mount namespace don't match. + * (The kernel enforces that the mount's mount namespace and the caller's mount + * namespace match.) + */ +TEST_F(mount_setattr_idmapped, attached_mount_outside_current_mount_namespace) +{ + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC); + ASSERT_GE(open_tree_fd, 0); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + + attr.userns_fd = get_userns_fd(0, 10000, 10000); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, + sizeof(attr)), 0); + ASSERT_EQ(close(attr.userns_fd), 0); + ASSERT_EQ(close(open_tree_fd), 0); +} + +/** + * Validate that an attached mount in our mount namespace can be idmapped. + */ +TEST_F(mount_setattr_idmapped, detached_mount_inside_current_mount_namespace) +{ + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + ASSERT_GE(open_tree_fd, 0); + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_EQ(sys_mount_setattr(open_tree_fd, "", + AT_EMPTY_PATH, &attr, sizeof(attr)), 0); + ASSERT_EQ(close(attr.userns_fd), 0); + ASSERT_EQ(close(open_tree_fd), 0); +} + +/** + * Validate that a detached mount not in our mount namespace can be idmapped. + */ +TEST_F(mount_setattr_idmapped, detached_mount_outside_current_mount_namespace) +{ + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + ASSERT_GE(open_tree_fd, 0); + + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + + /* Changing mount properties on a detached mount. */ + attr.userns_fd = get_userns_fd(0, 10000, 10000); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_EQ(sys_mount_setattr(open_tree_fd, "", + AT_EMPTY_PATH, &attr, sizeof(attr)), 0); + ASSERT_EQ(close(attr.userns_fd), 0); + ASSERT_EQ(close(open_tree_fd), 0); +} + +/** + * Validate that currently changing the idmapping of an idmapped mount fails. + */ +TEST_F(mount_setattr_idmapped, change_idmapping) +{ + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + open_tree_fd = sys_open_tree(-EBADF, "/mnt/D", + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + ASSERT_GE(open_tree_fd, 0); + + attr.userns_fd = get_userns_fd(0, 10000, 10000); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_EQ(sys_mount_setattr(open_tree_fd, "", + AT_EMPTY_PATH, &attr, sizeof(attr)), 0); + ASSERT_EQ(close(attr.userns_fd), 0); + + /* Change idmapping on a detached mount that is already idmapped. */ + attr.userns_fd = get_userns_fd(0, 20000, 10000); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); + ASSERT_EQ(close(attr.userns_fd), 0); + ASSERT_EQ(close(open_tree_fd), 0); +} + +static bool expected_uid_gid(int dfd, const char *path, int flags, + uid_t expected_uid, gid_t expected_gid) +{ + int ret; + struct stat st; + + ret = fstatat(dfd, path, &st, flags); + if (ret < 0) + return false; + + return st.st_uid == expected_uid && st.st_gid == expected_gid; +} + +TEST_F(mount_setattr_idmapped, idmap_mount_tree_invalid) +{ + int open_tree_fd = -EBADF; + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + + if (!mount_setattr_supported()) + SKIP(return, "mount_setattr syscall not supported"); + + ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/b", 0, 0, 0), 0); + ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/BB/b", 0, 0, 0), 0); + + open_tree_fd = sys_open_tree(-EBADF, "/mnt/A", + AT_RECURSIVE | + AT_EMPTY_PATH | + AT_NO_AUTOMOUNT | + AT_SYMLINK_NOFOLLOW | + OPEN_TREE_CLOEXEC | + OPEN_TREE_CLONE); + ASSERT_GE(open_tree_fd, 0); + + attr.userns_fd = get_userns_fd(0, 10000, 10000); + ASSERT_GE(attr.userns_fd, 0); + ASSERT_NE(sys_mount_setattr(open_tree_fd, "", AT_EMPTY_PATH, &attr, sizeof(attr)), 0); + ASSERT_EQ(close(attr.userns_fd), 0); + ASSERT_EQ(close(open_tree_fd), 0); + + ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/b", 0, 0, 0), 0); + ASSERT_EQ(expected_uid_gid(-EBADF, "/tmp/B/BB/b", 0, 0, 0), 0); + ASSERT_EQ(expected_uid_gid(open_tree_fd, "B/b", 0, 0, 0), 0); + ASSERT_EQ(expected_uid_gid(open_tree_fd, "B/BB/b", 0, 0, 0), 0); +} + TEST_HARNESS_MAIN