* [PATCH 0/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid
@ 2026-06-15 12:52 Christian Brauner
2026-06-15 12:52 ` [PATCH 1/2] " Christian Brauner
2026-06-15 12:52 ` [PATCH 2/2] selftests/filesystems: test O_TMPFILE creation on idmapped mounts Christian Brauner
0 siblings, 2 replies; 11+ messages in thread
From: Christian Brauner @ 2026-06-15 12:52 UTC (permalink / raw)
To: linux-fsdevel; +Cc: Alexander Viro, Jan Kara, Christian Brauner (Amutable)
vfs_tmpfile() is the only object-creation path in the VFS that never
checked whether the caller's fsuid and fsgid map into the filesystem.
On an idmapped mount whose idmapping does not cover the caller's
fs{u,g}id, the ->tmpfile() instance initializes the new inode through
inode_init_owner(), where mapped_fsuid()/mapped_fsgid() return
INVALID_UID/INVALID_GID. The tmpfile ends up owned by (uid_t)-1, and
because it is created I_LINKABLE it can subsequently be spliced into the
namespace with linkat(2).
Every other creation path already refuses this: may_o_create() (O_CREAT)
and may_create_dentry() (mkdir, mknod, symlink, link) bail out with
-EOVERFLOW via fsuidgid_has_mapping() precisely so that an object cannot
be created with an owner the filesystem cannot represent. O_TMPFILE is
no exception and must hold the same guarantee.
Patch 1 adds the missing fsuidgid_has_mapping() check to vfs_tmpfile().
It is a no-op on non-idmapped mounts -- there the caller's fs{u,g}id
always map in the superblock's user namespace -- and only takes effect
on an idmapped mount that does not map the caller. It applies to every
filesystem that sets FS_ALLOW_IDMAP and implements ->tmpfile() (tmpfs,
ext4, btrfs, xfs, f2fs, ...), and to overlayfs, whose upper-layer
tmpfile creation funnels through vfs_tmpfile() via
backing_tmpfile_open().
Patch 2 adds a selftest that idmaps a detached tmpfs mount and checks
both directions: an unmapped caller is refused with -EOVERFLOW, while a
mapped caller can create an O_TMPFILE, link it into the namespace, and
have its ownership round-trip through the mount idmap.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
Christian Brauner (2):
fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid
selftests/filesystems: test O_TMPFILE creation on idmapped mounts
fs/namei.c | 4 +
tools/testing/selftests/filesystems/.gitignore | 1 +
tools/testing/selftests/filesystems/Makefile | 4 +
.../selftests/filesystems/idmapped_tmpfile.c | 168 +++++++++++++++++++++
4 files changed, 177 insertions(+)
---
base-commit: 254f49634ee16a731174d2ae34bc50bd5f45e731
change-id: 20260615-work-idmapped-tmpfile-6b522f9db9a2
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-06-15 12:52 [PATCH 0/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid Christian Brauner @ 2026-06-15 12:52 ` Christian Brauner 2026-06-15 13:29 ` Jan Kara 2026-07-04 11:31 ` Christoph Hellwig 2026-06-15 12:52 ` [PATCH 2/2] selftests/filesystems: test O_TMPFILE creation on idmapped mounts Christian Brauner 1 sibling, 2 replies; 11+ messages in thread From: Christian Brauner @ 2026-06-15 12:52 UTC (permalink / raw) To: linux-fsdevel; +Cc: Alexander Viro, Jan Kara, Christian Brauner (Amutable) vfs_tmpfile() never checked that the caller's fsuid and fsgid map into the filesystem. On an idmapped mount whose idmapping does not cover the caller's fs{u,g}id, the ->tmpfile() instance initializes the new inode through inode_init_owner(), where mapped_fsuid()/mapped_fsgid() return INVALID_UID/INVALID_GID, and the tmpfile ends up owned by (uid_t)-1. Every other creation path already refuses this: may_o_create() (O_CREAT) and may_create_dentry() (mkdir, mknod, symlink, link) bail out with -EOVERFLOW via fsuidgid_has_mapping() precisely so that an object cannot be created with an owner the filesystem cannot represent. An O_TMPFILE is no exception: it is created I_LINKABLE and linkat(2) can splice it into the namespace afterwards, so the same guarantee must hold. Add the missing fsuidgid_has_mapping() check to vfs_tmpfile(). On a non-idmapped mount the caller's fs{u,g}id always map in the superblock's user namespace, so this is a no-op there and only takes effect on an idmapped mount that does not map the caller. It applies to every filesystem that sets FS_ALLOW_IDMAP and implements ->tmpfile() (tmpfs, ext4, btrfs, xfs, f2fs, ...), and to overlayfs, whose upper-layer tmpfile creation funnels through vfs_tmpfile() via backing_tmpfile_open(). Fixes: 8e5389132ab4 ("fs: introduce fsuidgid_has_mapping() helper") Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> --- fs/namei.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/fs/namei.c b/fs/namei.c index c7fac83c9a85..c11fdd196b41 100644 --- a/fs/namei.c +++ b/fs/namei.c @@ -4735,6 +4735,10 @@ int vfs_tmpfile(struct mnt_idmap *idmap, int error; int open_flag = file->f_flags; + /* A tmpfile is I_LINKABLE, so guard its owner like may_o_create(). */ + if (!fsuidgid_has_mapping(dir->i_sb, idmap)) + return -EOVERFLOW; + /* we want directory to be writable */ error = inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC); if (error) -- 2.47.3 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-06-15 12:52 ` [PATCH 1/2] " Christian Brauner @ 2026-06-15 13:29 ` Jan Kara 2026-07-04 11:31 ` Christoph Hellwig 1 sibling, 0 replies; 11+ messages in thread From: Jan Kara @ 2026-06-15 13:29 UTC (permalink / raw) To: Christian Brauner; +Cc: linux-fsdevel, Alexander Viro, Jan Kara On Mon 15-06-26 14:52:18, Christian Brauner wrote: > vfs_tmpfile() never checked that the caller's fsuid and fsgid map into > the filesystem. On an idmapped mount whose idmapping does not cover the > caller's fs{u,g}id, the ->tmpfile() instance initializes the new inode > through inode_init_owner(), where mapped_fsuid()/mapped_fsgid() return > INVALID_UID/INVALID_GID, and the tmpfile ends up owned by (uid_t)-1. > > Every other creation path already refuses this: may_o_create() (O_CREAT) > and may_create_dentry() (mkdir, mknod, symlink, link) bail out with > -EOVERFLOW via fsuidgid_has_mapping() precisely so that an object cannot > be created with an owner the filesystem cannot represent. An O_TMPFILE > is no exception: it is created I_LINKABLE and linkat(2) can splice it > into the namespace afterwards, so the same guarantee must hold. > > Add the missing fsuidgid_has_mapping() check to vfs_tmpfile(). On a > non-idmapped mount the caller's fs{u,g}id always map in the superblock's > user namespace, so this is a no-op there and only takes effect on an > idmapped mount that does not map the caller. It applies to every > filesystem that sets FS_ALLOW_IDMAP and implements ->tmpfile() (tmpfs, > ext4, btrfs, xfs, f2fs, ...), and to overlayfs, whose upper-layer > tmpfile creation funnels through vfs_tmpfile() via backing_tmpfile_open(). > > Fixes: 8e5389132ab4 ("fs: introduce fsuidgid_has_mapping() helper") > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Good catch! Feel free to add: Reviewed-by: Jan Kara <jack@suse.cz> Honza > --- > fs/namei.c | 4 ++++ > 1 file changed, 4 insertions(+) > > diff --git a/fs/namei.c b/fs/namei.c > index c7fac83c9a85..c11fdd196b41 100644 > --- a/fs/namei.c > +++ b/fs/namei.c > @@ -4735,6 +4735,10 @@ int vfs_tmpfile(struct mnt_idmap *idmap, > int error; > int open_flag = file->f_flags; > > + /* A tmpfile is I_LINKABLE, so guard its owner like may_o_create(). */ > + if (!fsuidgid_has_mapping(dir->i_sb, idmap)) > + return -EOVERFLOW; > + > /* we want directory to be writable */ > error = inode_permission(idmap, dir, MAY_WRITE | MAY_EXEC); > if (error) > > -- > 2.47.3 > -- Jan Kara <jack@suse.com> SUSE Labs, CR ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-06-15 12:52 ` [PATCH 1/2] " Christian Brauner 2026-06-15 13:29 ` Jan Kara @ 2026-07-04 11:31 ` Christoph Hellwig 2026-07-07 2:18 ` Darrick J. Wong 1 sibling, 1 reply; 11+ messages in thread From: Christoph Hellwig @ 2026-07-04 11:31 UTC (permalink / raw) To: Christian Brauner; +Cc: linux-fsdevel, Alexander Viro, Jan Kara This commit causes failures in xfstests generic/633 generic/696 and generic/697 with messages like: utils.c: 948: openat_tmpfile_supported - Value too large for defined data type - failure: create ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-07-04 11:31 ` Christoph Hellwig @ 2026-07-07 2:18 ` Darrick J. Wong 2026-07-07 7:20 ` Christian Brauner 0 siblings, 1 reply; 11+ messages in thread From: Darrick J. Wong @ 2026-07-07 2:18 UTC (permalink / raw) To: Christoph Hellwig Cc: Christian Brauner, linux-fsdevel, Alexander Viro, Jan Kara On Sat, Jul 04, 2026 at 04:31:00AM -0700, Christoph Hellwig wrote: > This commit causes failures in xfstests generic/633 generic/696 and > generic/697 with messages like: > > utils.c: 948: openat_tmpfile_supported - Value too large for defined data type - failure: create Seconded, I also see these test regressions now. --D ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-07-07 2:18 ` Darrick J. Wong @ 2026-07-07 7:20 ` Christian Brauner 2026-07-07 11:20 ` Christian Brauner 0 siblings, 1 reply; 11+ messages in thread From: Christian Brauner @ 2026-07-07 7:20 UTC (permalink / raw) To: Darrick J. Wong Cc: Christoph Hellwig, linux-fsdevel, Alexander Viro, Jan Kara On Mon, Jul 06, 2026 at 07:18:38PM -0700, Darrick J. Wong wrote: > On Sat, Jul 04, 2026 at 04:31:00AM -0700, Christoph Hellwig wrote: > > This commit causes failures in xfstests generic/633 generic/696 and > > generic/697 with messages like: > > > > utils.c: 948: openat_tmpfile_supported - Value too large for defined data type - failure: create > > Seconded, I also see these test regressions now. Yes, that's expected. It needs a fix in the idmapped mount feature test portion. I thought I had sent a fix for that though. ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-07-07 7:20 ` Christian Brauner @ 2026-07-07 11:20 ` Christian Brauner 2026-07-07 16:47 ` Darrick J. Wong 2026-07-10 5:31 ` Christoph Hellwig 0 siblings, 2 replies; 11+ messages in thread From: Christian Brauner @ 2026-07-07 11:20 UTC (permalink / raw) To: Darrick J. Wong Cc: Christoph Hellwig, linux-fsdevel, Alexander Viro, Jan Kara On Tue, Jul 07, 2026 at 09:20:48AM +0200, Christian Brauner wrote: > On Mon, Jul 06, 2026 at 07:18:38PM -0700, Darrick J. Wong wrote: > > On Sat, Jul 04, 2026 at 04:31:00AM -0700, Christoph Hellwig wrote: > > > This commit causes failures in xfstests generic/633 generic/696 and > > > generic/697 with messages like: > > > > > > utils.c: 948: openat_tmpfile_supported - Value too large for defined data type - failure: create > > > > Seconded, I also see these test regressions now. > > Yes, that's expected. It needs a fix in the idmapped mount feature test > portion. I thought I had sent a fix for that though. And so I did: https://lore.kernel.org/20260615-overlay-idmapped-vfstest-v1-1-c6b3b2228092@kernel.org ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-07-07 11:20 ` Christian Brauner @ 2026-07-07 16:47 ` Darrick J. Wong 2026-07-10 5:31 ` Christoph Hellwig 1 sibling, 0 replies; 11+ messages in thread From: Darrick J. Wong @ 2026-07-07 16:47 UTC (permalink / raw) To: Christian Brauner Cc: Christoph Hellwig, linux-fsdevel, Alexander Viro, Jan Kara On Tue, Jul 07, 2026 at 01:20:51PM +0200, Christian Brauner wrote: > On Tue, Jul 07, 2026 at 09:20:48AM +0200, Christian Brauner wrote: > > On Mon, Jul 06, 2026 at 07:18:38PM -0700, Darrick J. Wong wrote: > > > On Sat, Jul 04, 2026 at 04:31:00AM -0700, Christoph Hellwig wrote: > > > > This commit causes failures in xfstests generic/633 generic/696 and > > > > generic/697 with messages like: > > > > > > > > utils.c: 948: openat_tmpfile_supported - Value too large for defined data type - failure: create > > > > > > Seconded, I also see these test regressions now. > > > > Yes, that's expected. It needs a fix in the idmapped mount feature test > > portion. I thought I had sent a fix for that though. > > And so I did: > > https://lore.kernel.org/20260615-overlay-idmapped-vfstest-v1-1-c6b3b2228092@kernel.org Ah, thank you! --D ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 1/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid 2026-07-07 11:20 ` Christian Brauner 2026-07-07 16:47 ` Darrick J. Wong @ 2026-07-10 5:31 ` Christoph Hellwig 1 sibling, 0 replies; 11+ messages in thread From: Christoph Hellwig @ 2026-07-10 5:31 UTC (permalink / raw) To: Christian Brauner Cc: Darrick J. Wong, Christoph Hellwig, linux-fsdevel, Alexander Viro, Jan Kara On Tue, Jul 07, 2026 at 01:20:51PM +0200, Christian Brauner wrote: > On Tue, Jul 07, 2026 at 09:20:48AM +0200, Christian Brauner wrote: > > On Mon, Jul 06, 2026 at 07:18:38PM -0700, Darrick J. Wong wrote: > > > On Sat, Jul 04, 2026 at 04:31:00AM -0700, Christoph Hellwig wrote: > > > > This commit causes failures in xfstests generic/633 generic/696 and > > > > generic/697 with messages like: > > > > > > > > utils.c: 948: openat_tmpfile_supported - Value too large for defined data type - failure: create > > > > > > Seconded, I also see these test regressions now. > > > > Yes, that's expected. It needs a fix in the idmapped mount feature test > > portion. I thought I had sent a fix for that though. > > And so I did: > > https://lore.kernel.org/20260615-overlay-idmapped-vfstest-v1-1-c6b3b2228092@kernel.org It would be good to mention the test suite breakage in the commits insted of leaving people wondering, e.g.: This breaks xfstests foo/bar because it makes wrong assumptions. A fix (will be / has been) posted to the mailing list. ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 2/2] selftests/filesystems: test O_TMPFILE creation on idmapped mounts 2026-06-15 12:52 [PATCH 0/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid Christian Brauner 2026-06-15 12:52 ` [PATCH 1/2] " Christian Brauner @ 2026-06-15 12:52 ` Christian Brauner 2026-06-15 13:33 ` Jan Kara 1 sibling, 1 reply; 11+ messages in thread From: Christian Brauner @ 2026-06-15 12:52 UTC (permalink / raw) To: linux-fsdevel; +Cc: Alexander Viro, Jan Kara, Christian Brauner (Amutable) Add a regression test for the fsuidgid_has_mapping() check in vfs_tmpfile(). It idmaps a detached tmpfs mount so that the caller-visible id range [0, 10000) maps onto the on-disk range [10000, 20000) and checks that: - a caller whose fsuid/fsgid fall outside that range cannot create an O_TMPFILE through the mount and gets -EOVERFLOW instead of an inode owned by (uid_t)-1; - a mapped caller can create an O_TMPFILE, link it into the namespace, and the ownership round-trips through the mount idmap: it is reported as 0 through the mount and stored as 10000 on the underlying tmpfs. The test runs entirely as root and uses setfsuid()/setfsgid() to become the unmapped caller, so it needs no helper user. The layer directory is world-writable so that an unmapped caller still clears the directory permission check and reaches the fsuidgid_has_mapping() test. Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> --- tools/testing/selftests/filesystems/.gitignore | 1 + tools/testing/selftests/filesystems/Makefile | 4 + .../selftests/filesystems/idmapped_tmpfile.c | 168 +++++++++++++++++++++ 3 files changed, 173 insertions(+) diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore index 64ac0dfa46b7..a78f894157de 100644 --- a/tools/testing/selftests/filesystems/.gitignore +++ b/tools/testing/selftests/filesystems/.gitignore @@ -5,3 +5,4 @@ fclog file_stressor anon_inode_test kernfs_test +idmapped_tmpfile diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile index 85427d7f19b9..a7ec2ba2dd83 100644 --- a/tools/testing/selftests/filesystems/Makefile +++ b/tools/testing/selftests/filesystems/Makefile @@ -2,6 +2,10 @@ CFLAGS += $(KHDR_INCLUDES) TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog +TEST_GEN_PROGS += idmapped_tmpfile TEST_GEN_PROGS_EXTENDED := dnotify_test include ../lib.mk + +$(OUTPUT)/idmapped_tmpfile: LDLIBS += -lcap +$(OUTPUT)/idmapped_tmpfile: utils.c diff --git a/tools/testing/selftests/filesystems/idmapped_tmpfile.c b/tools/testing/selftests/filesystems/idmapped_tmpfile.c new file mode 100644 index 000000000000..bc411ab8281e --- /dev/null +++ b/tools/testing/selftests/filesystems/idmapped_tmpfile.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0 +#define _GNU_SOURCE + +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <sched.h> +#include <stdio.h> +#include <unistd.h> +#include <sys/fsuid.h> +#include <sys/stat.h> +#include <sys/syscall.h> + +#include <linux/mount.h> +#include <linux/types.h> + +#include "kselftest_harness.h" +#include "wrappers.h" +#include "utils.h" + +/* + * The test mount maps caller-visible ids [0, MAP_RANGE) onto the on-disk range + * [MAP_HOST, MAP_HOST + MAP_RANGE). An id outside [0, MAP_RANGE) therefore has + * no mapping in the mount and is not representable in the filesystem. + */ +#define MAP_HOST 10000 +#define MAP_RANGE 10000 +#define UNMAPPED 50000 + +#ifndef MOUNT_ATTR_IDMAP +#define MOUNT_ATTR_IDMAP 0x00100000 +#endif + +#ifndef __NR_mount_setattr +#define __NR_mount_setattr 442 +#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); +} + +/* + * Clone @path into a detached mount idmapped so that caller-visible ids + * [0, MAP_RANGE) map onto the on-disk ids [MAP_HOST, MAP_HOST + MAP_RANGE). + * Returns the mount fd, or -1 if idmapped mounts are not available. + */ +static int idmapped_clone(const char *path) +{ + struct mount_attr attr = { + .attr_set = MOUNT_ATTR_IDMAP, + }; + int fd_tree, userns_fd, ret; + + fd_tree = sys_open_tree(AT_FDCWD, path, + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); + if (fd_tree < 0) + return -1; + + userns_fd = get_userns_fd(MAP_HOST, 0, MAP_RANGE); + if (userns_fd < 0) { + close(fd_tree); + return -1; + } + + attr.userns_fd = userns_fd; + ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr, sizeof(attr)); + close(userns_fd); + if (ret) { + close(fd_tree); + return -1; + } + + return fd_tree; +} + +FIXTURE(idmapped_tmpfile) { + char dir[64]; /* non-idmapped path to the layer directory */ +}; + +FIXTURE_SETUP(idmapped_tmpfile) +{ + /* Private mount namespace so test mounts need no cleanup. */ + ASSERT_EQ(unshare(CLONE_NEWNS), 0); + ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); + ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); + + snprintf(self->dir, sizeof(self->dir), "/tmp/d"); + ASSERT_EQ(mkdir(self->dir, 0777), 0); + /* World-writable so an unmapped caller still passes permission(). */ + ASSERT_EQ(chmod(self->dir, 0777), 0); +} + +FIXTURE_TEARDOWN(idmapped_tmpfile) +{ +} + +/* + * A caller whose fsuid/fsgid have no mapping in the idmapped mount must not be + * able to create an O_TMPFILE. Without the check in vfs_tmpfile() the inode + * would be created owned by (uid_t)-1 and could then be linked into the + * namespace. + */ +TEST_F(idmapped_tmpfile, unmapped_caller_is_refused) +{ + int mfd, fd; + + mfd = idmapped_clone(self->dir); + if (mfd < 0) + SKIP(return, "idmapped mounts not supported"); + + /* Become a caller outside the mount's [0, MAP_RANGE) range. */ + setfsgid(UNMAPPED); + setfsuid(UNMAPPED); + ASSERT_EQ(setfsuid(-1), UNMAPPED); + + fd = openat(mfd, ".", O_TMPFILE | O_WRONLY, 0644); + ASSERT_LT(fd, 0); + EXPECT_EQ(errno, EOVERFLOW); + if (fd >= 0) + close(fd); + + EXPECT_EQ(close(mfd), 0); +} + +/* + * A mapped caller can create an O_TMPFILE and link it into the namespace; the + * ownership round-trips through the mount idmap. This is what makes refusing + * the unmapped case above necessary in the first place. + */ +TEST_F(idmapped_tmpfile, mapped_caller_creates_and_links) +{ + char path[PATH_MAX]; + struct stat st; + int mfd, fd; + + mfd = idmapped_clone(self->dir); + if (mfd < 0) + SKIP(return, "idmapped mounts not supported"); + + /* Caller is uid/gid 0, which maps to MAP_HOST through the mount. */ + fd = openat(mfd, ".", O_TMPFILE | O_RDWR, 0600); + ASSERT_GE(fd, 0); + + ASSERT_EQ(fstat(fd, &st), 0); + EXPECT_EQ(st.st_uid, 0); + EXPECT_EQ(st.st_gid, 0); + + /* The tmpfile is linkable: splice it into the directory. */ + ASSERT_EQ(linkat(fd, "", mfd, "linked", AT_EMPTY_PATH), 0); + EXPECT_EQ(close(fd), 0); + + ASSERT_EQ(fstatat(mfd, "linked", &st, 0), 0); + EXPECT_EQ(st.st_uid, 0); + EXPECT_EQ(st.st_gid, 0); + + /* On the underlying, non-idmapped tmpfs it is stored as MAP_HOST. */ + snprintf(path, sizeof(path), "%s/linked", self->dir); + ASSERT_EQ(stat(path, &st), 0); + EXPECT_EQ(st.st_uid, MAP_HOST); + EXPECT_EQ(st.st_gid, MAP_HOST); + + EXPECT_EQ(close(mfd), 0); +} + +TEST_HARNESS_MAIN -- 2.47.3 ^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 2/2] selftests/filesystems: test O_TMPFILE creation on idmapped mounts 2026-06-15 12:52 ` [PATCH 2/2] selftests/filesystems: test O_TMPFILE creation on idmapped mounts Christian Brauner @ 2026-06-15 13:33 ` Jan Kara 0 siblings, 0 replies; 11+ messages in thread From: Jan Kara @ 2026-06-15 13:33 UTC (permalink / raw) To: Christian Brauner; +Cc: linux-fsdevel, Alexander Viro, Jan Kara On Mon 15-06-26 14:52:19, Christian Brauner wrote: > Add a regression test for the fsuidgid_has_mapping() check in > vfs_tmpfile(). It idmaps a detached tmpfs mount so that the > caller-visible id range [0, 10000) maps onto the on-disk range > [10000, 20000) and checks that: > > - a caller whose fsuid/fsgid fall outside that range cannot create an > O_TMPFILE through the mount and gets -EOVERFLOW instead of an inode > owned by (uid_t)-1; > > - a mapped caller can create an O_TMPFILE, link it into the namespace, > and the ownership round-trips through the mount idmap: it is reported > as 0 through the mount and stored as 10000 on the underlying tmpfs. > > The test runs entirely as root and uses setfsuid()/setfsgid() to become > the unmapped caller, so it needs no helper user. The layer directory is > world-writable so that an unmapped caller still clears the directory > permission check and reaches the fsuidgid_has_mapping() test. > > Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org> Looks good. Feel free to add: Reviewed-by: Jan Kara <jack@suse.cz> Honza > --- > tools/testing/selftests/filesystems/.gitignore | 1 + > tools/testing/selftests/filesystems/Makefile | 4 + > .../selftests/filesystems/idmapped_tmpfile.c | 168 +++++++++++++++++++++ > 3 files changed, 173 insertions(+) > > diff --git a/tools/testing/selftests/filesystems/.gitignore b/tools/testing/selftests/filesystems/.gitignore > index 64ac0dfa46b7..a78f894157de 100644 > --- a/tools/testing/selftests/filesystems/.gitignore > +++ b/tools/testing/selftests/filesystems/.gitignore > @@ -5,3 +5,4 @@ fclog > file_stressor > anon_inode_test > kernfs_test > +idmapped_tmpfile > diff --git a/tools/testing/selftests/filesystems/Makefile b/tools/testing/selftests/filesystems/Makefile > index 85427d7f19b9..a7ec2ba2dd83 100644 > --- a/tools/testing/selftests/filesystems/Makefile > +++ b/tools/testing/selftests/filesystems/Makefile > @@ -2,6 +2,10 @@ > > CFLAGS += $(KHDR_INCLUDES) > TEST_GEN_PROGS := devpts_pts file_stressor anon_inode_test kernfs_test fclog > +TEST_GEN_PROGS += idmapped_tmpfile > TEST_GEN_PROGS_EXTENDED := dnotify_test > > include ../lib.mk > + > +$(OUTPUT)/idmapped_tmpfile: LDLIBS += -lcap > +$(OUTPUT)/idmapped_tmpfile: utils.c > diff --git a/tools/testing/selftests/filesystems/idmapped_tmpfile.c b/tools/testing/selftests/filesystems/idmapped_tmpfile.c > new file mode 100644 > index 000000000000..bc411ab8281e > --- /dev/null > +++ b/tools/testing/selftests/filesystems/idmapped_tmpfile.c > @@ -0,0 +1,168 @@ > +// SPDX-License-Identifier: GPL-2.0 > +#define _GNU_SOURCE > + > +#include <errno.h> > +#include <fcntl.h> > +#include <limits.h> > +#include <sched.h> > +#include <stdio.h> > +#include <unistd.h> > +#include <sys/fsuid.h> > +#include <sys/stat.h> > +#include <sys/syscall.h> > + > +#include <linux/mount.h> > +#include <linux/types.h> > + > +#include "kselftest_harness.h" > +#include "wrappers.h" > +#include "utils.h" > + > +/* > + * The test mount maps caller-visible ids [0, MAP_RANGE) onto the on-disk range > + * [MAP_HOST, MAP_HOST + MAP_RANGE). An id outside [0, MAP_RANGE) therefore has > + * no mapping in the mount and is not representable in the filesystem. > + */ > +#define MAP_HOST 10000 > +#define MAP_RANGE 10000 > +#define UNMAPPED 50000 > + > +#ifndef MOUNT_ATTR_IDMAP > +#define MOUNT_ATTR_IDMAP 0x00100000 > +#endif > + > +#ifndef __NR_mount_setattr > +#define __NR_mount_setattr 442 > +#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); > +} > + > +/* > + * Clone @path into a detached mount idmapped so that caller-visible ids > + * [0, MAP_RANGE) map onto the on-disk ids [MAP_HOST, MAP_HOST + MAP_RANGE). > + * Returns the mount fd, or -1 if idmapped mounts are not available. > + */ > +static int idmapped_clone(const char *path) > +{ > + struct mount_attr attr = { > + .attr_set = MOUNT_ATTR_IDMAP, > + }; > + int fd_tree, userns_fd, ret; > + > + fd_tree = sys_open_tree(AT_FDCWD, path, > + OPEN_TREE_CLONE | OPEN_TREE_CLOEXEC); > + if (fd_tree < 0) > + return -1; > + > + userns_fd = get_userns_fd(MAP_HOST, 0, MAP_RANGE); > + if (userns_fd < 0) { > + close(fd_tree); > + return -1; > + } > + > + attr.userns_fd = userns_fd; > + ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH, &attr, sizeof(attr)); > + close(userns_fd); > + if (ret) { > + close(fd_tree); > + return -1; > + } > + > + return fd_tree; > +} > + > +FIXTURE(idmapped_tmpfile) { > + char dir[64]; /* non-idmapped path to the layer directory */ > +}; > + > +FIXTURE_SETUP(idmapped_tmpfile) > +{ > + /* Private mount namespace so test mounts need no cleanup. */ > + ASSERT_EQ(unshare(CLONE_NEWNS), 0); > + ASSERT_EQ(sys_mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL), 0); > + ASSERT_EQ(sys_mount("tmpfs", "/tmp", "tmpfs", 0, NULL), 0); > + > + snprintf(self->dir, sizeof(self->dir), "/tmp/d"); > + ASSERT_EQ(mkdir(self->dir, 0777), 0); > + /* World-writable so an unmapped caller still passes permission(). */ > + ASSERT_EQ(chmod(self->dir, 0777), 0); > +} > + > +FIXTURE_TEARDOWN(idmapped_tmpfile) > +{ > +} > + > +/* > + * A caller whose fsuid/fsgid have no mapping in the idmapped mount must not be > + * able to create an O_TMPFILE. Without the check in vfs_tmpfile() the inode > + * would be created owned by (uid_t)-1 and could then be linked into the > + * namespace. > + */ > +TEST_F(idmapped_tmpfile, unmapped_caller_is_refused) > +{ > + int mfd, fd; > + > + mfd = idmapped_clone(self->dir); > + if (mfd < 0) > + SKIP(return, "idmapped mounts not supported"); > + > + /* Become a caller outside the mount's [0, MAP_RANGE) range. */ > + setfsgid(UNMAPPED); > + setfsuid(UNMAPPED); > + ASSERT_EQ(setfsuid(-1), UNMAPPED); > + > + fd = openat(mfd, ".", O_TMPFILE | O_WRONLY, 0644); > + ASSERT_LT(fd, 0); > + EXPECT_EQ(errno, EOVERFLOW); > + if (fd >= 0) > + close(fd); > + > + EXPECT_EQ(close(mfd), 0); > +} > + > +/* > + * A mapped caller can create an O_TMPFILE and link it into the namespace; the > + * ownership round-trips through the mount idmap. This is what makes refusing > + * the unmapped case above necessary in the first place. > + */ > +TEST_F(idmapped_tmpfile, mapped_caller_creates_and_links) > +{ > + char path[PATH_MAX]; > + struct stat st; > + int mfd, fd; > + > + mfd = idmapped_clone(self->dir); > + if (mfd < 0) > + SKIP(return, "idmapped mounts not supported"); > + > + /* Caller is uid/gid 0, which maps to MAP_HOST through the mount. */ > + fd = openat(mfd, ".", O_TMPFILE | O_RDWR, 0600); > + ASSERT_GE(fd, 0); > + > + ASSERT_EQ(fstat(fd, &st), 0); > + EXPECT_EQ(st.st_uid, 0); > + EXPECT_EQ(st.st_gid, 0); > + > + /* The tmpfile is linkable: splice it into the directory. */ > + ASSERT_EQ(linkat(fd, "", mfd, "linked", AT_EMPTY_PATH), 0); > + EXPECT_EQ(close(fd), 0); > + > + ASSERT_EQ(fstatat(mfd, "linked", &st, 0), 0); > + EXPECT_EQ(st.st_uid, 0); > + EXPECT_EQ(st.st_gid, 0); > + > + /* On the underlying, non-idmapped tmpfs it is stored as MAP_HOST. */ > + snprintf(path, sizeof(path), "%s/linked", self->dir); > + ASSERT_EQ(stat(path, &st), 0); > + EXPECT_EQ(st.st_uid, MAP_HOST); > + EXPECT_EQ(st.st_gid, MAP_HOST); > + > + EXPECT_EQ(close(mfd), 0); > +} > + > +TEST_HARNESS_MAIN > > -- > 2.47.3 > -- Jan Kara <jack@suse.com> SUSE Labs, CR ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-07-10 5:31 UTC | newest] Thread overview: 11+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-15 12:52 [PATCH 0/2] fs: refuse O_TMPFILE creation with an unmapped fsuid or fsgid Christian Brauner 2026-06-15 12:52 ` [PATCH 1/2] " Christian Brauner 2026-06-15 13:29 ` Jan Kara 2026-07-04 11:31 ` Christoph Hellwig 2026-07-07 2:18 ` Darrick J. Wong 2026-07-07 7:20 ` Christian Brauner 2026-07-07 11:20 ` Christian Brauner 2026-07-07 16:47 ` Darrick J. Wong 2026-07-10 5:31 ` Christoph Hellwig 2026-06-15 12:52 ` [PATCH 2/2] selftests/filesystems: test O_TMPFILE creation on idmapped mounts Christian Brauner 2026-06-15 13:33 ` Jan Kara
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox