chiark / gitweb /
namespace: make PrivateTmp= apply to both /tmp and /var/tmp
[elogind.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 "path-util.h"
37 #include "namespace.h"
38 #include "missing.h"
39
40 typedef enum PathMode {
41         /* This is ordered by priority! */
42         INACCESSIBLE,
43         READONLY,
44         PRIVATE,
45         READWRITE
46 } PathMode;
47
48 typedef struct Path {
49         const char *path;
50         PathMode mode;
51 } Path;
52
53 static int append_paths(Path **p, char **strv, PathMode mode) {
54         char **i;
55
56         STRV_FOREACH(i, strv) {
57
58                 if (!path_is_absolute(*i))
59                         return -EINVAL;
60
61                 (*p)->path = *i;
62                 (*p)->mode = mode;
63                 (*p)++;
64         }
65
66         return 0;
67 }
68
69 static int path_compare(const void *a, const void *b) {
70         const Path *p = a, *q = b;
71
72         if (path_equal(p->path, q->path)) {
73
74                 /* If the paths are equal, check the mode */
75                 if (p->mode < q->mode)
76                         return -1;
77
78                 if (p->mode > q->mode)
79                         return 1;
80
81                 return 0;
82         }
83
84         /* If the paths are not equal, then order prefixes first */
85         if (path_startswith(p->path, q->path))
86                 return 1;
87
88         if (path_startswith(q->path, p->path))
89                 return -1;
90
91         return 0;
92 }
93
94 static void drop_duplicates(Path *p, unsigned *n, bool *need_inaccessible, bool *need_private) {
95         Path *f, *t, *previous;
96
97         assert(p);
98         assert(n);
99         assert(need_inaccessible);
100         assert(need_private);
101
102         for (f = p, t = p, previous = NULL; f < p+*n; f++) {
103
104                 if (previous && path_equal(f->path, previous->path))
105                         continue;
106
107                 t->path = f->path;
108                 t->mode = f->mode;
109
110                 if (t->mode == PRIVATE)
111                         *need_private = true;
112
113                 if (t->mode == INACCESSIBLE)
114                         *need_inaccessible = true;
115
116                 previous = t;
117
118                 t++;
119         }
120
121         *n = t - p;
122 }
123
124 static int apply_mount(Path *p, const char *root_dir, const char *inaccessible_dir, const char *private_dir, unsigned long flags) {
125         const char *what;
126         char *where;
127         int r;
128
129         assert(p);
130         assert(root_dir);
131         assert(inaccessible_dir);
132         assert(private_dir);
133
134         where = strappend(root_dir, p->path);
135         if (!where)
136                 return -ENOMEM;
137
138         switch (p->mode) {
139
140         case INACCESSIBLE:
141                 what = inaccessible_dir;
142                 flags |= MS_RDONLY;
143                 break;
144
145         case READONLY:
146                 flags |= MS_RDONLY;
147                 /* Fall through */
148
149         case READWRITE:
150                 what = p->path;
151                 break;
152
153         case PRIVATE:
154                 what = private_dir;
155                 break;
156
157         default:
158                 assert_not_reached("Unknown mode");
159         }
160
161         r = mount(what, where, NULL, MS_BIND|MS_REC, NULL);
162         if (r >= 0) {
163                 log_debug("Successfully mounted %s to %s", what, where);
164
165                 /* The bind mount will always inherit the original
166                  * flags. If we want to set any flag we need
167                  * to do so in a second independent step. */
168                 if (flags)
169                         r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|MS_REC|flags, NULL);
170
171                 /* Avoid exponential growth of trees */
172                 if (r >= 0 && path_equal(p->path, "/"))
173                         r = mount(NULL, where, NULL, MS_REMOUNT|MS_BIND|flags, NULL);
174
175                 if (r < 0) {
176                         r = -errno;
177                         umount2(where, MNT_DETACH);
178                 }
179         }
180
181         free(where);
182         return r;
183 }
184
185 int setup_namespace(
186                 char **writable,
187                 char **readable,
188                 char **inaccessible,
189                 bool private_tmp,
190                 unsigned long flags) {
191
192         char
193                 tmp_dir[] = "/tmp/systemd-namespace-XXXXXX",
194                 root_dir[] = "/tmp/systemd-namespace-XXXXXX/root",
195                 old_root_dir[] = "/tmp/systemd-namespace-XXXXXX/root/tmp/old-root-XXXXXX",
196                 inaccessible_dir[] = "/tmp/systemd-namespace-XXXXXX/inaccessible",
197                 private_dir[] = "/tmp/systemd-namespace-XXXXXX/private";
198
199         Path *paths, *p;
200         unsigned n;
201         bool need_private = false, need_inaccessible = false;
202         bool remove_tmp = false, remove_root = false, remove_old_root = false, remove_inaccessible = false, remove_private = false;
203         int r;
204         const char *t;
205
206         n =
207                 strv_length(writable) +
208                 strv_length(readable) +
209                 strv_length(inaccessible) +
210                 (private_tmp ? 3 : 1);
211
212         paths = new(Path, n);
213         if (!paths)
214                 return -ENOMEM;
215
216         p = paths;
217         if ((r = append_paths(&p, writable, READWRITE)) < 0 ||
218             (r = append_paths(&p, readable, READONLY)) < 0 ||
219             (r = append_paths(&p, inaccessible, INACCESSIBLE)) < 0)
220                 goto fail;
221
222         if (private_tmp) {
223                 p->path = "/tmp";
224                 p->mode = PRIVATE;
225                 p++;
226
227                 p->path = "/var/tmp";
228                 p->mode = PRIVATE;
229                 p++;
230         }
231
232         p->path = "/";
233         p->mode = READWRITE;
234         p++;
235
236         assert(paths + n == p);
237
238         qsort(paths, n, sizeof(Path), path_compare);
239         drop_duplicates(paths, &n, &need_inaccessible, &need_private);
240
241         if (!mkdtemp(tmp_dir)) {
242                 r = -errno;
243                 goto fail;
244         }
245         remove_tmp = true;
246
247         memcpy(root_dir, tmp_dir, sizeof(tmp_dir)-1);
248         if (mkdir(root_dir, 0777) < 0) {
249                 r = -errno;
250                 goto fail;
251         }
252         remove_root = true;
253
254         if (need_inaccessible) {
255                 memcpy(inaccessible_dir, tmp_dir, sizeof(tmp_dir)-1);
256                 if (mkdir(inaccessible_dir, 0) < 0) {
257                         r = -errno;
258                         goto fail;
259                 }
260                 remove_inaccessible = true;
261         }
262
263         if (need_private) {
264                 mode_t u;
265
266                 memcpy(private_dir, tmp_dir, sizeof(tmp_dir)-1);
267
268                 u = umask(0000);
269                 if (mkdir(private_dir, 0777 + S_ISVTX) < 0) {
270                         umask(u);
271
272                         r = -errno;
273                         goto fail;
274                 }
275
276                 umask(u);
277                 remove_private = true;
278         }
279
280         if (unshare(CLONE_NEWNS) < 0) {
281                 r = -errno;
282                 goto fail;
283         }
284
285         /* Remount / as SLAVE so that nothing mounted in the namespace
286            shows up in the parent */
287         if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) {
288                 r = -errno;
289                 goto fail;
290         }
291
292         for (p = paths; p < paths + n; p++) {
293                 r = apply_mount(p, root_dir, inaccessible_dir, private_dir, flags);
294                 if (r < 0)
295                         goto undo_mounts;
296         }
297
298         memcpy(old_root_dir, tmp_dir, sizeof(tmp_dir)-1);
299         if (!mkdtemp(old_root_dir)) {
300                 r = -errno;
301                 goto undo_mounts;
302         }
303         remove_old_root = true;
304
305         if (chdir(root_dir) < 0) {
306                 r = -errno;
307                 goto undo_mounts;
308         }
309
310         if (pivot_root(root_dir, old_root_dir) < 0) {
311                 r = -errno;
312                 goto undo_mounts;
313         }
314
315         t = old_root_dir + sizeof(root_dir) - 1;
316         if (umount2(t, MNT_DETACH) < 0)
317                 /* At this point it's too late to turn anything back,
318                  * since we are already in the new root. */
319                 return -errno;
320
321         if (rmdir(t) < 0)
322                 return -errno;
323
324         return 0;
325
326 undo_mounts:
327
328         for (p--; p >= paths; p--) {
329                 char full_path[PATH_MAX];
330
331                 snprintf(full_path, sizeof(full_path), "%s%s", root_dir, p->path);
332                 char_array_0(full_path);
333
334                 umount2(full_path, MNT_DETACH);
335         }
336
337 fail:
338         if (remove_old_root)
339                 rmdir(old_root_dir);
340
341         if (remove_inaccessible)
342                 rmdir(inaccessible_dir);
343
344         if (remove_private)
345                 rmdir(private_dir);
346
347         if (remove_root)
348                 rmdir(root_dir);
349
350         if (remove_tmp)
351                 rmdir(tmp_dir);
352
353         free(paths);
354
355         return r;
356 }