]>
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 | ||
d155b47d SH |
56 | int unshare(int flags); |
57 | ||
58 | static void usage(const char *name) | |
59 | { | |
adade80c | 60 | printf("usage: %s [-h] [-m <uid-maps>] -- [command [arg ..]]\n", name); |
d155b47d | 61 | printf("\n"); |
6f94152d | 62 | printf(" -h this message\n"); |
d155b47d SH |
63 | printf("\n"); |
64 | printf(" -m <uid-maps> uid maps to use\n"); | |
65 | printf("\n"); | |
66 | printf(" uid-maps: [u|g|b]:ns_id:host_id:range\n"); | |
67 | printf(" [u|g|b]: map user id, group id, or both\n"); | |
68 | printf(" ns_id: the base id in the new namespace\n"); | |
69 | printf(" host_id: the base id in the parent namespace\n"); | |
70 | printf(" range: how many ids to map\n"); | |
71 | printf(" Note: This program uses newuidmap(2) and newgidmap(2).\n"); | |
72 | printf(" As such, /etc/subuid and /etc/subgid must grant the\n"); | |
73 | printf(" calling user permission to use the mapped ranges\n"); | |
f0c6ee28 | 74 | exit(EXIT_SUCCESS); |
d155b47d SH |
75 | } |
76 | ||
b5f4bc78 SH |
77 | static void opentty(const char * tty, int which) { |
78 | int fd, flags; | |
79 | ||
80 | if (tty[0] == '\0') | |
81 | return; | |
d155b47d SH |
82 | |
83 | fd = open(tty, O_RDWR | O_NONBLOCK); | |
84 | if (fd == -1) { | |
35e3a0cd | 85 | printf("WARN: could not reopen tty: %s\n", strerror(errno)); |
8d4b877a | 86 | return; |
d155b47d SH |
87 | } |
88 | ||
89 | flags = fcntl(fd, F_GETFL); | |
90 | flags &= ~O_NONBLOCK; | |
35e3a0cd SG |
91 | if (fcntl(fd, F_SETFL, flags) < 0) { |
92 | printf("WARN: could not set fd flags: %s\n", strerror(errno)); | |
93 | return; | |
94 | } | |
d155b47d | 95 | |
b5f4bc78 SH |
96 | close(which); |
97 | if (fd != which) { | |
98 | dup2(fd, which); | |
d155b47d | 99 | close(fd); |
b5f4bc78 | 100 | } |
d155b47d SH |
101 | } |
102 | // Code copy end | |
103 | ||
104 | static int do_child(void *vargv) | |
105 | { | |
106 | char **argv = (char **)vargv; | |
107 | ||
108 | // Assume we want to become root | |
109 | if (setgid(0) < 0) { | |
110 | perror("setgid"); | |
111 | return -1; | |
112 | } | |
113 | if (setuid(0) < 0) { | |
114 | perror("setuid"); | |
115 | return -1; | |
116 | } | |
117 | if (setgroups(0, NULL) < 0) { | |
118 | perror("setgroups"); | |
119 | return -1; | |
120 | } | |
121 | if (unshare(CLONE_NEWNS) < 0) { | |
122 | perror("unshare CLONE_NEWNS"); | |
123 | return -1; | |
124 | } | |
2c6f3fc9 SH |
125 | if (detect_shared_rootfs()) { |
126 | if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL)) { | |
127 | printf("Failed to make / rslave"); | |
128 | return -1; | |
129 | } | |
130 | } | |
d155b47d SH |
131 | execvp(argv[0], argv); |
132 | perror("execvpe"); | |
133 | return -1; | |
134 | } | |
135 | ||
0e6e3a41 | 136 | static struct lxc_list active_map; |
d155b47d SH |
137 | |
138 | /* | |
139 | * given a string like "b:0:100000:10", map both uids and gids | |
140 | * 0-10 to 100000 to 100010 | |
141 | */ | |
142 | static int parse_map(char *map) | |
143 | { | |
144 | struct id_map *newmap; | |
0e6e3a41 SG |
145 | struct lxc_list *tmp = NULL; |
146 | int ret; | |
147 | int i; | |
148 | char types[2] = {'u', 'g'}; | |
149 | char which; | |
150 | long host_id, ns_id, range; | |
d155b47d SH |
151 | |
152 | if (!map) | |
153 | return -1; | |
0e6e3a41 SG |
154 | |
155 | ret = sscanf(map, "%c:%ld:%ld:%ld", &which, &ns_id, &host_id, &range); | |
d155b47d | 156 | if (ret != 4) |
0e6e3a41 | 157 | return -1; |
d155b47d | 158 | |
0e6e3a41 SG |
159 | if (which != 'b' && which != 'u' && which != 'g') |
160 | return -1; | |
161 | ||
162 | for (i = 0; i < 2; i++) { | |
163 | if (which != types[i] && which != 'b') | |
164 | continue; | |
165 | ||
166 | newmap = malloc(sizeof(*newmap)); | |
167 | if (!newmap) | |
168 | return -1; | |
169 | ||
170 | newmap->hostid = host_id; | |
171 | newmap->nsid = ns_id; | |
172 | newmap->range = range; | |
173 | ||
174 | if (types[i] == 'u') | |
175 | newmap->idtype = ID_TYPE_UID; | |
176 | else | |
177 | newmap->idtype = ID_TYPE_GID; | |
178 | ||
179 | tmp = malloc(sizeof(*tmp)); | |
180 | if (!tmp) { | |
181 | free(newmap); | |
182 | return -1; | |
183 | } | |
184 | ||
185 | tmp->elem = newmap; | |
186 | lxc_list_add_tail(&active_map, tmp); | |
187 | } | |
188 | ||
189 | return 0; | |
d155b47d SH |
190 | } |
191 | ||
192 | /* | |
c7e426be SH |
193 | * This is called if the user did not pass any uid ranges in |
194 | * through -m flags. It's called once to get the default uid | |
195 | * map, and once for the default gid map. | |
196 | * Go through /etc/subuids and /etc/subgids to find this user's | |
197 | * allowed map. We only use the first one for each of uid and | |
198 | * gid, because otherwise we're not sure which entries the user | |
199 | * wanted. | |
d155b47d | 200 | */ |
0e6e3a41 | 201 | static int read_default_map(char *fnam, int which, char *username) |
d155b47d SH |
202 | { |
203 | FILE *fin; | |
204 | char *line = NULL; | |
205 | size_t sz = 0; | |
206 | struct id_map *newmap; | |
0e6e3a41 | 207 | struct lxc_list *tmp = NULL; |
c7e426be | 208 | char *p1, *p2; |
d155b47d SH |
209 | |
210 | fin = fopen(fnam, "r"); | |
211 | if (!fin) | |
212 | return -1; | |
213 | while (getline(&line, &sz, fin) != -1) { | |
214 | if (sz <= strlen(username) || | |
215 | strncmp(line, username, strlen(username)) != 0 || | |
216 | line[strlen(username)] != ':') | |
217 | continue; | |
46cd2845 | 218 | p1 = strchr(line, ':'); |
d155b47d SH |
219 | if (!p1) |
220 | continue; | |
46cd2845 | 221 | p2 = strchr(p1+1, ':'); |
d155b47d SH |
222 | if (!p2) |
223 | continue; | |
224 | newmap = malloc(sizeof(*newmap)); | |
c7e426be SH |
225 | if (!newmap) { |
226 | fclose(fin); | |
227 | free(line); | |
d155b47d | 228 | return -1; |
c7e426be | 229 | } |
0e6e3a41 | 230 | newmap->hostid = atol(p1+1); |
d155b47d | 231 | newmap->range = atol(p2+1); |
0e6e3a41 SG |
232 | newmap->nsid = 0; |
233 | newmap->idtype = which; | |
234 | ||
235 | tmp = malloc(sizeof(*tmp)); | |
236 | if (!tmp) { | |
237 | fclose(fin); | |
238 | free(line); | |
239 | free(newmap); | |
240 | return -1; | |
241 | } | |
242 | ||
243 | tmp->elem = newmap; | |
244 | lxc_list_add_tail(&active_map, tmp); | |
d155b47d SH |
245 | break; |
246 | } | |
247 | ||
f10fad2f | 248 | free(line); |
d155b47d SH |
249 | fclose(fin); |
250 | return 0; | |
251 | } | |
252 | ||
d155b47d SH |
253 | static int find_default_map(void) |
254 | { | |
255 | struct passwd *p = getpwuid(getuid()); | |
256 | if (!p) | |
257 | return -1; | |
0e6e3a41 | 258 | if (read_default_map(subuidfile, ID_TYPE_UID, p->pw_name) < 0) |
d155b47d | 259 | return -1; |
0e6e3a41 | 260 | if (read_default_map(subgidfile, ID_TYPE_GID, p->pw_name) < 0) |
d155b47d SH |
261 | return -1; |
262 | return 0; | |
263 | } | |
264 | ||
d155b47d SH |
265 | int main(int argc, char *argv[]) |
266 | { | |
267 | int c; | |
268 | unsigned long flags = CLONE_NEWUSER | CLONE_NEWNS; | |
b5f4bc78 | 269 | char ttyname0[256], ttyname1[256], ttyname2[256]; |
d155b47d SH |
270 | int status; |
271 | int ret; | |
272 | int pid; | |
273 | char *default_args[] = {"/bin/sh", NULL}; | |
cbaed76d | 274 | char buf[1]; |
d155b47d SH |
275 | int pipe1[2], // child tells parent it has unshared |
276 | pipe2[2]; // parent tells child it is mapped and may proceed | |
277 | ||
b5f4bc78 SH |
278 | memset(ttyname0, '\0', sizeof(ttyname0)); |
279 | memset(ttyname1, '\0', sizeof(ttyname1)); | |
280 | memset(ttyname2, '\0', sizeof(ttyname2)); | |
5d1df05b SH |
281 | if (isatty(0)) { |
282 | ret = readlink("/proc/self/fd/0", ttyname0, sizeof(ttyname0)); | |
283 | if (ret < 0) { | |
284 | perror("unable to open stdin."); | |
b52b0595 | 285 | exit(EXIT_FAILURE); |
5d1df05b SH |
286 | } |
287 | ret = readlink("/proc/self/fd/1", ttyname1, sizeof(ttyname1)); | |
288 | if (ret < 0) { | |
289 | printf("Warning: unable to open stdout, continuing."); | |
290 | memset(ttyname1, '\0', sizeof(ttyname1)); | |
291 | } | |
292 | ret = readlink("/proc/self/fd/2", ttyname2, sizeof(ttyname2)); | |
293 | if (ret < 0) { | |
5de4efe3 | 294 | printf("Warning: unable to open stderr, continuing."); |
5d1df05b SH |
295 | memset(ttyname2, '\0', sizeof(ttyname2)); |
296 | } | |
b5f4bc78 | 297 | } |
d155b47d | 298 | |
0e6e3a41 SG |
299 | lxc_list_init(&active_map); |
300 | ||
d155b47d SH |
301 | while ((c = getopt(argc, argv, "m:h")) != EOF) { |
302 | switch (c) { | |
303 | case 'm': if (parse_map(optarg)) usage(argv[0]); break; | |
304 | case 'h': | |
305 | default: | |
306 | usage(argv[0]); | |
307 | } | |
308 | }; | |
309 | ||
0e6e3a41 | 310 | if (lxc_list_empty(&active_map)) { |
d155b47d SH |
311 | if (find_default_map()) { |
312 | fprintf(stderr, "You have no allocated subuids or subgids\n"); | |
b52b0595 | 313 | exit(EXIT_FAILURE); |
d155b47d SH |
314 | } |
315 | } | |
316 | ||
317 | argv = &argv[optind]; | |
318 | argc = argc - optind; | |
319 | if (argc < 1) { | |
320 | argv = default_args; | |
321 | argc = 1; | |
322 | } | |
323 | ||
324 | if (pipe(pipe1) < 0 || pipe(pipe2) < 0) { | |
325 | perror("pipe"); | |
b52b0595 | 326 | exit(EXIT_FAILURE); |
d155b47d SH |
327 | } |
328 | if ((pid = fork()) == 0) { | |
329 | // Child. | |
330 | ||
331 | close(pipe1[0]); | |
332 | close(pipe2[1]); | |
b5f4bc78 SH |
333 | opentty(ttyname0, 0); |
334 | opentty(ttyname1, 1); | |
335 | opentty(ttyname2, 2); | |
d155b47d SH |
336 | |
337 | ret = unshare(flags); | |
338 | if (ret < 0) { | |
339 | perror("unshare"); | |
340 | return 1; | |
341 | } | |
cbaed76d SH |
342 | buf[0] = '1'; |
343 | if (write(pipe1[1], buf, 1) < 1) { | |
d155b47d | 344 | perror("write pipe"); |
b52b0595 | 345 | exit(EXIT_FAILURE); |
d155b47d | 346 | } |
cbaed76d | 347 | if (read(pipe2[0], buf, 1) < 1) { |
d155b47d | 348 | perror("read pipe"); |
b52b0595 | 349 | exit(EXIT_FAILURE); |
d155b47d | 350 | } |
cbaed76d | 351 | if (buf[0] != '1') { |
d155b47d | 352 | fprintf(stderr, "parent had an error, child exiting\n"); |
b52b0595 | 353 | exit(EXIT_FAILURE); |
d155b47d SH |
354 | } |
355 | ||
356 | close(pipe1[1]); | |
357 | close(pipe2[0]); | |
358 | return do_child((void*)argv); | |
359 | } | |
360 | ||
361 | close(pipe1[1]); | |
362 | close(pipe2[0]); | |
cbaed76d | 363 | if (read(pipe1[0], buf, 1) < 1) { |
d155b47d | 364 | perror("read pipe"); |
b52b0595 | 365 | exit(EXIT_FAILURE); |
d155b47d SH |
366 | } |
367 | ||
b543ce96 | 368 | buf[0] = '1'; |
0e6e3a41 SG |
369 | |
370 | if (lxc_map_ids(&active_map, pid)) { | |
d155b47d SH |
371 | fprintf(stderr, "error mapping child\n"); |
372 | ret = 0; | |
373 | } | |
cbaed76d | 374 | if (write(pipe2[1], buf, 1) < 0) { |
d155b47d | 375 | perror("write to pipe"); |
b52b0595 | 376 | exit(EXIT_FAILURE); |
d155b47d SH |
377 | } |
378 | ||
379 | if ((ret = waitpid(pid, &status, __WALL)) < 0) { | |
380 | printf("waitpid() returns %d, errno %d\n", ret, errno); | |
b52b0595 | 381 | exit(EXIT_FAILURE); |
d155b47d SH |
382 | } |
383 | ||
384 | exit(WEXITSTATUS(status)); | |
385 | } |