diff mbox series

[v4,9/9] vfs: Add tmpfs tests for idmap mounts

Message ID 20230314114511.128207-10-rodrigo@sdfg.com.ar (mailing list archive)
State New, archived
Headers show
Series Tests for idmapped tmpfs | expand

Commit Message

Rodrigo Campos March 14, 2023, 11:45 a.m. UTC
This patch calls all tests in the suite s_idmapped_mounts, but with a
tmpfs directory mounted inside a userns. This directory is setup as the
mount point for the test that runs nested.

This excercises that tmpfs mounted inside a userns works as expected
regarding idmap mounts.

Signed-off-by: Rodrigo Campos <rodrigo@sdfg.com.ar>
---
 src/vfs/Makefile                |   4 +-
 src/vfs/tmpfs-idmapped-mounts.c | 305 ++++++++++++++++++++++++++++++++
 src/vfs/tmpfs-idmapped-mounts.h |  14 ++
 src/vfs/utils.h                 |   2 +
 src/vfs/vfstest.c               |  13 +-
 tests/tmpfs/001                 |  27 +++
 tests/tmpfs/001.out             |   2 +
 tests/tmpfs/Makefile            |  24 +++
 8 files changed, 388 insertions(+), 3 deletions(-)
 create mode 100644 src/vfs/tmpfs-idmapped-mounts.c
 create mode 100644 src/vfs/tmpfs-idmapped-mounts.h
 create mode 100755 tests/tmpfs/001
 create mode 100644 tests/tmpfs/001.out
 create mode 100644 tests/tmpfs/Makefile

Comments

Christian Brauner March 14, 2023, 1:13 p.m. UTC | #1
On Tue, Mar 14, 2023 at 12:45:11PM +0100, Rodrigo Campos wrote:
> This patch calls all tests in the suite s_idmapped_mounts, but with a
> tmpfs directory mounted inside a userns. This directory is setup as the
> mount point for the test that runs nested.
> 
> This excercises that tmpfs mounted inside a userns works as expected
> regarding idmap mounts.
> 
> Signed-off-by: Rodrigo Campos <rodrigo@sdfg.com.ar>
> ---

Ok, I think this is good enough now. Thanks for working on this. Very
happy that you followed through,
Acked-by: Christian Brauner <brauner@kernel.org>
Zorro Lang March 14, 2023, 2:47 p.m. UTC | #2
On Tue, Mar 14, 2023 at 12:45:11PM +0100, Rodrigo Campos wrote:
> This patch calls all tests in the suite s_idmapped_mounts, but with a
> tmpfs directory mounted inside a userns. This directory is setup as the
> mount point for the test that runs nested.
> 
> This excercises that tmpfs mounted inside a userns works as expected
> regarding idmap mounts.
> 
> Signed-off-by: Rodrigo Campos <rodrigo@sdfg.com.ar>
> ---

This verion looks good to me, so ...
Reviewed-by: Zorro Lang <zlang@redhat.com>

Just curious, did you use "--no-prefix" option or use some similar configuration
to generate this patchset. I have to use "-p0" to merge this patchset :)

Thanks,
Zorro

