lkml.org 
[lkml]   [2021]   [Jul]   [15]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
SubjectRe: [PATCH v4 2/2] tests: add move_mount(MOVE_MOUNT_SET_GROUP) selftest
From
Date
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.

\
 
 \ /
  Last update: 2021-07-15 11:52    [W:1.769 / U:0.024 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site