4a588e46c59539ff7bfdd2a2a0897164df686a0b
[xfstests-dev.git] / src / detached_mounts_propagation.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /*
3  * Copyright (c) 2021 Christian Brauner <christian.brauner@ubuntu.com>
4  * All Rights Reserved.
5  *
6  * Regression test to verify that creating a series of detached mounts,
7  * attaching them to the filesystem, and unmounting them does not trigger an
8  * integer overflow in ns->mounts causing the kernel to block any new mounts in
9  * count_mounts() and returning ENOSPC because it falsely assumes that the
10  * maximum number of mounts in the mount namespace has been reached, i.e. it
11  * thinks it can't fit the new mounts into the mount namespace anymore.
12  */
13
14 #include <errno.h>
15 #include <fcntl.h>
16 #include <getopt.h>
17 #include <limits.h>
18 #include <sched.h>
19 #include <stdbool.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <sys/mount.h>
24 #include <sys/stat.h>
25 #include <sys/syscall.h>
26 #include <sys/types.h>
27 #include <unistd.h>
28
29 /* open_tree() */
30 #ifndef OPEN_TREE_CLONE
31 #define OPEN_TREE_CLONE 1
32 #endif
33
34 #ifndef OPEN_TREE_CLOEXEC
35 #define OPEN_TREE_CLOEXEC O_CLOEXEC
36 #endif
37
38 #ifndef __NR_open_tree
39         #if defined __alpha__
40                 #define __NR_open_tree 538
41         #elif defined _MIPS_SIM
42                 #if _MIPS_SIM == _MIPS_SIM_ABI32        /* o32 */
43                         #define __NR_open_tree 4428
44                 #endif
45                 #if _MIPS_SIM == _MIPS_SIM_NABI32       /* n32 */
46                         #define __NR_open_tree 6428
47                 #endif
48                 #if _MIPS_SIM == _MIPS_SIM_ABI64        /* n64 */
49                         #define __NR_open_tree 5428
50                 #endif
51         #elif defined __ia64__
52                 #define __NR_open_tree (428 + 1024)
53         #else
54                 #define __NR_open_tree 428
55         #endif
56 #endif
57
58 /* move_mount() */
59 #ifndef MOVE_MOUNT_F_EMPTY_PATH
60 #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 /* Empty from path permitted */
61 #endif
62
63 #ifndef __NR_move_mount
64         #if defined __alpha__
65                 #define __NR_move_mount 539
66         #elif defined _MIPS_SIM
67                 #if _MIPS_SIM == _MIPS_SIM_ABI32        /* o32 */
68                         #define __NR_move_mount 4429
69                 #endif
70                 #if _MIPS_SIM == _MIPS_SIM_NABI32       /* n32 */
71                         #define __NR_move_mount 6429
72                 #endif
73                 #if _MIPS_SIM == _MIPS_SIM_ABI64        /* n64 */
74                         #define __NR_move_mount 5429
75                 #endif
76         #elif defined __ia64__
77                 #define __NR_move_mount (428 + 1024)
78         #else
79                 #define __NR_move_mount 429
80         #endif
81 #endif
82
83 static inline int sys_open_tree(int dfd, const char *filename, unsigned int flags)
84 {
85         return syscall(__NR_open_tree, dfd, filename, flags);
86 }
87
88 static inline int sys_move_mount(int from_dfd, const char *from_pathname, int to_dfd,
89                                  const char *to_pathname, unsigned int flags)
90 {
91         return syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd, to_pathname, flags);
92 }
93
94 static bool is_shared_mountpoint(const char *path)
95 {
96         bool shared = false;
97         FILE *f = NULL;
98         char *line = NULL;
99         int i;
100         size_t len = 0;
101
102         f = fopen("/proc/self/mountinfo", "re");
103         if (!f)
104                 return 0;
105
106         while (getline(&line, &len, f) > 0) {
107                 char *slider1, *slider2;
108
109                 for (slider1 = line, i = 0; slider1 && i < 4; i++)
110                         slider1 = strchr(slider1 + 1, ' ');
111
112                 if (!slider1)
113                         continue;
114
115                 slider2 = strchr(slider1 + 1, ' ');
116                 if (!slider2)
117                         continue;
118
119                 *slider2 = '\0';
120                 if (strcmp(slider1 + 1, path) == 0) {
121                         /* This is the path. Is it shared? */
122                         slider1 = strchr(slider2 + 1, ' ');
123                         if (slider1 && strstr(slider1, "shared:")) {
124                                 shared = true;
125                                 break;
126                         }
127                 }
128         }
129         fclose(f);
130         free(line);
131
132         return shared;
133 }
134
135 static void usage(void)
136 {
137         const char *text = "mount-new [--recursive] <base-dir>\n";
138         fprintf(stderr, "%s", text);
139         _exit(EXIT_SUCCESS);
140 }
141
142 #define exit_usage(format, ...)                              \
143         ({                                                   \
144                 fprintf(stderr, format "\n", ##__VA_ARGS__); \
145                 usage();                                     \
146         })
147
148 #define exit_log(format, ...)                                \
149         ({                                                   \
150                 fprintf(stderr, format "\n", ##__VA_ARGS__); \
151                 exit(EXIT_FAILURE);                          \
152         })
153
154 static const struct option longopts[] = {
155         {"help",        no_argument,            0,      'a'},
156         { NULL,         no_argument,            0,       0 },
157 };
158
159 int main(int argc, char *argv[])
160 {
161         int exit_code = EXIT_SUCCESS, index = 0;
162         int dfd, fd_tree, new_argc, ret;
163         char *base_dir;
164         char *const *new_argv;
165         char target[PATH_MAX];
166
167         while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
168                 switch (ret) {
169                 case 'a':
170                         /* fallthrough */
171                 default:
172                         usage();
173                 }
174         }
175
176         new_argv = &argv[optind];
177         new_argc = argc - optind;
178         if (new_argc < 1)
179                 exit_usage("Missing base directory\n");
180         base_dir = new_argv[0];
181
182         if (*base_dir != '/')
183                 exit_log("Please specify an absolute path");
184
185         /* Ensure that target is a shared mountpoint. */
186         if (!is_shared_mountpoint(base_dir))
187                 exit_log("Please ensure that \"%s\" is a shared mountpoint", base_dir);
188
189         ret = unshare(CLONE_NEWNS);
190         if (ret < 0)
191                 exit_log("%m - Failed to create new mount namespace");
192
193         ret = mount(NULL, base_dir, NULL, MS_REC | MS_SHARED, NULL);
194         if (ret < 0)
195                 exit_log("%m - Failed to make base_dir shared mountpoint");
196
197         dfd = open(base_dir, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
198         if (dfd < 0)
199                 exit_log("%m - Failed to open base directory \"%s\"", base_dir);
200
201         ret = mkdirat(dfd, "detached-move-mount", 0755);
202         if (ret < 0)
203                 exit_log("%m - Failed to create required temporary directories");
204
205         ret = snprintf(target, sizeof(target), "%s/detached-move-mount", base_dir);
206         if (ret < 0 || (size_t)ret >= sizeof(target))
207                 exit_log("%m - Failed to assemble target path");
208
209         /*
210          * Having a mount table with 10000 mounts is already quite excessive
211          * and shoult account even for weird test systems.
212          */
213         for (size_t i = 0; i < 10000; i++) {
214                 fd_tree = sys_open_tree(dfd, "detached-move-mount",
215                                         OPEN_TREE_CLONE |
216                                         OPEN_TREE_CLOEXEC |
217                                         AT_EMPTY_PATH);
218                 if (fd_tree < 0) {
219                         if (errno == ENOSYS) /* New mount API not (fully) supported. */
220                                 break;
221
222                         fprintf(stderr, "%m - Failed to open %d(detached-move-mount)", dfd);
223                         exit_code = EXIT_FAILURE;
224                         break;
225                 }
226
227                 ret = sys_move_mount(fd_tree, "", dfd, "detached-move-mount", MOVE_MOUNT_F_EMPTY_PATH);
228                 if (ret < 0) {
229                         if (errno == ENOSPC)
230                                 fprintf(stderr, "%m - Buggy mount counting");
231                         else if (errno == ENOSYS) /* New mount API not (fully) supported. */
232                                 break;
233                         else
234                                 fprintf(stderr, "%m - Failed to attach mount to %d(detached-move-mount)", dfd);
235                         exit_code = EXIT_FAILURE;
236                         break;
237                 }
238                 close(fd_tree);
239
240                 ret = umount2(target, MNT_DETACH);
241                 if (ret < 0) {
242                         fprintf(stderr, "%m - Failed to unmount %s", target);
243                         exit_code = EXIT_FAILURE;
244                         break;
245                 }
246         }
247
248         (void)unlinkat(dfd, "detached-move-mount", AT_REMOVEDIR);
249         close(dfd);
250
251         exit(exit_code);
252 }