chiark / gitweb /
selinux: unify systemd and udev code
[elogind.git] / src / core / mount-setup.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 <sys/mount.h>
23 #include <errno.h>
24 #include <sys/stat.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <libgen.h>
28 #include <assert.h>
29 #include <unistd.h>
30 #include <ftw.h>
31
32 #include "mount-setup.h"
33 #include "log.h"
34 #include "macro.h"
35 #include "util.h"
36 #include "label.h"
37 #include "set.h"
38 #include "strv.h"
39 #include "mkdir.h"
40
41 #ifndef TTY_GID
42 #define TTY_GID 5
43 #endif
44
45 typedef struct MountPoint {
46         const char *what;
47         const char *where;
48         const char *type;
49         const char *options;
50         unsigned long flags;
51         bool fatal;
52 } MountPoint;
53
54 /* The first three entries we might need before SELinux is up. The
55  * fourth (securityfs) is needed by IMA to load a custom policy. The
56  * other ones we can delay until SELinux and IMA are loaded. */
57 #define N_EARLY_MOUNT 4
58
59 static const MountPoint mount_table[] = {
60         { "proc",     "/proc",                  "proc",     NULL,                MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
61         { "sysfs",    "/sys",                   "sysfs",    NULL,                MS_NOSUID|MS_NOEXEC|MS_NODEV, true },
62         { "devtmpfs", "/dev",                   "devtmpfs", "mode=755",          MS_NOSUID|MS_STRICTATIME,     true },
63         { "securityfs", "/sys/kernel/security", "securityfs", NULL,              MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
64         { "tmpfs",    "/dev/shm",               "tmpfs",    "mode=1777",         MS_NOSUID|MS_NODEV|MS_STRICTATIME, true },
65         { "devpts",   "/dev/pts",               "devpts",   "mode=620,gid=" STRINGIFY(TTY_GID), MS_NOSUID|MS_NOEXEC, false },
66         { "tmpfs",    "/run",                   "tmpfs",    "mode=755",          MS_NOSUID|MS_NODEV|MS_STRICTATIME, true },
67         { "tmpfs",    "/sys/fs/cgroup",         "tmpfs",    "mode=755",          MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME, false },
68         { "cgroup",   "/sys/fs/cgroup/systemd", "cgroup",   "none,name=systemd", MS_NOSUID|MS_NOEXEC|MS_NODEV, false },
69 };
70
71 /* These are API file systems that might be mounted by other software,
72  * we just list them here so that we know that we should ignore them */
73
74 static const char * const ignore_paths[] = {
75         "/sys/fs/selinux",
76         "/selinux",
77         "/proc/bus/usb"
78 };
79
80 bool mount_point_is_api(const char *path) {
81         unsigned i;
82
83         /* Checks if this mount point is considered "API", and hence
84          * should be ignored */
85
86         for (i = 0; i < ELEMENTSOF(mount_table); i ++)
87                 if (path_equal(path, mount_table[i].where))
88                         return true;
89
90         return path_startswith(path, "/sys/fs/cgroup/");
91 }
92
93 bool mount_point_ignore(const char *path) {
94         unsigned i;
95
96         for (i = 0; i < ELEMENTSOF(ignore_paths); i++)
97                 if (path_equal(path, ignore_paths[i]))
98                         return true;
99
100         return false;
101 }
102
103 static int mount_one(const MountPoint *p, bool relabel) {
104         int r;
105
106         assert(p);
107
108         /* Relabel first, just in case */
109         if (relabel)
110                 label_fix(p->where, true);
111
112         if ((r = path_is_mount_point(p->where, true)) < 0)
113                 return r;
114
115         if (r > 0)
116                 return 0;
117
118         /* The access mode here doesn't really matter too much, since
119          * the mounted file system will take precedence anyway. */
120         mkdir_p(p->where, 0755);
121
122         log_debug("Mounting %s to %s of type %s with options %s.",
123                   p->what,
124                   p->where,
125                   p->type,
126                   strna(p->options));
127
128         if (mount(p->what,
129                   p->where,
130                   p->type,
131                   p->flags,
132                   p->options) < 0) {
133                 log_error("Failed to mount %s: %s", p->where, strerror(errno));
134                 return p->fatal ? -errno : 0;
135         }
136
137         /* Relabel again, since we now mounted something fresh here */
138         if (relabel)
139                 label_fix(p->where, false);
140
141         return 1;
142 }
143
144 int mount_setup_early(void) {
145         unsigned i;
146         int r = 0;
147
148         assert_cc(N_EARLY_MOUNT <= ELEMENTSOF(mount_table));
149
150         /* Do a minimal mount of /proc and friends to enable the most
151          * basic stuff, such as SELinux */
152         for (i = 0; i < N_EARLY_MOUNT; i ++)  {
153                 int j;
154
155                 j = mount_one(mount_table + i, false);
156                 if (r == 0)
157                         r = j;
158         }
159
160         return r;
161 }
162
163 int mount_cgroup_controllers(char ***join_controllers) {
164         int r;
165         FILE *f;
166         char buf[LINE_MAX];
167         Set *controllers;
168
169         /* Mount all available cgroup controllers that are built into the kernel. */
170
171         f = fopen("/proc/cgroups", "re");
172         if (!f) {
173                 log_error("Failed to enumerate cgroup controllers: %m");
174                 return 0;
175         }
176
177         controllers = set_new(string_hash_func, string_compare_func);
178         if (!controllers) {
179                 r = -ENOMEM;
180                 log_error("Failed to allocate controller set.");
181                 goto finish;
182         }
183
184         /* Ignore the header line */
185         (void) fgets(buf, sizeof(buf), f);
186
187         for (;;) {
188                 char *controller;
189                 int enabled = 0;
190
191                 if (fscanf(f, "%ms %*i %*i %i", &controller, &enabled) != 2) {
192
193                         if (feof(f))
194                                 break;
195
196                         log_error("Failed to parse /proc/cgroups.");
197                         r = -EIO;
198                         goto finish;
199                 }
200
201                 if (!enabled) {
202                         free(controller);
203                         continue;
204                 }
205
206                 r = set_put(controllers, controller);
207                 if (r < 0) {
208                         log_error("Failed to add controller to set.");
209                         free(controller);
210                         goto finish;
211                 }
212         }
213
214         for (;;) {
215                 MountPoint p;
216                 char *controller, *where, *options;
217                 char ***k = NULL;
218
219                 controller = set_steal_first(controllers);
220                 if (!controller)
221                         break;
222
223                 if (join_controllers)
224                         for (k = join_controllers; *k; k++)
225                                 if (strv_find(*k, controller))
226                                         break;
227
228                 if (k && *k) {
229                         char **i, **j;
230
231                         for (i = *k, j = *k; *i; i++) {
232
233                                 if (!streq(*i, controller)) {
234                                         char *t;
235
236                                         t = set_remove(controllers, *i);
237                                         if (!t) {
238                                                 free(*i);
239                                                 continue;
240                                         }
241                                         free(t);
242                                 }
243
244                                 *(j++) = *i;
245                         }
246
247                         *j = NULL;
248
249                         options = strv_join(*k, ",");
250                         if (!options) {
251                                 log_error("Failed to join options");
252                                 free(controller);
253                                 r = -ENOMEM;
254                                 goto finish;
255                         }
256
257                 } else {
258                         options = controller;
259                         controller = NULL;
260                 }
261
262                 where = strappend("/sys/fs/cgroup/", options);
263                 if (!where) {
264                         log_error("Failed to build path");
265                         free(options);
266                         r = -ENOMEM;
267                         goto finish;
268                 }
269
270                 zero(p);
271                 p.what = "cgroup";
272                 p.where = where;
273                 p.type = "cgroup";
274                 p.options = options;
275                 p.flags = MS_NOSUID|MS_NOEXEC|MS_NODEV;
276                 p.fatal = false;
277
278                 r = mount_one(&p, true);
279                 free(controller);
280                 free(where);
281
282                 if (r < 0) {
283                         free(options);
284                         goto finish;
285                 }
286
287                 if (r > 0 && k && *k) {
288                         char **i;
289
290                         for (i = *k; *i; i++) {
291                                 char *t;
292
293                                 t = strappend("/sys/fs/cgroup/", *i);
294                                 if (!t) {
295                                         log_error("Failed to build path");
296                                         r = -ENOMEM;
297                                         free(options);
298                                         goto finish;
299                                 }
300
301                                 r = symlink(options, t);
302                                 free(t);
303
304                                 if (r < 0 && errno != EEXIST) {
305                                         log_error("Failed to create symlink: %m");
306                                         r = -errno;
307                                         free(options);
308                                         goto finish;
309                                 }
310                         }
311                 }
312
313                 free(options);
314         }
315
316         r = 0;
317
318 finish:
319         set_free_free(controllers);
320
321         fclose(f);
322
323         return r;
324 }
325
326 static int symlink_and_label(const char *old_path, const char *new_path) {
327         int r;
328
329         assert(old_path);
330         assert(new_path);
331
332         r = label_context_set(new_path, S_IFLNK);
333         if (r < 0)
334                 return r;
335
336         if (symlink(old_path, new_path) < 0)
337                 r = -errno;
338
339         label_context_clear();
340
341         return r;
342 }
343
344 static int nftw_cb(
345                 const char *fpath,
346                 const struct stat *sb,
347                 int tflag,
348                 struct FTW *ftwbuf) {
349
350         /* No need to label /dev twice in a row... */
351         if (_unlikely_(ftwbuf->level == 0))
352                 return FTW_CONTINUE;
353
354         label_fix(fpath, true);
355
356         /* /run/initramfs is static data and big, no need to
357          * dynamically relabel its contents at boot... */
358         if (_unlikely_(ftwbuf->level == 1 &&
359                       tflag == FTW_D &&
360                       streq(fpath, "/run/initramfs")))
361                 return FTW_SKIP_SUBTREE;
362
363         return FTW_CONTINUE;
364 };
365
366 int mount_setup(bool loaded_policy) {
367
368         static const char symlinks[] =
369                 "/proc/kcore\0"      "/dev/core\0"
370                 "/proc/self/fd\0"    "/dev/fd\0"
371                 "/proc/self/fd/0\0"  "/dev/stdin\0"
372                 "/proc/self/fd/1\0"  "/dev/stdout\0"
373                 "/proc/self/fd/2\0"  "/dev/stderr\0";
374
375         static const char relabel[] =
376                 "/run/initramfs/root-fsck\0"
377                 "/run/initramfs/shutdown\0";
378
379         int r;
380         unsigned i;
381         const char *j, *k;
382
383         for (i = 0; i < ELEMENTSOF(mount_table); i ++) {
384                 r = mount_one(mount_table + i, true);
385
386                 if (r < 0)
387                         return r;
388         }
389
390         /* Nodes in devtmpfs and /run need to be manually updated for
391          * the appropriate labels, after mounting. The other virtual
392          * API file systems like /sys and /proc do not need that, they
393          * use the same label for all their files. */
394         if (loaded_policy) {
395                 usec_t before_relabel, after_relabel;
396                 char timespan[FORMAT_TIMESPAN_MAX];
397
398                 before_relabel = now(CLOCK_MONOTONIC);
399
400                 nftw("/dev", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
401                 nftw("/run", nftw_cb, 64, FTW_MOUNT|FTW_PHYS|FTW_ACTIONRETVAL);
402
403                 /* Explicitly relabel these */
404                 NULSTR_FOREACH(j, relabel)
405                         label_fix(j, true);
406
407                 after_relabel = now(CLOCK_MONOTONIC);
408
409                 log_info("Relabelled /dev and /run in %s.",
410                          format_timespan(timespan, sizeof(timespan), after_relabel - before_relabel));
411         }
412
413         /* Create a few default symlinks, which are normally created
414          * by udevd, but some scripts might need them before we start
415          * udevd. */
416         NULSTR_FOREACH_PAIR(j, k, symlinks)
417                 symlink_and_label(j, k);
418
419         /* Create a few directories we always want around */
420         label_mkdir("/run/systemd", 0755);
421         label_mkdir("/run/systemd/system", 0755);
422
423         return 0;
424 }