>  src/vfs/Makefile                |   4 +-
>  src/vfs/tmpfs-idmapped-mounts.c | 305 ++++++++++++++++++++++++++++++++
>  src/vfs/tmpfs-idmapped-mounts.h |  14 ++
>  src/vfs/utils.h                 |   2 +
>  src/vfs/vfstest.c               |  13 +-
>  tests/tmpfs/001                 |  27 +++
>  tests/tmpfs/001.out             |   2 +
>  tests/tmpfs/Makefile            |  24 +++
>  8 files changed, 388 insertions(+), 3 deletions(-)
>  create mode 100644 src/vfs/tmpfs-idmapped-mounts.c
>  create mode 100644 src/vfs/tmpfs-idmapped-mounts.h
>  create mode 100755 tests/tmpfs/001
>  create mode 100644 tests/tmpfs/001.out
>  create mode 100644 tests/tmpfs/Makefile
> 
> diff --git src/vfs/Makefile src/vfs/Makefile
> index 1b0b364b..4841da12 100644
> --- src/vfs/Makefile
> +++ src/vfs/Makefile
> @@ -4,10 +4,10 @@ TOPDIR = ../..
>  include $(TOPDIR)/include/builddefs
>  
>  TARGETS = vfstest mount-idmapped
> -CFILES_VFSTEST = vfstest.c btrfs-idmapped-mounts.c idmapped-mounts.c utils.c
> +CFILES_VFSTEST = vfstest.c btrfs-idmapped-mounts.c idmapped-mounts.c utils.c tmpfs-idmapped-mounts.c
>  CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
>  
> -HFILES = missing.h utils.h btrfs-idmapped-mounts.h idmapped-mounts.h
> +HFILES = missing.h utils.h btrfs-idmapped-mounts.h idmapped-mounts.h tmpfs-idmapped-mounts.h
>  LLDLIBS += -pthread
>  LDIRT = $(TARGETS)
>  
> diff --git src/vfs/tmpfs-idmapped-mounts.c src/vfs/tmpfs-idmapped-mounts.c
> new file mode 100644
> index 00000000..0899aed9
> --- /dev/null
> +++ src/vfs/tmpfs-idmapped-mounts.c
> @@ -0,0 +1,305 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
> +
> +#include "../global.h"
> +
> +#include <dirent.h>
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <getopt.h>
> +#include <grp.h>
> +#include <limits.h>
> +#include <linux/limits.h>
> +#include <linux/types.h>
> +#include <pthread.h>
> +#include <pwd.h>
> +#include <sched.h>
> +#include <stdbool.h>
> +#include <sys/fsuid.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <sys/xattr.h>
> +#include <unistd.h>
> +
> +#include "missing.h"
> +#include "utils.h"
> +#include "vfstest.h"
> +#include "idmapped-mounts.h"
> +
> +static int tmpfs_nested_mount_setup(const struct vfstest_info *info, int (*test)(const struct vfstest_info *info))
> +{
> +	char path[PATH_MAX];
> +	int fret = -1;
> +	struct vfstest_info nested_test_info = *info;
> +
> +	/* Create mapping for userns
> +	 * Make the mapping quite long, so all nested userns that are created by
> +	 * any test we call is contained here (otherwise userns creation fails).
> +	 */
> +	struct mount_attr attr = {
> +		.attr_set	= MOUNT_ATTR_IDMAP,
> +		.userns_fd	= -EBADF,
> +	};
> +	attr.userns_fd = get_userns_fd(0, 10000, 200000);
> +	if (attr.userns_fd < 0) {
> +		log_stderr("failure: get_userns_fd");
> +		goto out_close;
> +	}
> +
> +	if (!switch_userns(attr.userns_fd, 0, 0, false)) {
> +		log_stderr("failure: switch_userns");
> +		goto out_close;
> +	}
> +
> +	/* create separate mount namespace */
> +	if (unshare(CLONE_NEWNS)) {
> +		log_stderr("failure: create new mount namespace");
> +		goto out_close;
> +	}
> +
> +	/* We don't want this mount in the parent mount ns */
> +	if (sys_mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0)) {
> +		log_stderr("failure: mount");
> +		goto out_close;
> +	}
> +
> +	/* Create DIR0 to mount there */
> +	if (mkdirat(info->t_mnt_fd, DIR0, 0777)) {
> +		log_stderr("failure: mkdirat");
> +		goto out_close;
> +	}
> +	if (fchmodat(info->t_mnt_fd, DIR0, 0777, 0)) {
> +		log_stderr("failure: fchmodat");
> +		goto out_rm;
> +	}
> +
> +	snprintf(path, sizeof(path), "%s/%s", info->t_mountpoint, DIR0);
> +	if (sys_mount("tmpfs", path, "tmpfs", 0, NULL)) {
> +		log_stderr("failure: mount");
> +		goto out_rm;
> +	}
> +
> +	// Create a new info to use for the test we will call.
> +	nested_test_info = *info;
> +	nested_test_info.t_mountpoint = strdup(path);
> +	if (!nested_test_info.t_mountpoint) {
> +		log_stderr("failure: strdup");
> +		goto out;
> +	}
> +	nested_test_info.t_mnt_fd = openat(-EBADF, nested_test_info.t_mountpoint, O_CLOEXEC | O_DIRECTORY);
> +	if (nested_test_info.t_mnt_fd < 0) {
> +		log_stderr("failure: openat");
> +		goto out;
> +	}
> +
> +	test_setup(&nested_test_info);
> +
> +	// Run the test.
> +	if ((*test)(&nested_test_info)) {
> +		log_stderr("failure: calling test");
> +		goto out;
> +	}
> +
> +	test_cleanup(&nested_test_info);
> +
> +	fret = 0;
> +	log_debug("Ran test");
> +out:
> +	snprintf(path, sizeof(path), "%s/" DIR0, info->t_mountpoint);
> +	sys_umount2(path, MNT_DETACH);
> +out_rm:
> +	if (rm_r(info->t_mnt_fd, DIR0))
> +		log_stderr("failure: rm_r");
> +out_close:
> +	safe_close(attr.userns_fd);
> +	return fret;
> +}
> +
> +static int tmpfs_acls(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_acls);
> +}
> +static int tmpfs_create_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_create_in_userns);
> +}
> +static int tmpfs_device_node_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_device_node_in_userns);
> +}
> +static int tmpfs_fsids_mapped(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_fsids_mapped);
> +}
> +static int tmpfs_fsids_unmapped(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_fsids_unmapped);
> +}
> +static int tmpfs_expected_uid_gid_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_expected_uid_gid_idmapped_mounts);
> +}
> +static int tmpfs_fscaps_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_fscaps_idmapped_mounts);
> +}
> +static int tmpfs_fscaps_idmapped_mounts_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_fscaps_idmapped_mounts_in_userns);
> +}
> +static int tmpfs_fscaps_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_fscaps_idmapped_mounts_in_userns_separate_userns);
> +}
> +
> +static int tmpfs_hardlink_crossing_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_hardlink_crossing_idmapped_mounts);
> +}
> +static int tmpfs_hardlink_from_idmapped_mount(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_hardlink_from_idmapped_mount);
> +}
> +static int tmpfs_hardlink_from_idmapped_mount_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_hardlink_from_idmapped_mount_in_userns);
> +}
> +
> +#ifdef HAVE_LIBURING_H
> +static int tmpfs_io_uring_idmapped(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped);
> +}
> +static int tmpfs_io_uring_idmapped_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped_userns);
> +}
> +static int tmpfs_io_uring_idmapped_unmapped(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped_unmapped);
> +}
> +static int tmpfs_io_uring_idmapped_unmapped_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped_unmapped_userns);
> +}
> +#endif /* HAVE_LIBURING_H */
> +
> +static int tmpfs_protected_symlinks_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_protected_symlinks_idmapped_mounts);
> +}
> +static int tmpfs_protected_symlinks_idmapped_mounts_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_protected_symlinks_idmapped_mounts_in_userns);
> +}
> +static int tmpfs_rename_crossing_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_rename_crossing_idmapped_mounts);
> +}
> +static int tmpfs_rename_from_idmapped_mount(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_rename_from_idmapped_mount);
> +}
> +static int tmpfs_rename_from_idmapped_mount_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_rename_from_idmapped_mount_in_userns);
> +}
> +static int tmpfs_setattr_truncate_idmapped(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_setattr_truncate_idmapped);
> +}
> +static int tmpfs_setattr_truncate_idmapped_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_setattr_truncate_idmapped_in_userns);
> +}
> +static int tmpfs_setgid_create_idmapped(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_setgid_create_idmapped);
> +}
> +static int tmpfs_setgid_create_idmapped_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_setgid_create_idmapped_in_userns);
> +}
> +static int tmpfs_setid_binaries_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_setid_binaries_idmapped_mounts);
> +}
> +static int tmpfs_setid_binaries_idmapped_mounts_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_setid_binaries_idmapped_mounts_in_userns);
> +}
> +static int tmpfs_setid_binaries_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_setid_binaries_idmapped_mounts_in_userns_separate_userns);
> +}
> +static int tmpfs_sticky_bit_unlink_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_unlink_idmapped_mounts);
> +}
> +static int tmpfs_sticky_bit_unlink_idmapped_mounts_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_unlink_idmapped_mounts_in_userns);
> +}
> +static int tmpfs_sticky_bit_rename_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_rename_idmapped_mounts);
> +}
> +static int tmpfs_sticky_bit_rename_idmapped_mounts_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_rename_idmapped_mounts_in_userns);
> +}
> +static int tmpfs_symlink_idmapped_mounts(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_symlink_idmapped_mounts);
> +}
> +static int tmpfs_symlink_idmapped_mounts_in_userns(const struct vfstest_info *info)
> +{
> +	return tmpfs_nested_mount_setup(info, tcore_symlink_idmapped_mounts_in_userns);
> +}
> +
> +static const struct test_struct t_tmpfs[] = {
> +	{ tmpfs_acls,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in user namespace",							      },
> +	{ tmpfs_create_in_userns,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in user namespace",							      },
> +	{ tmpfs_device_node_in_userns,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs device node in user namespace",								      },
> +	{ tmpfs_expected_uid_gid_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs expected ownership on idmapped mounts",							},
> +	{ tmpfs_fscaps_idmapped_mounts,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs fscaps on idmapped mounts",									},
> +	{ tmpfs_fscaps_idmapped_mounts_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs fscaps on idmapped mounts in user namespace",							},
> +	{ tmpfs_fscaps_idmapped_mounts_in_userns_separate_userns,		T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs fscaps on idmapped mounts in user namespace with different id mappings",			},
> +	{ tmpfs_fsids_mapped,							T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs mapped fsids",										      },
> +	{ tmpfs_fsids_unmapped,							T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs unmapped fsids",										      },
> +	{ tmpfs_hardlink_crossing_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs cross idmapped mount hardlink",								},
> +	{ tmpfs_hardlink_from_idmapped_mount,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs hardlinks from idmapped mounts",								},
> +	{ tmpfs_hardlink_from_idmapped_mount_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs hardlinks from idmapped mounts in user namespace",						},
> +#ifdef HAVE_LIBURING_H
> +	{ tmpfs_io_uring_idmapped,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts",								      },
> +	{ tmpfs_io_uring_idmapped_userns,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts in user namespace",					      },
> +	{ tmpfs_io_uring_idmapped_unmapped,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts with unmapped ids",					      },
> +	{ tmpfs_io_uring_idmapped_unmapped_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts with unmapped ids in user namespace",			      },
> +#endif
> +	{ tmpfs_protected_symlinks_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs following protected symlinks on idmapped mounts",						},
> +	{ tmpfs_protected_symlinks_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs following protected symlinks on idmapped mounts in user namespace",				},
> +	{ tmpfs_rename_crossing_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs cross idmapped mount rename",									},
> +	{ tmpfs_rename_from_idmapped_mount,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs rename from idmapped mounts",									},
> +	{ tmpfs_rename_from_idmapped_mount_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs rename from idmapped mounts in user namespace",						},
> +	{ tmpfs_setattr_truncate_idmapped,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setattr truncate on idmapped mounts",								},
> +	{ tmpfs_setattr_truncate_idmapped_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setattr truncate on idmapped mounts in user namespace",					},
> +	{ tmpfs_setgid_create_idmapped,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in directories with setgid bit set on idmapped mounts",			},
> +	{ tmpfs_setgid_create_idmapped_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in directories with setgid bit set on idmapped mounts in user namespace",	},
> +	{ tmpfs_setid_binaries_idmapped_mounts,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setid binaries on idmapped mounts",								},
> +	{ tmpfs_setid_binaries_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setid binaries on idmapped mounts in user namespace",						},
> +	{ tmpfs_setid_binaries_idmapped_mounts_in_userns_separate_userns,	T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setid binaries on idmapped mounts in user namespace with different id mappings",		},
> +	{ tmpfs_sticky_bit_unlink_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit unlink operations on idmapped mounts",						},
> +	{ tmpfs_sticky_bit_unlink_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit unlink operations on idmapped mounts in user namespace",				},
> +	{ tmpfs_sticky_bit_rename_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit rename operations on idmapped mounts",						},
> +	{ tmpfs_sticky_bit_rename_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit rename operations on idmapped mounts in user namespace",				},
> +	{ tmpfs_symlink_idmapped_mounts,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs symlink from idmapped mounts",									},
> +	{ tmpfs_symlink_idmapped_mounts_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs symlink from idmapped mounts in user namespace",						},
> +};
> +
> +
> +const struct test_suite s_tmpfs_idmapped_mounts = {
> +	.tests = t_tmpfs,
> +	.nr_tests = ARRAY_SIZE(t_tmpfs),
> +};
> diff --git src/vfs/tmpfs-idmapped-mounts.h src/vfs/tmpfs-idmapped-mounts.h
> new file mode 100644
> index 00000000..ed24651f
> --- /dev/null
> +++ src/vfs/tmpfs-idmapped-mounts.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#ifndef __TMPFS_IDMAPPED_MOUNTS_H
> +#define __TMPFS_IDMAPPED_MOUNTS_H
> +
> +#ifndef _GNU_SOURCE
> +#define _GNU_SOURCE
> +#endif
> +
> +#include "utils.h"
> +
> +extern const struct test_suite s_tmpfs_idmapped_mounts;
> +
> +#endif /* __TMPFS_IDMAPPED_MOUNTS_H */
> diff --git src/vfs/utils.h src/vfs/utils.h
> index f1681737..872fd96f 100644
> --- src/vfs/utils.h
> +++ src/vfs/utils.h
> @@ -45,6 +45,8 @@
>  #define DIR2 "dir2"
>  #define DIR3 "dir3"
>  #define DIR1_RENAME "dir1_rename"
> +// This directory may be used by tests that call another test.
> +#define DIR0 "dir0"
>  #define HARDLINK1 "hardlink1"
>  #define SYMLINK1 "symlink1"
>  #define SYMLINK_USER1 "symlink_user1"
> diff --git src/vfs/vfstest.c src/vfs/vfstest.c
> index 325f04a1..f842117d 100644
> --- src/vfs/vfstest.c
> +++ src/vfs/vfstest.c
> @@ -23,6 +23,7 @@
>  #include <unistd.h>
>  
>  #include "btrfs-idmapped-mounts.h"
> +#include "tmpfs-idmapped-mounts.h"
>  #include "idmapped-mounts.h"
>  #include "missing.h"
>  #include "utils.h"
> @@ -2316,6 +2317,7 @@ static void usage(void)
>  	fprintf(stderr, "--test-fscaps-regression            Run fscap regression tests\n");
>  	fprintf(stderr, "--test-nested-userns                Run nested userns idmapped mount testsuite\n");
>  	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
> +	fprintf(stderr, "--test-tmpfs                        Run tmpfs specific idmapped mount testsuite\n");
>  	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
>  	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
>  	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
> @@ -2340,6 +2342,7 @@ static const struct option longopts[] = {
>  	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
>  	{"test-setgid-create-umask",		no_argument,		0,	'u'},
>  	{"test-setgid-create-acl",		no_argument,		0,	'l'},
> +	{"test-tmpfs",				no_argument,		0,	't'},
>  	{NULL,					0,			0,	  0},
>  };
>  
> @@ -2480,7 +2483,7 @@ int main(int argc, char *argv[])
>  	bool idmapped_mounts_supported = false, test_btrfs = false,
>  	     test_core = false, test_fscaps_regression = false,
>  	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
> -	     test_setxattr_fix_705191b03d50 = false,
> +	     test_setxattr_fix_705191b03d50 = false, test_tmpfs = false,
>  	     test_setgid_create_umask = false, test_setgid_create_acl = false;
>  
>  	init_vfstest_info(&info);
> @@ -2529,6 +2532,9 @@ int main(int argc, char *argv[])
>  		case 'l':
>  			test_setgid_create_acl = true;
>  			break;
> +		case 't':
> +			test_tmpfs = true;
> +			break;
>  		case 'h':
>  			/* fallthrough */
>  		default:
> @@ -2622,6 +2628,11 @@ int main(int argc, char *argv[])
>  			goto out;
>  	}
>  
> +	if (test_tmpfs) {
> +		if (!run_suite(&info, &s_tmpfs_idmapped_mounts))
> +			goto out;
> +	}
> +
>  	fret = EXIT_SUCCESS;
>  
>  out:
> diff --git tests/tmpfs/001 tests/tmpfs/001
> new file mode 100755
> index 00000000..37ef0b18
> --- /dev/null
> +++ tests/tmpfs/001
> @@ -0,0 +1,27 @@
> +#! /bin/bash
> +# SPDX-License-Identifier: GPL-2.0
> +# Copyright (c) 2023 Rodrigo Campos Catelin (Microsoft). All Rights Reserved.
> +#
> +# FS QA Test 001
> +#
> +# Test that idmapped mounts behave correctly with tmpfs filesystem.
> +#
> +. ./common/preamble
> +_begin_fstest auto quick idmapped
> +
> +# get standard environment, filters and checks
> +. ./common/filter
> +
> +# real QA test starts here
> +
> +_supported_fs tmpfs
> +_require_idmapped_mounts
> +_require_test
> +
> +echo "Silence is golden"
> +
> +$here/src/vfs/vfstest --test-tmpfs --device "$TEST_DEV" \
> +	        --mount "$TEST_DIR" --fstype "$FSTYP"
> +
> +status=$?
> +exit
> diff --git tests/tmpfs/001.out tests/tmpfs/001.out
> new file mode 100644
> index 00000000..88678b8e
> --- /dev/null
> +++ tests/tmpfs/001.out
> @@ -0,0 +1,2 @@
> +QA output created by 001
> +Silence is golden
> diff --git tests/tmpfs/Makefile tests/tmpfs/Makefile
> new file mode 100644
> index 00000000..46544313
> --- /dev/null
> +++ tests/tmpfs/Makefile
> @@ -0,0 +1,24 @@
> +#
> +# Copyright (c) 2023 Rodrigo Campos Catelin (Microsoft). All Rights Reserved.
> +#
> +
> +TOPDIR = ../..
> +include $(TOPDIR)/include/builddefs
> +include $(TOPDIR)/include/buildgrouplist
> +
> +TMPFS_DIR = tmpfs
> +TARGET_DIR = $(PKG_LIB_DIR)/$(TESTS_DIR)/$(TMPFS_DIR)
> +DIRT = group.list
> +
> +default: $(DIRT)
> +
> +include $(BUILDRULES)
> +
> +install:
> +	$(INSTALL) -m 755 -d $(TARGET_DIR)
> +	$(INSTALL) -m 755 $(TESTS) $(TARGET_DIR)
> +	$(INSTALL) -m 644 group.list $(TARGET_DIR)
> +	$(INSTALL) -m 644 $(OUTFILES) $(TARGET_DIR)
> +
> +# Nothing.
> +install-dev install-lib:
> -- 
> 2.39.2
>
Rodrigo Campos March 14, 2023, 3:08 p.m. UTC | #3
On 3/14/23 15:47, Zorro Lang wrote:
> On Tue, Mar 14, 2023 at 12:45:11PM +0100, Rodrigo Campos wrote:
>> This patch calls all tests in the suite s_idmapped_mounts, but with a
>> tmpfs directory mounted inside a userns. This directory is setup as the
>> mount point for the test that runs nested.
>>
>> This excercises that tmpfs mounted inside a userns works as expected
>> regarding idmap mounts.
>>
>> Signed-off-by: Rodrigo Campos <rodrigo@sdfg.com.ar>
>> ---
> 
> This verion looks good to me, so ...
> Reviewed-by: Zorro Lang <zlang@redhat.com>

Thanks!

> Just curious, did you use "--no-prefix" option or use some similar configuration
> to generate this patchset. I have to use "-p0" to merge this patchset :)

Oh, yes, sorry, I used no prefix. I'll remove it from my config for this 
repo.



Thanks again,
Rodrigo
diff mbox series

Patch

diff --git src/vfs/Makefile src/vfs/Makefile
index 1b0b364b..4841da12 100644
--- src/vfs/Makefile
+++ src/vfs/Makefile
@@ -4,10 +4,10 @@  TOPDIR = ../..
 include $(TOPDIR)/include/builddefs
 
 TARGETS = vfstest mount-idmapped
-CFILES_VFSTEST = vfstest.c btrfs-idmapped-mounts.c idmapped-mounts.c utils.c
+CFILES_VFSTEST = vfstest.c btrfs-idmapped-mounts.c idmapped-mounts.c utils.c tmpfs-idmapped-mounts.c
 CFILES_MOUNT_IDMAPPED = mount-idmapped.c utils.c
 
-HFILES = missing.h utils.h btrfs-idmapped-mounts.h idmapped-mounts.h
+HFILES = missing.h utils.h btrfs-idmapped-mounts.h idmapped-mounts.h tmpfs-idmapped-mounts.h
 LLDLIBS += -pthread
 LDIRT = $(TARGETS)
 
diff --git src/vfs/tmpfs-idmapped-mounts.c src/vfs/tmpfs-idmapped-mounts.c
new file mode 100644
index 00000000..0899aed9
--- /dev/null
+++ src/vfs/tmpfs-idmapped-mounts.c
@@ -0,0 +1,305 @@ 
+// SPDX-License-Identifier: GPL-2.0
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "../global.h"
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <grp.h>
+#include <limits.h>
+#include <linux/limits.h>
+#include <linux/types.h>
+#include <pthread.h>
+#include <pwd.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <sys/fsuid.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/xattr.h>
+#include <unistd.h>
+
+#include "missing.h"
+#include "utils.h"
+#include "vfstest.h"
+#include "idmapped-mounts.h"
+
+static int tmpfs_nested_mount_setup(const struct vfstest_info *info, int (*test)(const struct vfstest_info *info))
+{
+	char path[PATH_MAX];
+	int fret = -1;
+	struct vfstest_info nested_test_info = *info;
+
+	/* Create mapping for userns
+	 * Make the mapping quite long, so all nested userns that are created by
+	 * any test we call is contained here (otherwise userns creation fails).
+	 */
+	struct mount_attr attr = {
+		.attr_set	= MOUNT_ATTR_IDMAP,
+		.userns_fd	= -EBADF,
+	};
+	attr.userns_fd = get_userns_fd(0, 10000, 200000);
+	if (attr.userns_fd < 0) {
+		log_stderr("failure: get_userns_fd");
+		goto out_close;
+	}
+
+	if (!switch_userns(attr.userns_fd, 0, 0, false)) {
+		log_stderr("failure: switch_userns");
+		goto out_close;
+	}
+
+	/* create separate mount namespace */
+	if (unshare(CLONE_NEWNS)) {
+		log_stderr("failure: create new mount namespace");
+		goto out_close;
+	}
+
+	/* We don't want this mount in the parent mount ns */
+	if (sys_mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, 0)) {
+		log_stderr("failure: mount");
+		goto out_close;
+	}
+
+	/* Create DIR0 to mount there */
+	if (mkdirat(info->t_mnt_fd, DIR0, 0777)) {
+		log_stderr("failure: mkdirat");
+		goto out_close;
+	}
+	if (fchmodat(info->t_mnt_fd, DIR0, 0777, 0)) {
+		log_stderr("failure: fchmodat");
+		goto out_rm;
+	}
+
+	snprintf(path, sizeof(path), "%s/%s", info->t_mountpoint, DIR0);
+	if (sys_mount("tmpfs", path, "tmpfs", 0, NULL)) {
+		log_stderr("failure: mount");
+		goto out_rm;
+	}
+
+	// Create a new info to use for the test we will call.
+	nested_test_info = *info;
+	nested_test_info.t_mountpoint = strdup(path);
+	if (!nested_test_info.t_mountpoint) {
+		log_stderr("failure: strdup");
+		goto out;
+	}
+	nested_test_info.t_mnt_fd = openat(-EBADF, nested_test_info.t_mountpoint, O_CLOEXEC | O_DIRECTORY);
+	if (nested_test_info.t_mnt_fd < 0) {
+		log_stderr("failure: openat");
+		goto out;
+	}
+
+	test_setup(&nested_test_info);
+
+	// Run the test.
+	if ((*test)(&nested_test_info)) {
+		log_stderr("failure: calling test");
+		goto out;
+	}
+
+	test_cleanup(&nested_test_info);
+
+	fret = 0;
+	log_debug("Ran test");
+out:
+	snprintf(path, sizeof(path), "%s/" DIR0, info->t_mountpoint);
+	sys_umount2(path, MNT_DETACH);
+out_rm:
+	if (rm_r(info->t_mnt_fd, DIR0))
+		log_stderr("failure: rm_r");
+out_close:
+	safe_close(attr.userns_fd);
+	return fret;
+}
+
+static int tmpfs_acls(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_acls);
+}
+static int tmpfs_create_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_create_in_userns);
+}
+static int tmpfs_device_node_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_device_node_in_userns);
+}
+static int tmpfs_fsids_mapped(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_fsids_mapped);
+}
+static int tmpfs_fsids_unmapped(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_fsids_unmapped);
+}
+static int tmpfs_expected_uid_gid_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_expected_uid_gid_idmapped_mounts);
+}
+static int tmpfs_fscaps_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_fscaps_idmapped_mounts);
+}
+static int tmpfs_fscaps_idmapped_mounts_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_fscaps_idmapped_mounts_in_userns);
+}
+static int tmpfs_fscaps_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_fscaps_idmapped_mounts_in_userns_separate_userns);
+}
+
+static int tmpfs_hardlink_crossing_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_hardlink_crossing_idmapped_mounts);
+}
+static int tmpfs_hardlink_from_idmapped_mount(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_hardlink_from_idmapped_mount);
+}
+static int tmpfs_hardlink_from_idmapped_mount_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_hardlink_from_idmapped_mount_in_userns);
+}
+
+#ifdef HAVE_LIBURING_H
+static int tmpfs_io_uring_idmapped(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped);
+}
+static int tmpfs_io_uring_idmapped_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped_userns);
+}
+static int tmpfs_io_uring_idmapped_unmapped(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped_unmapped);
+}
+static int tmpfs_io_uring_idmapped_unmapped_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_io_uring_idmapped_unmapped_userns);
+}
+#endif /* HAVE_LIBURING_H */
+
+static int tmpfs_protected_symlinks_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_protected_symlinks_idmapped_mounts);
+}
+static int tmpfs_protected_symlinks_idmapped_mounts_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_protected_symlinks_idmapped_mounts_in_userns);
+}
+static int tmpfs_rename_crossing_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_rename_crossing_idmapped_mounts);
+}
+static int tmpfs_rename_from_idmapped_mount(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_rename_from_idmapped_mount);
+}
+static int tmpfs_rename_from_idmapped_mount_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_rename_from_idmapped_mount_in_userns);
+}
+static int tmpfs_setattr_truncate_idmapped(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_setattr_truncate_idmapped);
+}
+static int tmpfs_setattr_truncate_idmapped_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_setattr_truncate_idmapped_in_userns);
+}
+static int tmpfs_setgid_create_idmapped(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_setgid_create_idmapped);
+}
+static int tmpfs_setgid_create_idmapped_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_setgid_create_idmapped_in_userns);
+}
+static int tmpfs_setid_binaries_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_setid_binaries_idmapped_mounts);
+}
+static int tmpfs_setid_binaries_idmapped_mounts_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_setid_binaries_idmapped_mounts_in_userns);
+}
+static int tmpfs_setid_binaries_idmapped_mounts_in_userns_separate_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_setid_binaries_idmapped_mounts_in_userns_separate_userns);
+}
+static int tmpfs_sticky_bit_unlink_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_unlink_idmapped_mounts);
+}
+static int tmpfs_sticky_bit_unlink_idmapped_mounts_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_unlink_idmapped_mounts_in_userns);
+}
+static int tmpfs_sticky_bit_rename_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_rename_idmapped_mounts);
+}
+static int tmpfs_sticky_bit_rename_idmapped_mounts_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_sticky_bit_rename_idmapped_mounts_in_userns);
+}
+static int tmpfs_symlink_idmapped_mounts(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_symlink_idmapped_mounts);
+}
+static int tmpfs_symlink_idmapped_mounts_in_userns(const struct vfstest_info *info)
+{
+	return tmpfs_nested_mount_setup(info, tcore_symlink_idmapped_mounts_in_userns);
+}
+
+static const struct test_struct t_tmpfs[] = {
+	{ tmpfs_acls,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in user namespace",							      },
+	{ tmpfs_create_in_userns,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in user namespace",							      },
+	{ tmpfs_device_node_in_userns,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs device node in user namespace",								      },
+	{ tmpfs_expected_uid_gid_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs expected ownership on idmapped mounts",							},
+	{ tmpfs_fscaps_idmapped_mounts,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs fscaps on idmapped mounts",									},
+	{ tmpfs_fscaps_idmapped_mounts_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs fscaps on idmapped mounts in user namespace",							},
+	{ tmpfs_fscaps_idmapped_mounts_in_userns_separate_userns,		T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs fscaps on idmapped mounts in user namespace with different id mappings",			},
+	{ tmpfs_fsids_mapped,							T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs mapped fsids",										      },
+	{ tmpfs_fsids_unmapped,							T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs unmapped fsids",										      },
+	{ tmpfs_hardlink_crossing_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs cross idmapped mount hardlink",								},
+	{ tmpfs_hardlink_from_idmapped_mount,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs hardlinks from idmapped mounts",								},
+	{ tmpfs_hardlink_from_idmapped_mount_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs hardlinks from idmapped mounts in user namespace",						},
+#ifdef HAVE_LIBURING_H
+	{ tmpfs_io_uring_idmapped,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts",								      },
+	{ tmpfs_io_uring_idmapped_userns,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts in user namespace",					      },
+	{ tmpfs_io_uring_idmapped_unmapped,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts with unmapped ids",					      },
+	{ tmpfs_io_uring_idmapped_unmapped_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs io_uring from idmapped mounts with unmapped ids in user namespace",			      },
+#endif
+	{ tmpfs_protected_symlinks_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs following protected symlinks on idmapped mounts",						},
+	{ tmpfs_protected_symlinks_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs following protected symlinks on idmapped mounts in user namespace",				},
+	{ tmpfs_rename_crossing_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs cross idmapped mount rename",									},
+	{ tmpfs_rename_from_idmapped_mount,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs rename from idmapped mounts",									},
+	{ tmpfs_rename_from_idmapped_mount_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs rename from idmapped mounts in user namespace",						},
+	{ tmpfs_setattr_truncate_idmapped,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setattr truncate on idmapped mounts",								},
+	{ tmpfs_setattr_truncate_idmapped_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setattr truncate on idmapped mounts in user namespace",					},
+	{ tmpfs_setgid_create_idmapped,						T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in directories with setgid bit set on idmapped mounts",			},
+	{ tmpfs_setgid_create_idmapped_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs create operations in directories with setgid bit set on idmapped mounts in user namespace",	},
+	{ tmpfs_setid_binaries_idmapped_mounts,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setid binaries on idmapped mounts",								},
+	{ tmpfs_setid_binaries_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setid binaries on idmapped mounts in user namespace",						},
+	{ tmpfs_setid_binaries_idmapped_mounts_in_userns_separate_userns,	T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs setid binaries on idmapped mounts in user namespace with different id mappings",		},
+	{ tmpfs_sticky_bit_unlink_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit unlink operations on idmapped mounts",						},
+	{ tmpfs_sticky_bit_unlink_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit unlink operations on idmapped mounts in user namespace",				},
+	{ tmpfs_sticky_bit_rename_idmapped_mounts,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit rename operations on idmapped mounts",						},
+	{ tmpfs_sticky_bit_rename_idmapped_mounts_in_userns,			T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs sticky bit rename operations on idmapped mounts in user namespace",				},
+	{ tmpfs_symlink_idmapped_mounts,					T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs symlink from idmapped mounts",									},
+	{ tmpfs_symlink_idmapped_mounts_in_userns,				T_REQUIRE_USERNS | T_REQUIRE_IDMAPPED_MOUNTS,	"tmpfs symlink from idmapped mounts in user namespace",						},
+};
+
+
+const struct test_suite s_tmpfs_idmapped_mounts = {
+	.tests = t_tmpfs,
+	.nr_tests = ARRAY_SIZE(t_tmpfs),
+};
diff --git src/vfs/tmpfs-idmapped-mounts.h src/vfs/tmpfs-idmapped-mounts.h
new file mode 100644
index 00000000..ed24651f
--- /dev/null
+++ src/vfs/tmpfs-idmapped-mounts.h
@@ -0,0 +1,14 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef __TMPFS_IDMAPPED_MOUNTS_H
+#define __TMPFS_IDMAPPED_MOUNTS_H
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+
+#include "utils.h"
+
+extern const struct test_suite s_tmpfs_idmapped_mounts;
+
+#endif /* __TMPFS_IDMAPPED_MOUNTS_H */
diff --git src/vfs/utils.h src/vfs/utils.h
index f1681737..872fd96f 100644
--- src/vfs/utils.h
+++ src/vfs/utils.h
@@ -45,6 +45,8 @@ 
 #define DIR2 "dir2"
 #define DIR3 "dir3"
 #define DIR1_RENAME "dir1_rename"
+// This directory may be used by tests that call another test.
+#define DIR0 "dir0"
 #define HARDLINK1 "hardlink1"
 #define SYMLINK1 "symlink1"
 #define SYMLINK_USER1 "symlink_user1"
diff --git src/vfs/vfstest.c src/vfs/vfstest.c
index 325f04a1..f842117d 100644
--- src/vfs/vfstest.c
+++ src/vfs/vfstest.c
@@ -23,6 +23,7 @@ 
 #include <unistd.h>
 
 #include "btrfs-idmapped-mounts.h"
+#include "tmpfs-idmapped-mounts.h"
 #include "idmapped-mounts.h"
 #include "missing.h"
 #include "utils.h"
@@ -2316,6 +2317,7 @@  static void usage(void)
 	fprintf(stderr, "--test-fscaps-regression            Run fscap regression tests\n");
 	fprintf(stderr, "--test-nested-userns                Run nested userns idmapped mount testsuite\n");
 	fprintf(stderr, "--test-btrfs                        Run btrfs specific idmapped mount testsuite\n");
+	fprintf(stderr, "--test-tmpfs                        Run tmpfs specific idmapped mount testsuite\n");
 	fprintf(stderr, "--test-setattr-fix-968219708108     Run setattr regression tests\n");
 	fprintf(stderr, "--test-setxattr-fix-705191b03d50    Run setxattr regression tests\n");
 	fprintf(stderr, "--test-setgid-create-umask          Run setgid with umask tests\n");
@@ -2340,6 +2342,7 @@  static const struct option longopts[] = {
 	{"test-setxattr-fix-705191b03d50",	no_argument,		0,	'j'},
 	{"test-setgid-create-umask",		no_argument,		0,	'u'},
 	{"test-setgid-create-acl",		no_argument,		0,	'l'},
+	{"test-tmpfs",				no_argument,		0,	't'},
 	{NULL,					0,			0,	  0},
 };
 
@@ -2480,7 +2483,7 @@  int main(int argc, char *argv[])
 	bool idmapped_mounts_supported = false, test_btrfs = false,
 	     test_core = false, test_fscaps_regression = false,
 	     test_nested_userns = false, test_setattr_fix_968219708108 = false,
-	     test_setxattr_fix_705191b03d50 = false,
+	     test_setxattr_fix_705191b03d50 = false, test_tmpfs = false,
 	     test_setgid_create_umask = false, test_setgid_create_acl = false;
 
 	init_vfstest_info(&info);
@@ -2529,6 +2532,9 @@  int main(int argc, char *argv[])
 		case 'l':
 			test_setgid_create_acl = true;
 			break;
+		case 't':
+			test_tmpfs = true;
+			break;
 		case 'h':
 			/* fallthrough */
 		default:
@@ -2622,6 +2628,11 @@  int main(int argc, char *argv[])
 			goto out;
 	}
 
+	if (test_tmpfs) {
+		if (!run_suite(&info, &s_tmpfs_idmapped_mounts))
+			goto out;
+	}
+
 	fret = EXIT_SUCCESS;
 
 out:
diff --git tests/tmpfs/001 tests/tmpfs/001
new file mode 100755
index 00000000..37ef0b18
--- /dev/null
+++ tests/tmpfs/001
@@ -0,0 +1,27 @@ 
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0
+# Copyright (c) 2023 Rodrigo Campos Catelin (Microsoft). All Rights Reserved.
+#
+# FS QA Test 001
+#
+# Test that idmapped mounts behave correctly with tmpfs filesystem.
+#
+. ./common/preamble
+_begin_fstest auto quick idmapped
+
+# get standard environment, filters and checks
+. ./common/filter
+
+# real QA test starts here
+
+_supported_fs tmpfs
+_require_idmapped_mounts
+_require_test
+
+echo "Silence is golden"
+
+$here/src/vfs/vfstest --test-tmpfs --device "$TEST_DEV" \
+	        --mount "$TEST_DIR" --fstype "$FSTYP"
+
+status=$?
+exit
diff --git tests/tmpfs/001.out tests/tmpfs/001.out
new file mode 100644
index 00000000..88678b8e
--- /dev/null
+++ tests/tmpfs/001.out
@@ -0,0 +1,2 @@ 
+QA output created by 001
+Silence is golden
diff --git tests/tmpfs/Makefile tests/tmpfs/Makefile
new file mode 100644
index 00000000..46544313
--- /dev/null
+++ tests/tmpfs/Makefile
@@ -0,0 +1,24 @@ 
+#
+# Copyright (c) 2023 Rodrigo Campos Catelin (Microsoft). All Rights Reserved.
+#
+
+TOPDIR = ../..
+include $(TOPDIR)/include/builddefs
+include $(TOPDIR)/include/buildgrouplist
+
+TMPFS_DIR = tmpfs
+TARGET_DIR = $(PKG_LIB_DIR)/$(TESTS_DIR)/$(TMPFS_DIR)
+DIRT = group.list
+
+default: $(DIRT)
+
+include $(BUILDRULES)
+
+install:
+	$(INSTALL) -m 755 -d $(TARGET_DIR)
+	$(INSTALL) -m 755 $(TESTS) $(TARGET_DIR)
+	$(INSTALL) -m 644 group.list $(TARGET_DIR)
+	$(INSTALL) -m 644 $(OUTFILES) $(TARGET_DIR)
+
+# Nothing.
+install-dev install-lib: