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.
prev 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).