]>
Commit | Line | Data |
---|---|---|
e0732705 CS |
1 | /* |
2 | * lxc: linux Container library | |
3 | * | |
4 | * (C) Copyright IBM Corp. 2007, 2008 | |
5 | * | |
6 | * Authors: | |
9afe19d6 | 7 | * Daniel Lezcano <daniel.lezcano at free.fr> |
e0732705 CS |
8 | * |
9 | * This library is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU Lesser General Public | |
11 | * License as published by the Free Software Foundation; either | |
12 | * version 2.1 of the License, or (at your option) any later version. | |
13 | * | |
14 | * This library is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
17 | * Lesser General Public License for more details. | |
18 | * | |
19 | * You should have received a copy of the GNU Lesser General Public | |
20 | * License along with this library; if not, write to the Free Software | |
21 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
22 | */ | |
23 | ||
24 | #define _GNU_SOURCE | |
25 | #include <unistd.h> | |
26 | #include <stdio.h> | |
27 | #include <string.h> | |
28 | #include <stdlib.h> | |
29 | #include <errno.h> | |
30 | #include <fcntl.h> | |
31 | #include <sys/param.h> | |
32 | #include <sys/prctl.h> | |
7a0b0b56 | 33 | #include <sys/mount.h> |
1ba0013f | 34 | #include <sys/syscall.h> |
905022f7 | 35 | #include <sys/wait.h> |
910bb4fa | 36 | #include <linux/unistd.h> |
905022f7 | 37 | #include <pwd.h> |
e0732705 CS |
38 | |
39 | #if !HAVE_DECL_PR_CAPBSET_DROP | |
40 | #define PR_CAPBSET_DROP 24 | |
41 | #endif | |
42 | ||
43 | #include "namespace.h" | |
44 | #include "log.h" | |
45 | #include "attach.h" | |
46 | #include "caps.h" | |
e0732705 | 47 | #include "config.h" |
9958532b | 48 | #include "apparmor.h" |
e0732705 | 49 | |
e0732705 CS |
50 | lxc_log_define(lxc_attach, lxc); |
51 | ||
2d76d1d7 SG |
52 | /* Define setns() if missing from the C library */ |
53 | #ifndef HAVE_SETNS | |
54 | static int setns(int fd, int nstype) | |
e0732705 | 55 | { |
2d76d1d7 SG |
56 | #ifdef __NR_setns |
57 | return syscall(__NR_setns, fd, nstype); | |
e0732705 | 58 | #else |
2d76d1d7 SG |
59 | errno = ENOSYS; |
60 | return -1; | |
e0732705 CS |
61 | #endif |
62 | } | |
2d76d1d7 SG |
63 | #endif |
64 | ||
65 | /* Define unshare() if missing from the C library */ | |
66 | #ifndef HAVE_UNSHARE | |
67 | static int unshare(int flags) | |
68 | { | |
69 | #ifdef __NR_unshare | |
70 | return syscall(__NR_unshare, flags); | |
71 | #else | |
72 | errno = ENOSYS; | |
73 | return -1; | |
74 | #endif | |
75 | } | |
76 | #endif | |
e0732705 | 77 | |
1ba0013f SG |
78 | /* Define getline() if missing from the C library */ |
79 | #ifndef HAVE_GETLINE | |
80 | #ifdef HAVE_FGETLN | |
81 | #include <../include/getline.h> | |
82 | #endif | |
83 | #endif | |
84 | ||
e0732705 CS |
85 | struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid) |
86 | { | |
87 | struct lxc_proc_context_info *info = calloc(1, sizeof(*info)); | |
88 | FILE *proc_file; | |
89 | char proc_fn[MAXPATHLEN]; | |
460a1cf0 | 90 | char *line = NULL; |
e0732705 | 91 | size_t line_bufsz = 0; |
460a1cf0 | 92 | int ret, found; |
e0732705 CS |
93 | |
94 | if (!info) { | |
95 | SYSERROR("Could not allocate memory."); | |
96 | return NULL; | |
97 | } | |
98 | ||
99 | /* read capabilities */ | |
100 | snprintf(proc_fn, MAXPATHLEN, "/proc/%d/status", pid); | |
101 | ||
102 | proc_file = fopen(proc_fn, "r"); | |
103 | if (!proc_file) { | |
104 | SYSERROR("Could not open %s", proc_fn); | |
105 | goto out_error; | |
106 | } | |
107 | ||
108 | found = 0; | |
109 | while (getline(&line, &line_bufsz, proc_file) != -1) { | |
110 | ret = sscanf(line, "CapBnd: %llx", &info->capability_mask); | |
111 | if (ret != EOF && ret > 0) { | |
112 | found = 1; | |
113 | break; | |
114 | } | |
115 | } | |
116 | ||
117 | fclose(proc_file); | |
118 | ||
119 | if (!found) { | |
120 | SYSERROR("Could not read capability bounding set from %s", proc_fn); | |
121 | errno = ENOENT; | |
122 | goto out_error; | |
123 | } | |
124 | ||
125 | /* read personality */ | |
126 | snprintf(proc_fn, MAXPATHLEN, "/proc/%d/personality", pid); | |
127 | ||
128 | proc_file = fopen(proc_fn, "r"); | |
129 | if (!proc_file) { | |
130 | SYSERROR("Could not open %s", proc_fn); | |
131 | goto out_error; | |
132 | } | |
133 | ||
134 | ret = fscanf(proc_file, "%lx", &info->personality); | |
135 | fclose(proc_file); | |
136 | ||
137 | if (ret == EOF || ret == 0) { | |
138 | SYSERROR("Could not read personality from %s", proc_fn); | |
139 | errno = ENOENT; | |
140 | goto out_error; | |
141 | } | |
9958532b | 142 | info->aa_profile = aa_get_profile(pid); |
e0732705 | 143 | |
e0732705 CS |
144 | return info; |
145 | ||
146 | out_error: | |
460a1cf0 | 147 | free(info); |
e0732705 CS |
148 | free(line); |
149 | return NULL; | |
150 | } | |
151 | ||
fc763ab7 | 152 | int lxc_attach_to_ns(pid_t pid, int which) |
99d50954 CS |
153 | { |
154 | char path[MAXPATHLEN]; | |
fc763ab7 CS |
155 | /* according to <http://article.gmane.org/gmane.linux.kernel.containers.lxc.devel/1429>, |
156 | * the file for user namepsaces in /proc/$pid/ns will be called | |
157 | * 'user' once the kernel supports it | |
158 | */ | |
159 | static char *ns[] = { "mnt", "pid", "uts", "ipc", "user", "net" }; | |
160 | static int flags[] = { | |
161 | CLONE_NEWNS, CLONE_NEWPID, CLONE_NEWUTS, CLONE_NEWIPC, | |
162 | CLONE_NEWUSER, CLONE_NEWNET | |
163 | }; | |
164 | static const int size = sizeof(ns) / sizeof(char *); | |
99d50954 | 165 | int fd[size]; |
fc763ab7 CS |
166 | int i, j, saved_errno; |
167 | ||
99d50954 CS |
168 | |
169 | snprintf(path, MAXPATHLEN, "/proc/%d/ns", pid); | |
170 | if (access(path, X_OK)) { | |
171 | ERROR("Does this kernel version support 'attach' ?"); | |
172 | return -1; | |
173 | } | |
174 | ||
175 | for (i = 0; i < size; i++) { | |
fc763ab7 CS |
176 | /* ignore if we are not supposed to attach to that |
177 | * namespace | |
178 | */ | |
179 | if (which != -1 && !(which & flags[i])) { | |
180 | fd[i] = -1; | |
181 | continue; | |
182 | } | |
183 | ||
99d50954 CS |
184 | snprintf(path, MAXPATHLEN, "/proc/%d/ns/%s", pid, ns[i]); |
185 | fd[i] = open(path, O_RDONLY); | |
186 | if (fd[i] < 0) { | |
fc763ab7 CS |
187 | saved_errno = errno; |
188 | ||
189 | /* close all already opened file descriptors before | |
190 | * we return an error, so we don't leak them | |
191 | */ | |
192 | for (j = 0; j < i; j++) | |
193 | close(fd[j]); | |
194 | ||
195 | errno = saved_errno; | |
99d50954 CS |
196 | SYSERROR("failed to open '%s'", path); |
197 | return -1; | |
198 | } | |
199 | } | |
200 | ||
201 | for (i = 0; i < size; i++) { | |
fc763ab7 CS |
202 | if (fd[i] >= 0 && setns(fd[i], 0) != 0) { |
203 | saved_errno = errno; | |
204 | ||
205 | for (j = i; j < size; j++) | |
206 | close(fd[j]); | |
207 | ||
208 | errno = saved_errno; | |
99d50954 CS |
209 | SYSERROR("failed to set namespace '%s'", ns[i]); |
210 | return -1; | |
211 | } | |
212 | ||
213 | close(fd[i]); | |
214 | } | |
215 | ||
216 | return 0; | |
217 | } | |
218 | ||
7a0b0b56 CS |
219 | int lxc_attach_remount_sys_proc() |
220 | { | |
221 | int ret; | |
222 | ||
223 | ret = unshare(CLONE_NEWNS); | |
224 | if (ret < 0) { | |
225 | SYSERROR("failed to unshare mount namespace"); | |
226 | return -1; | |
227 | } | |
228 | ||
229 | /* assume /proc is always mounted, so remount it */ | |
230 | ret = umount2("/proc", MNT_DETACH); | |
231 | if (ret < 0) { | |
232 | SYSERROR("failed to unmount /proc"); | |
233 | return -1; | |
234 | } | |
235 | ||
236 | ret = mount("none", "/proc", "proc", 0, NULL); | |
237 | if (ret < 0) { | |
238 | SYSERROR("failed to remount /proc"); | |
239 | return -1; | |
240 | } | |
241 | ||
242 | /* try to umount /sys - if it's not a mount point, | |
243 | * we'll get EINVAL, then we ignore it because it | |
244 | * may not have been mounted in the first place | |
245 | */ | |
246 | ret = umount2("/sys", MNT_DETACH); | |
247 | if (ret < 0 && errno != EINVAL) { | |
248 | SYSERROR("failed to unmount /sys"); | |
249 | return -1; | |
250 | } else if (ret == 0) { | |
251 | /* remount it */ | |
252 | ret = mount("none", "/sys", "sysfs", 0, NULL); | |
253 | if (ret < 0) { | |
254 | SYSERROR("failed to remount /sys"); | |
255 | return -1; | |
256 | } | |
257 | } | |
258 | ||
259 | return 0; | |
260 | } | |
261 | ||
e0732705 CS |
262 | int lxc_attach_drop_privs(struct lxc_proc_context_info *ctx) |
263 | { | |
264 | int last_cap = lxc_caps_last_cap(); | |
265 | int cap; | |
266 | ||
267 | for (cap = 0; cap <= last_cap; cap++) { | |
268 | if (ctx->capability_mask & (1LL << cap)) | |
269 | continue; | |
270 | ||
271 | if (prctl(PR_CAPBSET_DROP, cap, 0, 0, 0)) { | |
272 | SYSERROR("failed to remove capability id %d", cap); | |
273 | return -1; | |
274 | } | |
275 | } | |
276 | ||
277 | return 0; | |
278 | } | |
905022f7 | 279 | |
b3a39ba6 DW |
280 | int lxc_attach_set_environment() |
281 | { | |
282 | if (clearenv()) { | |
283 | SYSERROR("failed to clear environment"); | |
284 | /* don't error out though */ | |
285 | } | |
286 | ||
287 | if (putenv("container=lxc")) { | |
288 | SYSERROR("failed to set environment variable"); | |
289 | return -1; | |
290 | } | |
291 | ||
292 | return 0; | |
293 | } | |
294 | ||
905022f7 CS |
295 | char *lxc_attach_getpwshell(uid_t uid) |
296 | { | |
297 | /* local variables */ | |
298 | pid_t pid; | |
299 | int pipes[2]; | |
300 | int ret; | |
301 | int fd; | |
302 | char *result = NULL; | |
303 | ||
304 | /* we need to fork off a process that runs the | |
305 | * getent program, and we need to capture its | |
306 | * output, so we use a pipe for that purpose | |
307 | */ | |
308 | ret = pipe(pipes); | |
309 | if (ret < 0) | |
310 | return NULL; | |
311 | ||
312 | pid = fork(); | |
313 | if (pid < 0) { | |
314 | close(pipes[0]); | |
315 | close(pipes[1]); | |
316 | return NULL; | |
317 | } | |
318 | ||
319 | if (pid) { | |
320 | /* parent process */ | |
321 | FILE *pipe_f; | |
322 | char *line = NULL; | |
323 | size_t line_bufsz = 0; | |
324 | int found = 0; | |
325 | int status; | |
326 | ||
327 | close(pipes[1]); | |
328 | ||
329 | pipe_f = fdopen(pipes[0], "r"); | |
330 | while (getline(&line, &line_bufsz, pipe_f) != -1) { | |
331 | char *token; | |
332 | char *saveptr = NULL; | |
333 | long value; | |
334 | char *endptr = NULL; | |
335 | int i; | |
336 | ||
337 | /* if we already found something, just continue | |
338 | * to read until the pipe doesn't deliver any more | |
339 | * data, but don't modify the existing data | |
340 | * structure | |
341 | */ | |
342 | if (found) | |
343 | continue; | |
344 | ||
345 | /* trim line on the right hand side */ | |
346 | for (i = strlen(line); line && i > 0 && (line[i - 1] == '\n' || line[i - 1] == '\r'); --i) | |
347 | line[i - 1] = '\0'; | |
348 | ||
349 | /* split into tokens: first user name */ | |
350 | token = strtok_r(line, ":", &saveptr); | |
351 | if (!token) | |
352 | continue; | |
353 | /* next: dummy password field */ | |
354 | token = strtok_r(NULL, ":", &saveptr); | |
355 | if (!token) | |
356 | continue; | |
357 | /* next: user id */ | |
358 | token = strtok_r(NULL, ":", &saveptr); | |
359 | value = token ? strtol(token, &endptr, 10) : 0; | |
360 | if (!token || !endptr || *endptr || value == LONG_MIN || value == LONG_MAX) | |
361 | continue; | |
362 | /* dummy sanity check: user id matches */ | |
363 | if ((uid_t) value != uid) | |
364 | continue; | |
365 | /* skip fields: gid, gecos, dir, go to next field 'shell' */ | |
366 | for (i = 0; i < 4; i++) { | |
367 | token = strtok_r(NULL, ":", &saveptr); | |
368 | if (!token) | |
369 | break; | |
370 | } | |
371 | if (!token) | |
372 | continue; | |
53a54099 SH |
373 | if (result) |
374 | free(result); | |
905022f7 CS |
375 | result = strdup(token); |
376 | ||
377 | /* sanity check that there are no fields after that */ | |
378 | token = strtok_r(NULL, ":", &saveptr); | |
379 | if (token) | |
380 | continue; | |
381 | ||
382 | found = 1; | |
383 | } | |
384 | ||
385 | free(line); | |
386 | fclose(pipe_f); | |
387 | again: | |
388 | if (waitpid(pid, &status, 0) < 0) { | |
389 | if (errno == EINTR) | |
390 | goto again; | |
391 | return NULL; | |
392 | } | |
393 | ||
394 | /* some sanity checks: if anything even hinted at going | |
395 | * wrong: we can't be sure we have a valid result, so | |
396 | * we assume we don't | |
397 | */ | |
398 | ||
399 | if (!WIFEXITED(status)) | |
400 | return NULL; | |
401 | ||
402 | if (WEXITSTATUS(status) != 0) | |
403 | return NULL; | |
404 | ||
405 | if (!found) | |
406 | return NULL; | |
407 | ||
408 | return result; | |
409 | } else { | |
410 | /* child process */ | |
411 | char uid_buf[32]; | |
412 | char *arguments[] = { | |
413 | "getent", | |
414 | "passwd", | |
415 | uid_buf, | |
416 | NULL | |
417 | }; | |
418 | ||
419 | close(pipes[0]); | |
420 | ||
421 | /* we want to capture stdout */ | |
422 | dup2(pipes[1], 1); | |
423 | close(pipes[1]); | |
424 | ||
425 | /* get rid of stdin/stderr, so we try to associate it | |
426 | * with /dev/null | |
427 | */ | |
428 | fd = open("/dev/null", O_RDWR); | |
429 | if (fd < 0) { | |
430 | close(0); | |
431 | close(2); | |
432 | } else { | |
433 | dup2(fd, 0); | |
434 | dup2(fd, 2); | |
435 | close(fd); | |
436 | } | |
437 | ||
438 | /* finish argument list */ | |
439 | ret = snprintf(uid_buf, sizeof(uid_buf), "%ld", (long) uid); | |
440 | if (ret <= 0) | |
441 | exit(-1); | |
442 | ||
443 | /* try to run getent program */ | |
444 | (void) execvp("getent", arguments); | |
445 | exit(-1); | |
446 | } | |
447 | } | |
cb3e61fa CS |
448 | |
449 | void lxc_attach_get_init_uidgid(uid_t* init_uid, gid_t* init_gid) | |
450 | { | |
451 | FILE *proc_file; | |
452 | char proc_fn[MAXPATHLEN]; | |
453 | char *line = NULL; | |
454 | size_t line_bufsz = 0; | |
455 | int ret; | |
456 | long value = -1; | |
457 | uid_t uid = (uid_t)-1; | |
458 | gid_t gid = (gid_t)-1; | |
459 | ||
460 | /* read capabilities */ | |
461 | snprintf(proc_fn, MAXPATHLEN, "/proc/%d/status", 1); | |
462 | ||
463 | proc_file = fopen(proc_fn, "r"); | |
464 | if (!proc_file) | |
465 | return; | |
466 | ||
467 | while (getline(&line, &line_bufsz, proc_file) != -1) { | |
468 | /* format is: real, effective, saved set user, fs | |
469 | * we only care about real uid | |
470 | */ | |
471 | ret = sscanf(line, "Uid: %ld", &value); | |
472 | if (ret != EOF && ret > 0) { | |
473 | uid = (uid_t) value; | |
474 | } else { | |
475 | ret = sscanf(line, "Gid: %ld", &value); | |
476 | if (ret != EOF && ret > 0) | |
477 | gid = (gid_t) value; | |
478 | } | |
479 | if (uid != (uid_t)-1 && gid != (gid_t)-1) | |
480 | break; | |
481 | } | |
482 | ||
483 | fclose(proc_file); | |
484 | free(line); | |
485 | ||
486 | /* only override arguments if we found something */ | |
487 | if (uid != (uid_t)-1) | |
488 | *init_uid = uid; | |
489 | if (gid != (gid_t)-1) | |
490 | *init_gid = gid; | |
491 | ||
492 | /* TODO: we should also parse supplementary groups and use | |
493 | * setgroups() to set them */ | |
494 | } |