]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/core/namespace.c
relicense to LGPLv2.1 (with exceptions)
[thirdparty/systemd.git] / src / core / namespace.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2010 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <sys/mount.h>
24 #include <string.h>
25 #include <stdio.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <sys/types.h>
29 #include <sched.h>
30 #include <sys/syscall.h>
31 #include <limits.h>
32 #include <linux/fs.h>
33
34 #include "strv.h"
35 #include "util.h"
36 #include "namespace.h"
37 #include "missing.h"
38
39 typedef enum PathMode {
40 /* This is ordered by priority! */
41 INACCESSIBLE,
42 READONLY,
43 PRIVATE,
44 READWRITE
45 } PathMode;
46
47 typedef struct Path {
48 const char *path;
49 PathMode mode;
50 } Path;
51
52 static int append_paths(Path **p, char **strv, PathMode mode) {
53 char **i;
54
55 STRV_FOREACH(i, strv) {
56
57 if (!path_is_absolute(*i))
58 return -EINVAL;
59
60 (*p)->path = *i;
61 (*p)->mode = mode;
62 (*p)++;
63 }
64
65 return 0;
66 }
67
68 static int path_compare(const void *a, const void *b) {
69 const Path *p = a, *q = b;
70
71 if (path_equal(p->path, q->path)) {
72
73 /* If the paths are equal, check the mode */
74 if (p->mode < q->mode)
75 return -1;
76
77 if (p->mode > q->mode)
78 return 1;
79
80 return 0;
81 }
82
83 /* If the paths are not equal, then order prefixes first */
84 if (path_startswith(p->path, q->path))
85 return 1;
86
87 if (path_startswith(q->path, p->path))
88 return -1;
89
90 return 0;
91 }
92
93 static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible, bool *need_private) {
94 Path *f, *t, *previous;
95
96 assert(p);
97 assert(n);
98 assert(need_inaccessible);
99 assert(need_private);
100
101 for (f = p, t = p, previous = NULL; f < p+*n; f++) {
102
103 if (previous && path_equal(f->path, previous->path))
104 continue;
105
106 t->path = f->path;
107 t->mode = f->mode;
108
109 if (t->mode == PRIVATE)
110 *need_private = true;
111
112 if (t->mode == INACCESSIBLE)
113 *need_inaccessible = true;
114
115 previous = t;
116
117 t++;
118 }
119
120 *n = t - p;
121 }
122
123 static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) {
124 const char *what;
125 char *where;
126 int r;
127
128 assert(p);
129 assert(root_dir);
130 assert(inaccessible_dir);
131 assert(private_dir);
132
133 if (!(where = strappend(root_dir, p->path)))
134 return -ENOMEM;
135
136 switch (p->mode) {
137
138 case INACCESSIBLE:
139 what = inaccessible_dir;
140 flags |= MS_RDONLY;
141 break;
142
143 case READONLY:
144 flags |= MS_RDONLY;
145 /* Fall through */
146
147 case READWRITE:
148 what = p->path;
149 break;
150
151 case PRIVATE:
152 what = private_dir;
153 break;
154
155 default:
156 assert_not_reached("Unknown mode");
157 }
158
159 if ((r = mount(what, where, NULL, MS_BIND|MS_REC, NULL)) >= 0) {
160 log_debug("Successfully mounted %s to %s", what, where);
161
162 /* The bind mount will always inherit the original
163 * flags. If we want to set any flag we need
164 * to do so in a second independent step. */
165 if (flags)
166 r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL);
167
168 /* Avoid exponential growth of trees */
169 if (r >= 0 && path_equal(p->path, "/"))
170 r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_UNBINDABLE|flags, NULL);
171
172 if (r < 0) {
173 r = -errno;
174 umount2(where, MNT_DETACH);
175 }
176 }
177
178 free(where);
179 return r;
180 }
181
182 int setup_namespace(
183 char **writable,
184 char **readable,
185 char **inaccessible,
186 bool private_tmp,
187 unsigned long flags) {
188
189 char
190 tmp_dir[] = "/tmp/systemd-namespace-XXXXXX",
191 root_dir[] = "/tmp/systemd-namespace-XXXXXX/root",
192 old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX",
193 inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible",
194 private_dir[] = "/tmp/systemd-namespace-XXXXXX/private";
195
196 Path *paths, *p;
197 unsigned n;
198 bool need_private = false, need_inaccessible = false;
199 bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false;
200 int r;
201 const char *t;
202
203 n =
204 strv_length(writable) +
205 strv_length(readable) +
206 strv_length(inaccessible) +
207 (private_tmp ? 2 : 1);
208
209 if (!(paths = new(Path, n)))
210 return -ENOMEM;
211
212 p = paths;
213 if ((r = append_paths(&p, writable, READWRITE)) < 0 ||
214 (r = append_paths(&p, readable, READONLY)) < 0 ||
215 (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0)
216 goto fail;
217
218 if (private_tmp) {
219 p->path = "/tmp";
220 p->mode = PRIVATE;
221 p++;
222 }
223
224 p->path = "/";
225 p->mode = READWRITE;
226 p++;
227
228 assert(paths + n == p);
229
230 qsort(paths, n, sizeof(Path), path_compare);
231 drop_duplicates(paths, &n, &need_inaccessible, &need_private);
232
233 if (!mkdtemp(tmp_dir)) {
234 r = -errno;
235 goto fail;
236 }
237 remove_tmp = true;
238
239 memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1);
240 if (mkdir(root_dir, 0777) < 0) {
241 r = -errno;
242 goto fail;
243 }
244 remove_root = true;
245
246 if (need_inaccessible) {
247 memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1);
248 if (mkdir(inaccessible_dir, 0) < 0) {
249 r = -errno;
250 goto fail;
251 }
252 remove_inaccessible = true;
253 }
254
255 if (need_private) {
256 mode_t u;
257
258 memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1);
259
260 u = umask(0000);
261 if (mkdir(private_dir, 0777 + S_ISVTX) < 0) {
262 umask(u);
263
264 r = -errno;
265 goto fail;
266 }
267
268 umask(u);
269 remove_private = true;
270 }
271
272 if (unshare(CLONE_NEWNS) < 0) {
273 r = -errno;
274 goto fail;
275 }
276
277 /* Remount / as SLAVE so that nothing mounted in the namespace
278 shows up in the parent */
279 if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
280 r = -errno;
281 goto fail;
282 }
283
284 for (p = paths; p < paths + n; p++)
285 if ((r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags)) < 0)
286 goto undo_mounts;
287
288 memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1);
289 if (!mkdtemp(old_root_dir)) {
290 r = -errno;
291 goto undo_mounts;
292 }
293 remove_old_root = true;
294
295 if (chdir(root_dir) < 0) {
296 r = -errno;
297 goto undo_mounts;
298 }
299
300 if (pivot_root(root_dir, old_root_dir) < 0) {
301 r = -errno;
302 goto undo_mounts;
303 }
304
305 t = old_root_dir + sizeof(root_dir) - 1;
306 if (umount2(t, MNT_DETACH) < 0)
307 /* At this point it's too late to turn anything back,
308 * since we are already in the new root. */
309 return -errno;
310
311 if (rmdir(t) < 0)
312 return -errno;
313
314 return 0;
315
316 undo_mounts:
317
318 for (p--; p >= paths; p--) {
319 char full_path[PATH_MAX];
320
321 snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path);
322 char_array_0(full_path);
323
324 umount2(full_path, MNT_DETACH);
325 }
326
327 fail:
328 if (remove_old_root)
329 rmdir(old_root_dir);
330
331 if (remove_inaccessible)
332 rmdir(inaccessible_dir);
333
334 if (remove_private)
335 rmdir(private_dir);
336
337 if (remove_root)
338 rmdir(root_dir);
339
340 if (remove_tmp)
341 rmdir(tmp_dir);
342
343 free(paths);
344
345 return r;
346 }