xfs/106: don't test disabling quota accounting
[xfstests-dev.git] / src / nsexec.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright 2013, Michael Kerrisk
4  *
5  * Create a child process that executes a shell command in new
6  * namespace(s); allow UID and GID mappings to be specified when
7  * creating a user namespace.
8  */
9
10 #ifndef  _GNU_SOURCE
11 #define _GNU_SOURCE
12 #endif
13 #include <sched.h>
14 #include <unistd.h>
15 #include <stdlib.h>
16 #include <sys/wait.h>
17 #include <signal.h>
18 #include <fcntl.h>
19 #include <stdio.h>
20 #include <string.h>
21 #include <limits.h>
22 #include <errno.h>
23
24 /* A simple error-handling function: print an error message based
25    on the value in 'errno' and terminate the calling process */
26
27 #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
28                         } while (0)
29
30 struct child_args {
31     char **argv;        /* Command to be executed by child, with arguments */
32     int    pipe_fd[2];  /* Pipe used to synchronize parent and child */
33 };
34
35 static int verbose, setid;
36
37 static void
38 usage(char *pname)
39 {
40     fprintf(stderr, "Usage: %s [options] cmd [arg...]\n\n", pname);
41     fprintf(stderr, "Create a child process that executes a shell command "
42             "in a new user namespace,\n"
43             "and possibly also other new namespace(s).\n\n");
44     fprintf(stderr, "Options can be:\n\n");
45 #define fpe(str) fprintf(stderr, "    %s", str);
46     fpe("-i          New IPC namespace\n");
47     fpe("-m          New mount namespace\n");
48     fpe("-n          New network namespace\n");
49     fpe("-p          New PID namespace\n");
50     fpe("-u          New UTS namespace\n");
51     fpe("-U          New user namespace\n");
52     fpe("-M uid_map  Specify UID map for user namespace\n");
53     fpe("-G gid_map  Specify GID map for user namespace\n");
54     fpe("            If -M or -G is specified, -U is required\n");
55     fpe("-s          Set uid/gid to 0 in the new user namespace\n");
56     fpe("-v          Display verbose messages\n");
57     fpe("\n");
58     fpe("Map strings for -M and -G consist of records of the form:\n");
59     fpe("\n");
60     fpe("    ID-inside-ns   ID-outside-ns   len\n");
61     fpe("\n");
62     fpe("A map string can contain multiple records, separated by commas;\n");
63     fpe("the commas are replaced by newlines before writing to map files.\n");
64
65     exit(EXIT_FAILURE);
66 }
67
68 /* Update the mapping file 'map_file', with the value provided in
69    'mapping', a string that defines a UID or GID mapping. A UID or
70    GID mapping consists of one or more newline-delimited records
71    of the form:
72
73        ID_inside-ns    ID-outside-ns   length
74
75    Requiring the user to supply a string that contains newlines is
76    of course inconvenient for command-line use. Thus, we permit the
77    use of commas to delimit records in this string, and replace them
78    with newlines before writing the string to the file. */
79
80 static void
81 update_map(char *mapping, char *map_file)
82 {
83     int fd, j;
84     size_t map_len;     /* Length of 'mapping' */
85
86     /* Replace commas in mapping string with newlines */
87
88     map_len = strlen(mapping);
89     for (j = 0; j < map_len; j++)
90         if (mapping[j] == ',')
91             mapping[j] = '\n';
92
93     fd = open(map_file, O_RDWR);
94     if (fd == -1) {
95         fprintf(stderr, "open %s: %s\n", map_file, strerror(errno));
96         exit(EXIT_FAILURE);
97     }
98
99     if (write(fd, mapping, map_len) != map_len) {
100         fprintf(stderr, "write %s: %s\n", map_file, strerror(errno));
101         exit(EXIT_FAILURE);
102     }
103
104     close(fd);
105 }
106
107 static int              /* Start function for cloned child */
108 childFunc(void *arg)
109 {
110     struct child_args *args = (struct child_args *) arg;
111     char ch;
112
113     /* Wait until the parent has updated the UID and GID mappings. See
114        the comment in main(). We wait for end of file on a pipe that will
115        be closed by the parent process once it has updated the mappings. */
116
117     close(args->pipe_fd[1]);    /* Close our descriptor for the write end
118                                    of the pipe so that we see EOF when
119                                    parent closes its descriptor */
120     if (read(args->pipe_fd[0], &ch, 1) != 0) {
121         fprintf(stderr, "Failure in child: read from pipe returned != 0\n");
122         exit(EXIT_FAILURE);
123     }
124
125     if (setid) {
126     if (setgid(0) < 0)
127         fprintf(stderr, "Failure in child to setgid 0: %s\n", strerror(errno));
128     if (setuid(0) < 0)
129         fprintf(stderr, "Failure in child to setuid 0: %s\n", strerror(errno));
130     }
131
132     /* Execute a shell command */
133
134     execvp(args->argv[0], args->argv);
135     errExit("execvp");
136 }
137
138 #define STACK_SIZE (1024 * 1024)
139
140 /* Space for child's stack */
141 static char child_stack[STACK_SIZE] __attribute__((aligned));
142
143 int
144 main(int argc, char *argv[])
145 {
146     int flags, opt;
147     pid_t child_pid;
148     struct child_args args;
149     char *uid_map, *gid_map;
150     char map_path[PATH_MAX];
151
152     /* Parse command-line options. The initial '+' character in
153        the final getopt() argument prevents GNU-style permutation
154        of command-line options. That's useful, since sometimes
155        the 'command' to be executed by this program itself
156        has command-line options. We don't want getopt() to treat
157        those as options to this program. */
158
159     flags = 0;
160     verbose = 0;
161     setid = 0;
162     gid_map = NULL;
163     uid_map = NULL;
164     while ((opt = getopt(argc, argv, "+imnpuUM:G:vs")) != -1) {
165         switch (opt) {
166         case 'i': flags |= CLONE_NEWIPC;        break;
167         case 'm': flags |= CLONE_NEWNS;         break;
168         case 'n': flags |= CLONE_NEWNET;        break;
169         case 'p': flags |= CLONE_NEWPID;        break;
170         case 'u': flags |= CLONE_NEWUTS;        break;
171         case 'v': verbose = 1;                  break;
172         case 'M': uid_map = optarg;             break;
173         case 'G': gid_map = optarg;             break;
174         case 'U': flags |= CLONE_NEWUSER;       break;
175         case 's': setid = 1;                    break;
176         default:  usage(argv[0]);
177         }
178     }
179
180     /* -M or -G without -U is nonsensical */
181
182     if ((uid_map != NULL || gid_map != NULL) &&
183             !(flags & CLONE_NEWUSER))
184         usage(argv[0]);
185
186     args.argv = &argv[optind];
187
188     /* We use a pipe to synchronize the parent and child, in order to
189        ensure that the parent sets the UID and GID maps before the child
190        calls execve(). This ensures that the child maintains its
191        capabilities during the execve() in the common case where we
192        want to map the child's effective user ID to 0 in the new user
193        namespace. Without this synchronization, the child would lose
194        its capabilities if it performed an execve() with nonzero
195        user IDs (see the capabilities(7) man page for details of the
196        transformation of a process's capabilities during execve()). */
197
198     if (pipe(args.pipe_fd) == -1)
199         errExit("pipe");
200
201     /* Create the child in new namespace(s) */
202
203     child_pid = clone(childFunc, child_stack + STACK_SIZE,
204                       flags | SIGCHLD, &args);
205     if (child_pid == -1)
206         errExit("clone");
207
208     /* Parent falls through to here */
209
210     if (verbose)
211         printf("%s: PID of child created by clone() is %ld\n",
212                 argv[0], (long) child_pid);
213
214     /* Update the UID and GID maps in the child */
215
216     if (uid_map != NULL) {
217         snprintf(map_path, PATH_MAX, "/proc/%ld/uid_map",
218                 (long) child_pid);
219         update_map(uid_map, map_path);
220     }
221     if (gid_map != NULL) {
222         snprintf(map_path, PATH_MAX, "/proc/%ld/gid_map",
223                 (long) child_pid);
224         update_map(gid_map, map_path);
225     }
226
227     /* Close the write end of the pipe, to signal to the child that we
228        have updated the UID and GID maps */
229
230     close(args.pipe_fd[1]);
231
232     if (waitpid(child_pid, NULL, 0) == -1)      /* Wait for child */
233         errExit("waitpid");
234
235     if (verbose)
236         printf("%s: terminating\n", argv[0]);
237
238     exit(EXIT_SUCCESS);
239 }