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