]>
Commit | Line | Data |
---|---|---|
d155b47d SH |
1 | /* |
2 | * (C) Copyright IBM Corp. 2008 | |
3 | * (C) Copyright Canonical, Inc 2010-2013 | |
4 | * | |
5 | * Authors: | |
6 | * Serge Hallyn <serge.hallyn@ubuntu.com> | |
7 | * (Once upon a time, this was based on nsexec from the IBM | |
8 | * container tools) | |
9 | * | |
10 | * This library is free software; you can redistribute it and/or | |
11 | * modify it under the terms of the GNU Lesser General Public | |
12 | * License as published by the Free Software Foundation; either | |
13 | * version 2.1 of the License, or (at your option) any later version. | |
14 | * | |
15 | * This library is distributed in the hope that it will be useful, | |
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
18 | * Lesser General Public License for more details. | |
19 | * | |
20 | * You should have received a copy of the GNU Lesser General Public | |
21 | * License along with this library; if not, write to the Free Software | |
250b1eec | 22 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
d155b47d | 23 | */ |
7a435f9a | 24 | #include "config.h" |
d155b47d SH |
25 | |
26 | #include <stdio.h> | |
27 | #include <stdlib.h> | |
28 | #include <unistd.h> | |
29 | #include <sched.h> | |
30 | #include <sys/syscall.h> | |
31 | #include <signal.h> | |
32 | #include <string.h> | |
33 | #include <errno.h> | |
34 | #include <libgen.h> | |
35 | #include <fcntl.h> | |
36 | #include <sys/stat.h> | |
37 | #include <sys/types.h> | |
2c6f3fc9 | 38 | #include <sys/mount.h> |
d155b47d SH |
39 | #include <sys/wait.h> |
40 | #include <sched.h> | |
41 | #include <pwd.h> | |
42 | #include <grp.h> | |
f2363e38 | 43 | |
0e6e3a41 | 44 | #include "conf.h" |
d155b47d | 45 | #include "namespace.h" |
7ee37fac | 46 | #include "utils.h" |
d155b47d | 47 | |
d6a3c917 SG |
48 | #ifndef MS_REC |
49 | #define MS_REC 16384 | |
50 | #endif | |
51 | ||
52 | #ifndef MS_SLAVE | |
eab15c1e | 53 | #define MS_SLAVE (1 << 19) |
d6a3c917 SG |
54 | #endif |
55 | ||
2d22b22d TA |
56 | extern int lxc_log_fd; |
57 | ||
d155b47d SH |
58 | int unshare(int flags); |
59 | ||
60 | static void usage(const char *name) | |
61 | { | |
adade80c | 62 | printf("usage: %s [-h] [-m <uid-maps>] -- [command [arg ..]]\n", name); |
d155b47d | 63 | printf("\n"); |
6f94152d | 64 | printf(" -h this message\n"); |
d155b47d SH |
65 | printf("\n"); |
66 | printf(" -m <uid-maps> uid maps to use\n"); | |
67 | printf("\n"); | |
68 | printf(" uid-maps: [u|g|b]:ns_id:host_id:range\n"); | |
69 | printf(" [u|g|b]: map user id, group id, or both\n"); | |
70 | printf(" ns_id: the base id in the new namespace\n"); | |
71 | printf(" host_id: the base id in the parent namespace\n"); | |
72 | printf(" range: how many ids to map\n"); | |
73 | printf(" Note: This program uses newuidmap(2) and newgidmap(2).\n"); | |
74 | printf(" As such, /etc/subuid and /etc/subgid must grant the\n"); | |
75 | printf(" calling user permission to use the mapped ranges\n"); | |
d155b47d SH |
76 | } |
77 | ||
b5f4bc78 SH |
78 | static void opentty(const char * tty, int which) { |
79 | int fd, flags; | |
80 | ||
81 | if (tty[0] == '\0') | |
82 | return; | |
d155b47d SH |
83 | |
84 | fd = open(tty, O_RDWR | O_NONBLOCK); | |
85 | if (fd == -1) { | |
35e3a0cd | 86 | printf("WARN: could not reopen tty: %s\n", strerror(errno)); |
8d4b877a | 87 | return; |
d155b47d SH |
88 | } |
89 | ||
90 | flags = fcntl(fd, F_GETFL); | |
91 | flags &= ~O_NONBLOCK; | |
35e3a0cd SG |
92 | if (fcntl(fd, F_SETFL, flags) < 0) { |
93 | printf("WARN: could not set fd flags: %s\n", strerror(errno)); | |
94 | return; | |
95 | } | |
d155b47d | 96 | |
b5f4bc78 SH |
97 | close(which); |
98 | if (fd != which) { | |
99 | dup2(fd, which); | |
d155b47d | 100 | close(fd); |
b5f4bc78 | 101 | } |
d155b47d | 102 | } |
1a0e70ac | 103 | /* Code copy end */ |
d155b47d SH |
104 | |
105 | static int do_child(void *vargv) | |
106 | { | |
107 | char **argv = (char **)vargv; | |
108 | ||
1a0e70ac | 109 | /* Assume we want to become root */ |
d155b47d SH |
110 | if (setgid(0) < 0) { |
111 | perror("setgid"); | |
112 | return -1; | |
113 | } | |
114 | if (setuid(0) < 0) { | |
115 | perror("setuid"); | |
116 | return -1; | |
117 | } | |
118 | if (setgroups(0, NULL) < 0) { | |
119 | perror("setgroups"); | |
120 | return -1; | |
121 | } | |
122 | if (unshare(CLONE_NEWNS) < 0) { | |
123 | perror("unshare CLONE_NEWNS"); | |
124 | return -1; | |
125 | } | |
2c6f3fc9 SH |
126 | if (detect_shared_rootfs()) { |
127 | if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL)) { | |
3eaff0ba | 128 | printf("Failed to make / rslave\n"); |
2c6f3fc9 SH |
129 | return -1; |
130 | } | |
131 | } | |
d155b47d SH |
132 | execvp(argv[0], argv); |
133 | perror("execvpe"); | |
134 | return -1; | |
135 | } | |
136 | ||
0e6e3a41 | 137 | static struct lxc_list active_map; |
d155b47d SH |
138 | |
139 | /* | |
140 | * given a string like "b:0:100000:10", map both uids and gids | |
141 | * 0-10 to 100000 to 100010 | |
142 | */ | |
143 | static int parse_map(char *map) | |
144 | { | |
145 | struct id_map *newmap; | |
0e6e3a41 SG |
146 | struct lxc_list *tmp = NULL; |
147 | int ret; | |
148 | int i; | |
149 | char types[2] = {'u', 'g'}; | |
150 | char which; | |
151 | long host_id, ns_id, range; | |
d155b47d SH |
152 | |
153 | if (!map) | |
154 | return -1; | |
0e6e3a41 SG |
155 | |
156 | ret = sscanf(map, "%c:%ld:%ld:%ld", &which, &ns_id, &host_id, &range); | |
d155b47d | 157 | if (ret != 4) |
0e6e3a41 | 158 | return -1; |
d155b47d | 159 | |
0e6e3a41 SG |
160 | if (which != 'b' && which != 'u' && which != 'g') |
161 | return -1; | |
162 | ||
163 | for (i = 0; i < 2; i++) { | |
164 | if (which != types[i] && which != 'b') | |
165 | continue; | |
166 | ||
167 | newmap = malloc(sizeof(*newmap)); | |
168 | if (!newmap) | |
169 | return -1; | |
170 | ||
171 | newmap->hostid = host_id; | |
172 | newmap->nsid = ns_id; | |
173 | newmap->range = range; | |
174 | ||
175 | if (types[i] == 'u') | |
176 | newmap->idtype = ID_TYPE_UID; | |
177 | else | |
178 | newmap->idtype = ID_TYPE_GID; | |
179 | ||
180 | tmp = malloc(sizeof(*tmp)); | |
181 | if (!tmp) { | |
182 | free(newmap); | |
183 | return -1; | |
184 | } | |
185 | ||
186 | tmp->elem = newmap; | |
187 | lxc_list_add_tail(&active_map, tmp); | |
188 | } | |
189 | ||
190 | return 0; | |
d155b47d SH |
191 | } |
192 | ||
193 | /* | |
c7e426be SH |
194 | * This is called if the user did not pass any uid ranges in |
195 | * through -m flags. It's called once to get the default uid | |
196 | * map, and once for the default gid map. | |
197 | * Go through /etc/subuids and /etc/subgids to find this user's | |
198 | * allowed map. We only use the first one for each of uid and | |
199 | * gid, because otherwise we're not sure which entries the user | |
200 | * wanted. | |
d155b47d | 201 | */ |
0e6e3a41 | 202 | static int read_default_map(char *fnam, int which, char *username) |
d155b47d SH |
203 | { |
204 | FILE *fin; | |
205 | char *line = NULL; | |
206 | size_t sz = 0; | |
207 | struct id_map *newmap; | |
0e6e3a41 | 208 | struct lxc_list *tmp = NULL; |
c7e426be | 209 | char *p1, *p2; |
d155b47d SH |
210 | |
211 | fin = fopen(fnam, "r"); | |
212 | if (!fin) | |
213 | return -1; | |
214 | while (getline(&line, &sz, fin) != -1) { | |
215 | if (sz <= strlen(username) || | |
216 | strncmp(line, username, strlen(username)) != 0 || | |
217 | line[strlen(username)] != ':') | |
218 | continue; | |
46cd2845 | 219 | p1 = strchr(line, ':'); |
d155b47d SH |
220 | if (!p1) |
221 | continue; | |
46cd2845 | 222 | p2 = strchr(p1+1, ':'); |
d155b47d SH |
223 | if (!p2) |
224 | continue; | |
225 | newmap = malloc(sizeof(*newmap)); | |
c7e426be SH |
226 | if (!newmap) { |
227 | fclose(fin); | |
228 | free(line); | |
d155b47d | 229 | return -1; |
c7e426be | 230 | } |
0e6e3a41 | 231 | newmap->hostid = atol(p1+1); |
d155b47d | 232 | newmap->range = atol(p2+1); |
0e6e3a41 SG |
233 | newmap->nsid = 0; |
234 | newmap->idtype = which; | |
235 | ||
236 | tmp = malloc(sizeof(*tmp)); | |
237 | if (!tmp) { | |
238 | fclose(fin); | |
239 | free(line); | |
240 | free(newmap); | |
241 | return -1; | |
242 | } | |
243 | ||
244 | tmp->elem = newmap; | |
245 | lxc_list_add_tail(&active_map, tmp); | |
d155b47d SH |
246 | break; |
247 | } | |
248 | ||
f10fad2f | 249 | free(line); |
d155b47d SH |
250 | fclose(fin); |
251 | return 0; | |
252 | } | |
253 | ||
d155b47d SH |
254 | static int find_default_map(void) |
255 | { | |
cb7aa5e8 DJ |
256 | struct passwd pwent; |
257 | struct passwd *pwentp = NULL; | |
258 | char *buf; | |
259 | size_t bufsize; | |
260 | int ret; | |
261 | ||
262 | bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); | |
263 | if (bufsize == -1) | |
264 | bufsize = 1024; | |
265 | ||
266 | buf = malloc(bufsize); | |
267 | if (!buf) | |
d155b47d | 268 | return -1; |
cb7aa5e8 DJ |
269 | |
270 | ret = getpwuid_r(getuid(), &pwent, buf, bufsize, &pwentp); | |
271 | if (!pwentp) { | |
272 | if (ret == 0) | |
273 | printf("WARN: could not find matched password record\n"); | |
274 | ||
275 | printf("Failed to get password record - %u\n", getuid()); | |
276 | free(buf); | |
d155b47d | 277 | return -1; |
cb7aa5e8 DJ |
278 | } |
279 | ||
280 | if (read_default_map(subuidfile, ID_TYPE_UID, pwent.pw_name) < 0) { | |
281 | free(buf); | |
d155b47d | 282 | return -1; |
cb7aa5e8 DJ |
283 | } |
284 | ||
285 | if (read_default_map(subgidfile, ID_TYPE_GID, pwent.pw_name) < 0) { | |
286 | free(buf); | |
287 | return -1; | |
288 | } | |
289 | ||
290 | free(buf); | |
291 | return 0; | |
d155b47d SH |
292 | } |
293 | ||
d155b47d SH |
294 | int main(int argc, char *argv[]) |
295 | { | |
296 | int c; | |
297 | unsigned long flags = CLONE_NEWUSER | CLONE_NEWNS; | |
b5f4bc78 | 298 | char ttyname0[256], ttyname1[256], ttyname2[256]; |
d155b47d SH |
299 | int status; |
300 | int ret; | |
301 | int pid; | |
302 | char *default_args[] = {"/bin/sh", NULL}; | |
cbaed76d | 303 | char buf[1]; |
1a0e70ac CB |
304 | int pipe1[2], /* child tells parent it has unshared */ |
305 | pipe2[2]; /* parent tells child it is mapped and may proceed */ | |
d155b47d | 306 | |
2d22b22d TA |
307 | lxc_log_fd = STDERR_FILENO; |
308 | ||
b5f4bc78 SH |
309 | memset(ttyname0, '\0', sizeof(ttyname0)); |
310 | memset(ttyname1, '\0', sizeof(ttyname1)); | |
311 | memset(ttyname2, '\0', sizeof(ttyname2)); | |
5d1df05b SH |
312 | if (isatty(0)) { |
313 | ret = readlink("/proc/self/fd/0", ttyname0, sizeof(ttyname0)); | |
314 | if (ret < 0) { | |
315 | perror("unable to open stdin."); | |
b52b0595 | 316 | exit(EXIT_FAILURE); |
5d1df05b SH |
317 | } |
318 | ret = readlink("/proc/self/fd/1", ttyname1, sizeof(ttyname1)); | |
319 | if (ret < 0) { | |
3eaff0ba | 320 | printf("Warning: unable to open stdout, continuing.\n"); |
5d1df05b SH |
321 | memset(ttyname1, '\0', sizeof(ttyname1)); |
322 | } | |
323 | ret = readlink("/proc/self/fd/2", ttyname2, sizeof(ttyname2)); | |
324 | if (ret < 0) { | |
3eaff0ba | 325 | printf("Warning: unable to open stderr, continuing.\n"); |
5d1df05b SH |
326 | memset(ttyname2, '\0', sizeof(ttyname2)); |
327 | } | |
b5f4bc78 | 328 | } |
d155b47d | 329 | |
0e6e3a41 SG |
330 | lxc_list_init(&active_map); |
331 | ||
d155b47d SH |
332 | while ((c = getopt(argc, argv, "m:h")) != EOF) { |
333 | switch (c) { | |
d8208db4 TA |
334 | case 'm': |
335 | if (parse_map(optarg)) { | |
336 | usage(argv[0]); | |
337 | exit(EXIT_FAILURE); | |
338 | } | |
339 | break; | |
d155b47d | 340 | case 'h': |
d8208db4 TA |
341 | usage(argv[0]); |
342 | exit(EXIT_SUCCESS); | |
d155b47d SH |
343 | default: |
344 | usage(argv[0]); | |
d8208db4 | 345 | exit(EXIT_FAILURE); |
d155b47d SH |
346 | } |
347 | }; | |
348 | ||
0e6e3a41 | 349 | if (lxc_list_empty(&active_map)) { |
d155b47d SH |
350 | if (find_default_map()) { |
351 | fprintf(stderr, "You have no allocated subuids or subgids\n"); | |
b52b0595 | 352 | exit(EXIT_FAILURE); |
d155b47d SH |
353 | } |
354 | } | |
355 | ||
356 | argv = &argv[optind]; | |
357 | argc = argc - optind; | |
1285f7d5 | 358 | if (argc < 1) |
d155b47d | 359 | argv = default_args; |
d155b47d SH |
360 | |
361 | if (pipe(pipe1) < 0 || pipe(pipe2) < 0) { | |
362 | perror("pipe"); | |
b52b0595 | 363 | exit(EXIT_FAILURE); |
d155b47d SH |
364 | } |
365 | if ((pid = fork()) == 0) { | |
1a0e70ac | 366 | /* Child. */ |
d155b47d SH |
367 | |
368 | close(pipe1[0]); | |
369 | close(pipe2[1]); | |
b5f4bc78 SH |
370 | opentty(ttyname0, 0); |
371 | opentty(ttyname1, 1); | |
372 | opentty(ttyname2, 2); | |
d155b47d SH |
373 | |
374 | ret = unshare(flags); | |
375 | if (ret < 0) { | |
376 | perror("unshare"); | |
377 | return 1; | |
378 | } | |
cbaed76d SH |
379 | buf[0] = '1'; |
380 | if (write(pipe1[1], buf, 1) < 1) { | |
d155b47d | 381 | perror("write pipe"); |
b52b0595 | 382 | exit(EXIT_FAILURE); |
d155b47d | 383 | } |
cbaed76d | 384 | if (read(pipe2[0], buf, 1) < 1) { |
d155b47d | 385 | perror("read pipe"); |
b52b0595 | 386 | exit(EXIT_FAILURE); |
d155b47d | 387 | } |
cbaed76d | 388 | if (buf[0] != '1') { |
d155b47d | 389 | fprintf(stderr, "parent had an error, child exiting\n"); |
b52b0595 | 390 | exit(EXIT_FAILURE); |
d155b47d SH |
391 | } |
392 | ||
393 | close(pipe1[1]); | |
394 | close(pipe2[0]); | |
395 | return do_child((void*)argv); | |
396 | } | |
397 | ||
398 | close(pipe1[1]); | |
399 | close(pipe2[0]); | |
cbaed76d | 400 | if (read(pipe1[0], buf, 1) < 1) { |
d155b47d | 401 | perror("read pipe"); |
b52b0595 | 402 | exit(EXIT_FAILURE); |
d155b47d SH |
403 | } |
404 | ||
b543ce96 | 405 | buf[0] = '1'; |
0e6e3a41 | 406 | |
1285f7d5 | 407 | if (lxc_map_ids(&active_map, pid)) |
d155b47d | 408 | fprintf(stderr, "error mapping child\n"); |
1285f7d5 | 409 | |
cbaed76d | 410 | if (write(pipe2[1], buf, 1) < 0) { |
d155b47d | 411 | perror("write to pipe"); |
b52b0595 | 412 | exit(EXIT_FAILURE); |
d155b47d SH |
413 | } |
414 | ||
415 | if ((ret = waitpid(pid, &status, __WALL)) < 0) { | |
416 | printf("waitpid() returns %d, errno %d\n", ret, errno); | |
b52b0595 | 417 | exit(EXIT_FAILURE); |
d155b47d SH |
418 | } |
419 | ||
420 | exit(WEXITSTATUS(status)); | |
421 | } |