| /* 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> | 
 |  | 
 | #include "idmapped-mounts/missing.h" | 
 |  | 
 | 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); | 
 | } |