linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
To: Christian Brauner <christian.brauner@ubuntu.com>,
	linux-fsdevel@vger.kernel.org
Cc: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>,
	Alexander Viro <viro@zeniv.linux.org.uk>,
	Mattias Nissler <mnissler@chromium.org>,
	Aleksa Sarai <cyphar@cyphar.com>, Andrei Vagin <avagin@gmail.com>,
	linux-api@vger.kernel.org, lkml <linux-kernel@vger.kernel.org>
Subject: Re: [PATCH v4 2/2] tests: add move_mount(MOVE_MOUNT_SET_GROUP) selftest
Date: Thu, 15 Jul 2021 12:51:09 +0300	[thread overview]
Message-ID: <d15401ca-f4f2-0042-62ff-a1b1dfa28548@virtuozzo.com> (raw)
In-Reply-To: <20210714161056.105591-2-ptikhomirov@virtuozzo.com>

Adding everybody back to CC, sorry I accidentally dropped all CC in the 
2/2 selftest patch.

On 14.07.2021 19:10, Pavel Tikhomirov wrote:
> Add a simple selftest for a move_mount(MOVE_MOUNT_SET_GROUP). This tests
> that one can copy sharing from one mount from nested mntns with nested
> userns owner to another mount from other nested mntns with other nested
> userns owner while in their parent userns.
> 
>    TAP version 13
>    1..1
>    # Starting 1 tests from 2 test cases.
>    #  RUN           move_mount_set_group.complex_sharing_copying ...
>    #            OK  move_mount_set_group.complex_sharing_copying
>    ok 1 move_mount_set_group.complex_sharing_copying
>    # PASSED: 1 / 1 tests passed.
>    # Totals: pass:1 fail:0 xfail:0 xpass:0 skip:0 error:0
> 
> Signed-off-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
> 
> ---
> I took mount_setattr test as an example, I'm not to experienced in
> selftests so hope I'm not doing something wrong here.
> 
> I implemented a testcase having in mind the way how I plan to use this
> interface in criu, so it's not simply copying sharing between two nearby
> mounts but it also adds some userns+mntns-es to test cross-namespace
> copying.
> 
> Note: One can also test MOVE_MOUNT_SET_GROUP via zdtm tests on criu
> mount-v2 POC: https://github.com/Snorch/criu/commits/mount-v2-poc
> 
> v3: add some test
> 
> ---
>   tools/testing/selftests/Makefile              |   1 +
>   .../selftests/move_mount_set_group/.gitignore |   1 +
>   .../selftests/move_mount_set_group/Makefile   |   7 +
>   .../selftests/move_mount_set_group/config     |   1 +
>   .../move_mount_set_group_test.c               | 375 ++++++++++++++++++
>   5 files changed, 385 insertions(+)
>   create mode 100644 tools/testing/selftests/move_mount_set_group/.gitignore
>   create mode 100644 tools/testing/selftests/move_mount_set_group/Makefile
>   create mode 100644 tools/testing/selftests/move_mount_set_group/config
>   create mode 100644 tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c
> 
> diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
> index fb010a35d61a..dd0388eab94d 100644
> --- a/tools/testing/selftests/Makefile
> +++ b/tools/testing/selftests/Makefile
> @@ -35,6 +35,7 @@ TARGETS += memory-hotplug
>   TARGETS += mincore
>   TARGETS += mount
>   TARGETS += mount_setattr
> +TARGETS += move_mount_set_group
>   TARGETS += mqueue
>   TARGETS += nci
>   TARGETS += net
> diff --git a/tools/testing/selftests/move_mount_set_group/.gitignore b/tools/testing/selftests/move_mount_set_group/.gitignore
> new file mode 100644
> index 000000000000..f5e339268720
> --- /dev/null
> +++ b/tools/testing/selftests/move_mount_set_group/.gitignore
> @@ -0,0 +1 @@
> +move_mount_set_group_test
> diff --git a/tools/testing/selftests/move_mount_set_group/Makefile b/tools/testing/selftests/move_mount_set_group/Makefile
> new file mode 100644
> index 000000000000..80c2d86812b0
> --- /dev/null
> +++ b/tools/testing/selftests/move_mount_set_group/Makefile
> @@ -0,0 +1,7 @@
> +# SPDX-License-Identifier: GPL-2.0
> +# Makefile for mount selftests.
> +CFLAGS = -g -I../../../../usr/include/ -Wall -O2
> +
> +TEST_GEN_FILES += move_mount_set_group_test
> +
> +include ../lib.mk
> diff --git a/tools/testing/selftests/move_mount_set_group/config b/tools/testing/selftests/move_mount_set_group/config
> new file mode 100644
> index 000000000000..416bd53ce982
> --- /dev/null
> +++ b/tools/testing/selftests/move_mount_set_group/config
> @@ -0,0 +1 @@
> +CONFIG_USER_NS=y
> diff --git a/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c
> new file mode 100644
> index 000000000000..ca0c0c2db991
> --- /dev/null
> +++ b/tools/testing/selftests/move_mount_set_group/move_mount_set_group_test.c
> @@ -0,0 +1,375 @@
> +// SPDX-License-Identifier: GPL-2.0
> +#define _GNU_SOURCE
> +#include <sched.h>
> +#include <stdio.h>
> +#include <errno.h>
> +#include <string.h>
> +#include <sys/stat.h>
> +#include <sys/types.h>
> +#include <sys/mount.h>
> +#include <sys/wait.h>
> +#include <stdlib.h>
> +#include <unistd.h>
> +#include <fcntl.h>
> +#include <stdbool.h>
> +#include <stdarg.h>
> +#include <sys/syscall.h>
> +
> +#include "../kselftest_harness.h"
> +
> +#ifndef CLONE_NEWNS
> +#define CLONE_NEWNS 0x00020000
> +#endif
> +
> +#ifndef CLONE_NEWUSER
> +#define CLONE_NEWUSER 0x10000000
> +#endif
> +
> +#ifndef MS_SHARED
> +#define MS_SHARED (1 << 20)
> +#endif
> +
> +#ifndef MS_PRIVATE
> +#define MS_PRIVATE (1<<18)
> +#endif
> +
> +#ifndef MOVE_MOUNT_SET_GROUP
> +#define MOVE_MOUNT_SET_GROUP 0x00000100
> +#endif
> +
> +#ifndef MOVE_MOUNT_F_EMPTY_PATH
> +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
> +#endif
> +
> +#ifndef MOVE_MOUNT_T_EMPTY_PATH
> +#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040
> +#endif
> +
> +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 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;
> +}
> +
> +/* Attempt to de-conflict with the selftests tree. */
> +#ifndef SKIP
> +#define SKIP(s, ...)	XFAIL(s, ##__VA_ARGS__)
> +#endif
> +
> +#define SET_GROUP_FROM	"/tmp/move_mount_set_group_supported_from"
> +#define SET_GROUP_TO	"/tmp/move_mount_set_group_supported_to"
> +
> +static int move_mount_set_group_supported(void)
> +{
> +	int ret;
> +
> +	if (mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
> +		  "size=100000,mode=700"))
> +		return -1;
> +
> +	if (mount(NULL, "/tmp", NULL, MS_PRIVATE, 0))
> +		return -1;
> +
> +	if (mkdir(SET_GROUP_FROM, 0777))
> +		return -1;
> +
> +	if (mkdir(SET_GROUP_TO, 0777))
> +		return -1;
> +
> +	if (mount("testing", SET_GROUP_FROM, "tmpfs", MS_NOATIME | MS_NODEV,
> +		  "size=100000,mode=700"))
> +		return -1;
> +
> +	if (mount(SET_GROUP_FROM, SET_GROUP_TO, NULL, MS_BIND, NULL))
> +		return -1;
> +
> +	if (mount(NULL, SET_GROUP_FROM, NULL, MS_SHARED, 0))
> +		return -1;
> +
> +	ret = syscall(SYS_move_mount, AT_FDCWD, SET_GROUP_FROM,
> +		      AT_FDCWD, SET_GROUP_TO, MOVE_MOUNT_SET_GROUP);
> +	umount2("/tmp", MNT_DETACH);
> +
> +	return ret < 0 ? false : true;
> +}
> +
> +FIXTURE(move_mount_set_group) {
> +};
> +
> +#define SET_GROUP_A "/tmp/A"
> +
> +FIXTURE_SETUP(move_mount_set_group)
> +{
> +	int ret;
> +
> +	ASSERT_EQ(prepare_unpriv_mountns(), 0);
> +
> +	ret = move_mount_set_group_supported();
> +	ASSERT_GE(ret, 0);
> +	if (!ret)
> +		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
> +
> +	umount2("/tmp", MNT_DETACH);
> +
> +	ASSERT_EQ(mount("testing", "/tmp", "tmpfs", MS_NOATIME | MS_NODEV,
> +			"size=100000,mode=700"), 0);
> +
> +	ASSERT_EQ(mkdir(SET_GROUP_A, 0777), 0);
> +
> +	ASSERT_EQ(mount("testing", SET_GROUP_A, "tmpfs", MS_NOATIME | MS_NODEV,
> +			"size=100000,mode=700"), 0);
> +}
> +
> +FIXTURE_TEARDOWN(move_mount_set_group)
> +{
> +	int ret;
> +
> +	ret = move_mount_set_group_supported();
> +	ASSERT_GE(ret, 0);
> +	if (!ret)
> +		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
> +
> +	umount2("/tmp", MNT_DETACH);
> +}
> +
> +#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 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);
> +}
> +
> +struct child_args {
> +	int unsfd;
> +	int mntnsfd;
> +	bool shared;
> +	int mntfd;
> +};
> +
> +static int get_nestedns_mount_cb(void *data)
> +{
> +	struct child_args *ca = (struct child_args *)data;
> +	int ret;
> +
> +	ret = prepare_unpriv_mountns();
> +	if (ret)
> +		return 1;
> +
> +	if (ca->shared) {
> +		ret = mount(NULL, SET_GROUP_A, NULL, MS_SHARED, 0);
> +		if (ret)
> +			return 1;
> +	}
> +
> +	ret = open("/proc/self/ns/user", O_RDONLY);
> +	if (ret < 0)
> +		return 1;
> +	ca->unsfd = ret;
> +
> +	ret = open("/proc/self/ns/mnt", O_RDONLY);
> +	if (ret < 0)
> +		return 1;
> +	ca->mntnsfd = ret;
> +
> +	ret = open(SET_GROUP_A, O_RDONLY);
> +	if (ret < 0)
> +		return 1;
> +	ca->mntfd = ret;
> +
> +	return 0;
> +}
> +
> +TEST_F(move_mount_set_group, complex_sharing_copying)
> +{
> +	struct child_args ca_from = {
> +		.shared = true,
> +	};
> +	struct child_args ca_to = {
> +		.shared = false,
> +	};
> +	pid_t pid;
> +	int ret;
> +
> +	ret = move_mount_set_group_supported();
> +	ASSERT_GE(ret, 0);
> +	if (!ret)
> +		SKIP(return, "move_mount(MOVE_MOUNT_SET_GROUP) is not supported");
> +
> +	pid = do_clone(get_nestedns_mount_cb, (void *)&ca_from, CLONE_VFORK |
> +		       CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
> +	ASSERT_EQ(wait_for_pid(pid), 0);
> +
> +	pid = do_clone(get_nestedns_mount_cb, (void *)&ca_to, CLONE_VFORK |
> +		       CLONE_VM | CLONE_FILES); ASSERT_GT(pid, 0);
> +	ASSERT_EQ(wait_for_pid(pid), 0);
> +
> +	ASSERT_EQ(syscall(SYS_move_mount, ca_from.mntfd, "",
> +			  ca_to.mntfd, "", MOVE_MOUNT_SET_GROUP
> +			  | MOVE_MOUNT_F_EMPTY_PATH | MOVE_MOUNT_T_EMPTY_PATH),
> +		  0);
> +
> +	ASSERT_EQ(setns(ca_to.mntnsfd, CLONE_NEWNS), 0);
> +	ASSERT_EQ(is_shared_mount(SET_GROUP_A), 1);
> +}
> +
> +TEST_HARNESS_MAIN
> 

-- 
Best regards, Tikhomirov Pavel
Software Developer, Virtuozzo.

      parent reply	other threads:[~2021-07-15  9:51 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-07-14 16:10 [PATCH v4 1/2] move_mount: allow to add a mount into an existing group Pavel Tikhomirov
     [not found] ` <20210714161056.105591-2-ptikhomirov@virtuozzo.com>
2021-07-15  9:51   ` Pavel Tikhomirov [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=d15401ca-f4f2-0042-62ff-a1b1dfa28548@virtuozzo.com \
    --to=ptikhomirov@virtuozzo.com \
    --cc=avagin@gmail.com \
    --cc=christian.brauner@ubuntu.com \
    --cc=cyphar@cyphar.com \
    --cc=linux-api@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mnissler@chromium.org \
    --cc=viro@zeniv.linux.org.uk \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).