@@ -62,6 +62,7 @@
/src/cloner
/src/dbtest
/src/deduperace
+/src/detached_mounts_propagation
/src/devzero
/src/dio-interleaved
/src/dio-invalidate-cache
@@ -29,7 +29,8 @@ LINUX_TARGETS = xfsctl bstat t_mtab getdevicesize preallo_rw_pattern_reader \
attr-list-by-handle-cursor-test listxattr dio-interleaved t_dir_type \
dio-invalidate-cache stat_test t_encrypted_d_revalidate \
attr_replace_test swapon mkswap t_attr_corruption t_open_tmpfiles \
- fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail
+ fscrypt-crypt-util bulkstat_null_ocount splice-test chprojid_fail \
+ detached_mounts_propagation
SUBDIRS = log-writes perf
new file mode 100644
@@ -0,0 +1,252 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+/*
+ * Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
+ * All Rights Reserved.
+ *
+ * Regression test to verify that creating a series of detached mounts,
+ * attaching them to the filesystem, and unmounting them does not trigger an
+ * integer overflow in ns->mounts causing the kernel to block any new mounts in
+ * count_mounts() and returning ENOSPC because it falsely assumes that the
+ * maximum number of mounts in the mount namespace has been reached, i.e. it
+ * thinks it can't fit the new mounts into the mount namespace anymore.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <limits.h>
+#include <sched.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* open_tree() */
+#ifndef OPEN_TREE_CLONE
+#define OPEN_TREE_CLONE 1
+#endif
+
+#ifndef OPEN_TREE_CLOEXEC
+#define OPEN_TREE_CLOEXEC O_CLOEXEC
+#endif
+
+#ifndef __NR_open_tree
+ #if defined __alpha__
+ #define __NR_open_tree 538
+ #elif defined _MIPS_SIM
+ #if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */
+ #define __NR_open_tree 4428
+ #endif
+ #if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */
+ #define __NR_open_tree 6428
+ #endif
+ #if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */
+ #define __NR_open_tree 5428
+ #endif
+ #elif defined __ia64__
+ #define __NR_open_tree (428 + 1024)
+ #else
+ #define __NR_open_tree 428
+ #endif
+#endif
+
+/* move_mount() */
+#ifndef MOVE_MOUNT_F_EMPTY_PATH
+#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
+#endif
+
+#ifndef __NR_move_mount
+ #if defined __alpha__
+ #define __NR_move_mount 539
+ #elif defined _MIPS_SIM
+ #if _MIPS_SIM == _MIPS_SIM_ABI32 /* o32 */
+ #define __NR_move_mount 4429
+ #endif
+ #if _MIPS_SIM == _MIPS_SIM_NABI32 /* n32 */
+ #define __NR_move_mount 6429
+ #endif
+ #if _MIPS_SIM == _MIPS_SIM_ABI64 /* n64 */
+ #define __NR_move_mount 5429
+ #endif
+ #elif defined __ia64__
+ #define __NR_move_mount (428 + 1024)
+ #else
+ #define __NR_move_mount 429
+ #endif
+#endif
+
+static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
+{
+ return syscall(__NR_open_tree, dfd, filename, flags);
+}
+
+static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
+ const char *to_pathname, unsigned int flags)
+{
+ return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
+}
+
+static bool is_shared_mountpoint(const char *path)
+{
+ bool shared = false;
+ FILE *f = NULL;
+ char *line = NULL;
+ int i;
+ size_t len = 0;
+
+ f = fopen("/proc/self/mountinfo", "re");
+ if (!f)
+ return 0;
+
+ while (getline(&line, &len, f) > 0) {
+ char *slider1, *slider2;
+
+ for (slider1 = line, i = 0; slider1 && i < 4; i++)
+ slider1 = strchr(slider1 + 1, ' ');
+
+ if (!slider1)
+ continue;
+
+ slider2 = strchr(slider1 + 1, ' ');
+ if (!slider2)
+ continue;
+
+ *slider2 = '\0';
+ if (strcmp(slider1 + 1, path) == 0) {
+ /* This is the path. Is it shared? */
+ slider1 = strchr(slider2 + 1, ' ');
+ if (slider1 && strstr(slider1, "shared:")) {
+ shared = true;
+ break;
+ }
+ }
+ }
+ fclose(f);
+ free(line);
+
+ return shared;
+}
+
+static void usage(void)
+{
+ const char *text = "mount-new [--recursive] <base-dir>\n";
+ fprintf(stderr, "%s", text);
+ _exit(EXIT_SUCCESS);
+}
+
+#define exit_usage(format, ...) \
+ ({ \
+ fprintf(stderr, format "\n", ##__VA_ARGS__); \
+ usage(); \
+ })
+
+#define exit_log(format, ...) \
+ ({ \
+ fprintf(stderr, format "\n", ##__VA_ARGS__); \
+ exit(EXIT_FAILURE); \
+ })
+
+static const struct option longopts[] = {
+ {"help", no_argument, 0, 'a'},
+ { NULL, no_argument, 0, 0 },
+};
+
+int main(int argc, char *argv[])
+{
+ int exit_code = EXIT_SUCCESS, index = 0;
+ int dfd, fd_tree, new_argc, ret;
+ char *base_dir;
+ char *const *new_argv;
+ char target[PATH_MAX];
+
+ while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
+ switch (ret) {
+ case 'a':
+ /* fallthrough */
+ default:
+ usage();
+ }
+ }
+
+ new_argv = &argv[optind];
+ new_argc = argc - optind;
+ if (new_argc < 1)
+ exit_usage("Missing base directory\n");
+ base_dir = new_argv[0];
+
+ if (*base_dir != '/')
+ exit_log("Please specify an absolute path");
+
+ /* Ensure that target is a shared mountpoint. */
+ if (!is_shared_mountpoint(base_dir))
+ exit_log("Please ensure that \"%s\" is a shared mountpoint", base_dir);
+
+ ret = unshare(CLONE_NEWNS);
+ if (ret < 0)
+ exit_log("%m - Failed to create new mount namespace");
+
+ ret = mount(NULL, base_dir, NULL, MS_REC | MS_SHARED, NULL);
+ if (ret < 0)
+ exit_log("%m - Failed to make base_dir shared mountpoint");
+
+ dfd = open(base_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (dfd < 0)
+ exit_log("%m - Failed to open base directory \"%s\"", base_dir);
+
+ ret = mkdirat(dfd, "detached-move-mount", 0755);
+ if (ret < 0)
+ exit_log("%m - Failed to create required temporary directories");
+
+ ret = snprintf(target, sizeof(target), "%s/detached-move-mount", base_dir);
+ if (ret < 0 || (size_t)ret >= sizeof(target))
+ exit_log("%m - Failed to assemble target path");
+
+ /*
+ * Having a mount table with 10000 mounts is already quite excessive
+ * and shoult account even for weird test systems.
+ */
+ for (size_t i = 0; i < 10000; i++) {
+ fd_tree = sys_open_tree(dfd, "detached-move-mount",
+ OPEN_TREE_CLONE |
+ OPEN_TREE_CLOEXEC |
+ AT_EMPTY_PATH);
+ if (fd_tree < 0) {
+ if (errno == ENOSYS) /* New mount API not (fully) supported. */
+ break;
+
+ fprintf(stderr, "%m - Failed to open %d(detached-move-mount)", dfd);
+ exit_code = EXIT_FAILURE;
+ break;
+ }
+
+ ret = sys_move_mount(fd_tree, "", dfd, "detached-move-mount", MOVE_MOUNT_F_EMPTY_PATH);
+ if (ret < 0) {
+ if (errno == ENOSPC)
+ fprintf(stderr, "%m - Buggy mount counting");
+ else if (errno == ENOSYS) /* New mount API not (fully) supported. */
+ break;
+ else
+ fprintf(stderr, "%m - Failed to attach mount to %d(detached-move-mount)", dfd);
+ exit_code = EXIT_FAILURE;
+ break;
+ }
+ close(fd_tree);
+
+ ret = umount2(target, MNT_DETACH);
+ if (ret < 0) {
+ fprintf(stderr, "%m - Failed to unmount %s", target);
+ exit_code = EXIT_FAILURE;
+ break;
+ }
+ }
+
+ (void)unlinkat(dfd, "detached-move-mount", AT_REMOVEDIR);
+ close(dfd);
+
+ exit(exit_code);
+}
new file mode 100644
@@ -0,0 +1,43 @@
+#! /bin/bash
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
+# All Rights Reserved.
+#
+# FS QA Test No. 631
+#
+# Regression test to verify that creating a series of detached mounts,
+# attaching them to the filesystem, and unmounting them does not trigger an
+# integer overflow in ns->mounts causing the kernel to block any new mounts in
+# count_mounts() and returning ENOSPC because it falsely assumes that the
+# maximum number of mounts in the mount namespace has been reached, i.e. it
+# thinks it can't fit the new mounts into the mount namespace anymore.
+#
+seq=`basename $0`
+seqres=$RESULT_DIR/$seq
+echo "QA output created by $seq"
+
+here=`pwd`
+tmp=/tmp/$$
+status=1 # failure is the default!
+trap "_cleanup; exit \$status" 0 1 2 3 15
+
+_cleanup()
+{
+ cd /
+ rm -f $tmp.*
+}
+
+. ./common/rc
+
+rm -f $seqres.full
+
+_supported_fs generic
+_require_test
+_require_test_program "detached_mounts_propagation"
+
+$here/src/detached_mounts_propagation $TEST_DIR >> $seqres.full
+status=$?
+
+echo silence is golden
+exit
new file mode 100644
@@ -0,0 +1,2 @@
+QA output created by 631
+silence is golden
@@ -633,3 +633,4 @@
628 auto quick rw clone
629 auto quick rw copy_range
630 auto quick rw dedupe clone
+631 auto quick mount