• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/procd/jail/fs.c

  1 /*
  2  * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
  3  * Copyright (C) 2015 Etienne Champetier <champetier.etienne@gmail.com>
  4  * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
  5  *
  6  * This program is free software; you can redistribute it and/or modify
  7  * it under the terms of the GNU Lesser General Public License version 2.1
  8  * as published by the Free Software Foundation
  9  *
 10  * This program is distributed in the hope that it will be useful,
 11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13  * GNU General Public License for more details.
 14  */
 15 
 16 #define _GNU_SOURCE
 17 
 18 #include <assert.h>
 19 #include <elf.h>
 20 #include <errno.h>
 21 #include <fcntl.h>
 22 #include <linux/limits.h>
 23 #include <stdlib.h>
 24 #include <stdio.h>
 25 #include <string.h>
 26 #include <sys/stat.h>
 27 #include <sys/mman.h>
 28 #include <unistd.h>
 29 #include <libgen.h>
 30 
 31 #include <libubox/avl.h>
 32 #include <libubox/avl-cmp.h>
 33 #include <libubox/blobmsg.h>
 34 #include <libubox/list.h>
 35 #include <libubox/utils.h>
 36 
 37 #include "elf.h"
 38 #include "fs.h"
 39 #include "jail.h"
 40 #include "log.h"
 41 
 42 #define UJAIL_NOAFILE "/tmp/.ujailnoafile"
 43 
 44 struct mount {
 45         struct avl_node avl;
 46         const char *source;
 47         const char *target;
 48         const char *filesystemtype;
 49         unsigned long mountflags;
 50         unsigned long propflags;
 51         const char *optstr;
 52         int error;
 53         bool inner;
 54 };
 55 
 56 struct avl_tree mounts;
 57 
 58 static int do_mount(const char *root, const char *orig_source, const char *target, const char *filesystemtype,
 59                     unsigned long orig_mountflags, unsigned long propflags, const char *optstr, int error, bool inner)
 60 {
 61         struct stat s;
 62         char new[PATH_MAX];
 63         char *source = (char *)orig_source;
 64         int fd;
 65         bool is_bind = (orig_mountflags & MS_BIND);
 66         bool is_mask = (source == (void *)(-1));
 67         unsigned long mountflags = orig_mountflags;
 68 
 69         if (source && is_bind && stat(source, &s)) {
 70                 ERROR("stat(%s) failed: %m\n", source);
 71                 return error;
 72         }
 73 
 74         if (!is_mask && orig_source && inner) {
 75                 if (asprintf(&source, "%s%s", root, orig_source) < 0)
 76                         return ENOMEM;
 77         }
 78 
 79         snprintf(new, sizeof(new), "%s%s", root, target?target:source);
 80 
 81         if (is_mask) {
 82                 if (stat(new, &s))
 83                         return 0; /* doesn't exists, nothing to mask */
 84 
 85                 if (S_ISDIR(s.st_mode)) {/* use empty 0-sized tmpfs for directories */
 86                         if (mount(NULL, new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, "size=0,mode=000"))
 87                                 return error;
 88                 } else {
 89                         /* mount-bind 0-sized file having mode 000 */
 90                         if (mount(UJAIL_NOAFILE, new, "bind", MS_BIND, NULL))
 91                                 return error;
 92 
 93                         if (mount(UJAIL_NOAFILE, new, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, NULL))
 94                                 return error;
 95                 }
 96 
 97                 DEBUG("masked path %s\n", new);
 98                 return 0;
 99         }
100 
101 
102         if (!is_bind || (source && S_ISDIR(s.st_mode))) {
103                 mkdir_p(new, 0755);
104         } else if (is_bind && source) {
105                 mkdir_p(dirname(new), 0755);
106                 snprintf(new, sizeof(new), "%s%s", root, target?target:source);
107                 fd = creat(new, 0644);
108                 if (fd < 0) {
109                         ERROR("creat(%s) failed: %m\n", new);
110                         return error;
111                 }
112                 close(fd);
113         }
114 
115         if (is_bind) {
116                 if (mount(source?:new, new, filesystemtype?:"bind", MS_BIND | (mountflags & MS_REC), optstr)) {
117                         if (error)
118                                 ERROR("failed to mount -B %s %s: %m\n", source, new);
119 
120                         if (inner)
121                                 free(source);
122 
123                         return error;
124                 }
125                 mountflags |= MS_REMOUNT;
126         }
127 
128         const char *hack_fstype = ((!filesystemtype || strcmp(filesystemtype, "cgroup"))?filesystemtype:"cgroup2");
129         if (mount(source?:(is_bind?new:NULL), new, hack_fstype?:"none", mountflags, optstr)) {
130                 if (error)
131                         ERROR("failed to mount %s %s: %m\n", source, new);
132 
133                 if (inner)
134                         free(source);
135 
136                 return error;
137         }
138 
139         DEBUG("mount %s%s %s (%s)\n", (mountflags & MS_BIND)?"-B ":"", source, new,
140               (mountflags & MS_RDONLY)?"ro":"rw");
141 
142         if (propflags && mount("none", new, "none", propflags, NULL)) {
143                 if (error)
144                         ERROR("failed to mount --make-... %s \n", new);
145 
146                 if (inner)
147                         free(source);
148 
149                 return error;
150         }
151 
152         if (inner)
153                 free(source);
154 
155         return 0;
156 }
157 
158 static int _add_mount(const char *source, const char *target, const char *filesystemtype,
159                       unsigned long mountflags, unsigned long propflags, const char *optstr,
160                       int error, bool inner)
161 {
162         assert(target != NULL);
163 
164         if (avl_find(&mounts, target))
165                 return 1;
166 
167         struct mount *m;
168         m = calloc(1, sizeof(struct mount));
169         if (!m)
170                 return ENOMEM;
171 
172         m->avl.key = m->target = strdup(target);
173         if (source) {
174                 if (source != (void*)(-1))
175                         m->source = strdup(source);
176                 else
177                         m->source = (void*)(-1);
178         }
179         if (filesystemtype)
180                 m->filesystemtype = strdup(filesystemtype);
181 
182         if (optstr)
183                 m->optstr = strdup(optstr);
184 
185         m->mountflags = mountflags;
186         m->propflags = propflags;
187         m->error = error;
188         m->inner = inner;
189 
190         avl_insert(&mounts, &m->avl);
191         DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m->source == (void*)(-1))?"mask":m->source, m->target,
192                 !!(m->mountflags & MS_BIND), !!(m->mountflags & MS_RDONLY), m->error != 0);
193 
194         return 0;
195 }
196 
197 int add_mount(const char *source, const char *target, const char *filesystemtype,
198               unsigned long mountflags, unsigned long propflags, const char *optstr, int error)
199 {
200         return _add_mount(source, target, filesystemtype, mountflags, propflags, optstr, error, false);
201 }
202 
203 int add_mount_inner(const char *source, const char *target, const char *filesystemtype,
204               unsigned long mountflags, unsigned long propflags, const char *optstr, int error)
205 {
206         return _add_mount(source, target, filesystemtype, mountflags, propflags, optstr, error, true);
207 }
208 
209 static int _add_mount_bind(const char *path, const char *path2, int readonly, int error)
210 {
211         unsigned long mountflags = MS_BIND;
212 
213         if (readonly)
214                 mountflags |= MS_RDONLY;
215 
216         return add_mount(path, path2, NULL, mountflags, 0, NULL, error);
217 }
218 
219 int add_mount_bind(const char *path, int readonly, int error)
220 {
221         return _add_mount_bind(path, path, readonly, error);
222 }
223 
224 enum {
225         OCI_MOUNT_SOURCE,
226         OCI_MOUNT_DESTINATION,
227         OCI_MOUNT_TYPE,
228         OCI_MOUNT_OPTIONS,
229         __OCI_MOUNT_MAX,
230 };
231 
232 static const struct blobmsg_policy oci_mount_policy[] = {
233         [OCI_MOUNT_SOURCE] = { "source", BLOBMSG_TYPE_STRING },
234         [OCI_MOUNT_DESTINATION] = { "destination", BLOBMSG_TYPE_STRING },
235         [OCI_MOUNT_TYPE] = { "type", BLOBMSG_TYPE_STRING },
236         [OCI_MOUNT_OPTIONS] = { "options", BLOBMSG_TYPE_ARRAY },
237 };
238 
239 struct mount_opt {
240         struct list_head list;
241         char *optstr;
242 };
243 
244 #ifndef MS_LAZYTIME
245 #define MS_LAZYTIME (1 << 25)
246 #endif
247 
248 static int parseOCImountopts(struct blob_attr *msg, unsigned long *mount_flags, unsigned long *propagation_flags, char **mount_data, int *error)
249 {
250         struct blob_attr *cur;
251         int rem;
252         unsigned long mf = 0;
253         unsigned long pf = 0;
254         char *tmp;
255         struct list_head fsopts = LIST_HEAD_INIT(fsopts);
256         size_t len = 0;
257         struct mount_opt *opt, *tmpopt;
258 
259         blobmsg_for_each_attr(cur, msg, rem) {
260                 tmp = blobmsg_get_string(cur);
261                 if (!strcmp("ro", tmp))
262                         mf |= MS_RDONLY;
263                 else if (!strcmp("rw", tmp))
264                         mf &= ~MS_RDONLY;
265                 else if (!strcmp("bind", tmp))
266                         mf = MS_BIND;
267                 else if (!strcmp("rbind", tmp))
268                         mf |= MS_BIND | MS_REC;
269                 else if (!strcmp("sync", tmp))
270                         mf |= MS_SYNCHRONOUS;
271                 else if (!strcmp("async", tmp))
272                         mf &= ~MS_SYNCHRONOUS;
273                 else if (!strcmp("atime", tmp))
274                         mf &= ~MS_NOATIME;
275                 else if (!strcmp("noatime", tmp))
276                         mf |= MS_NOATIME;
277                 else if (!strcmp("defaults", tmp))
278                         mf = 0; /* rw, suid, dev, exec, auto, nouser, and async */
279                 else if (!strcmp("dev", tmp))
280                         mf &= ~MS_NODEV;
281                 else if (!strcmp("nodev", tmp))
282                         mf |= MS_NODEV;
283                 else if (!strcmp("iversion", tmp))
284                         mf |= MS_I_VERSION;
285                 else if (!strcmp("noiversion", tmp))
286                         mf &= ~MS_I_VERSION;
287                 else if (!strcmp("diratime", tmp))
288                         mf &= ~MS_NODIRATIME;
289                 else if (!strcmp("nodiratime", tmp))
290                         mf |= MS_NODIRATIME;
291                 else if (!strcmp("dirsync", tmp))
292                         mf |= MS_DIRSYNC;
293                 else if (!strcmp("exec", tmp))
294                         mf &= ~MS_NOEXEC;
295                 else if (!strcmp("noexec", tmp))
296                         mf |= MS_NOEXEC;
297                 else if (!strcmp("mand", tmp))
298                         mf |= MS_MANDLOCK;
299                 else if (!strcmp("nomand", tmp))
300                         mf &= ~MS_MANDLOCK;
301                 else if (!strcmp("relatime", tmp))
302                         mf |= MS_RELATIME;
303                 else if (!strcmp("norelatime", tmp))
304                         mf &= ~MS_RELATIME;
305                 else if (!strcmp("strictatime", tmp))
306                         mf |= MS_STRICTATIME;
307                 else if (!strcmp("nostrictatime", tmp))
308                         mf &= ~MS_STRICTATIME;
309                 else if (!strcmp("lazytime", tmp))
310                         mf |= MS_LAZYTIME;
311                 else if (!strcmp("nolazytime", tmp))
312                         mf &= ~MS_LAZYTIME;
313                 else if (!strcmp("suid", tmp))
314                         mf &= ~MS_NOSUID;
315                 else if (!strcmp("nosuid", tmp))
316                         mf |= MS_NOSUID;
317                 else if (!strcmp("remount", tmp))
318                         mf |= MS_REMOUNT;
319                 /* propagation flags */
320                 else if (!strcmp("private", tmp))
321                         pf |= MS_PRIVATE;
322                 else if (!strcmp("rprivate", tmp))
323                         pf |= MS_PRIVATE | MS_REC;
324                 else if (!strcmp("slave", tmp))
325                         pf |= MS_SLAVE;
326                 else if (!strcmp("rslave", tmp))
327                         pf |= MS_SLAVE | MS_REC;
328                 else if (!strcmp("shared", tmp))
329                         pf |= MS_SHARED;
330                 else if (!strcmp("rshared", tmp))
331                         pf |= MS_SHARED | MS_REC;
332                 else if (!strcmp("unbindable", tmp))
333                         pf |= MS_UNBINDABLE;
334                 else if (!strcmp("runbindable", tmp))
335                         pf |= MS_UNBINDABLE | MS_REC;
336                 /* special case: 'nofail' */
337                 else if(!strcmp("nofail", tmp))
338                         *error = 0;
339                 else if (!strcmp("auto", tmp) ||
340                          !strcmp("noauto", tmp) ||
341                          !strcmp("user", tmp) ||
342                          !strcmp("group", tmp) ||
343                          !strcmp("_netdev", tmp))
344                         DEBUG("ignoring built-in mount option %s\n", tmp);
345                 else {
346                         /* filesystem-specific free-form option */
347                         opt = calloc(1, sizeof(*opt));
348                         opt->optstr = tmp;
349                         list_add_tail(&opt->list, &fsopts);
350                 }
351         };
352 
353         *mount_flags = mf;
354         *propagation_flags = pf;
355 
356         list_for_each_entry(opt, &fsopts, list) {
357                 if (len)
358                         ++len;
359 
360                 len += strlen(opt->optstr);
361         };
362 
363         if (len) {
364                 *mount_data = calloc(len + 1, sizeof(char));
365                 if (!(*mount_data))
366                         return ENOMEM;
367 
368                 len = 0;
369                 list_for_each_entry(opt, &fsopts, list) {
370                         if (len)
371                                 strcat(*mount_data, ",");
372 
373                         strcat(*mount_data, opt->optstr);
374                         ++len;
375                 }
376 
377                 list_for_each_entry_safe(opt, tmpopt, &fsopts, list) {
378                         list_del(&opt->list);
379                         free(opt);
380                 }
381         }
382 
383         DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf, pf, *mount_data?:"");
384 
385         return 0;
386 }
387 
388 int parseOCImount(struct blob_attr *msg)
389 {
390         struct blob_attr *tb[__OCI_MOUNT_MAX];
391         unsigned long mount_flags = 0;
392         unsigned long propagation_flags = 0;
393         char *mount_data = NULL;
394         int ret, err = -1;
395 
396         blobmsg_parse(oci_mount_policy, __OCI_MOUNT_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
397 
398         if (!tb[OCI_MOUNT_DESTINATION])
399                 return EINVAL;
400 
401         if (tb[OCI_MOUNT_OPTIONS]) {
402                 ret = parseOCImountopts(tb[OCI_MOUNT_OPTIONS], &mount_flags, &propagation_flags, &mount_data, &err);
403                 if (ret)
404                         return ret;
405         }
406 
407         ret = add_mount(tb[OCI_MOUNT_SOURCE] ? blobmsg_get_string(tb[OCI_MOUNT_SOURCE]) : NULL,
408                   blobmsg_get_string(tb[OCI_MOUNT_DESTINATION]),
409                   tb[OCI_MOUNT_TYPE] ? blobmsg_get_string(tb[OCI_MOUNT_TYPE]) : NULL,
410                   mount_flags, propagation_flags, mount_data, err);
411 
412         if (mount_data)
413                 free(mount_data);
414 
415         return ret;
416 }
417 
418 static void build_noafile(void) {
419         int fd;
420 
421         fd = creat(UJAIL_NOAFILE, 0000);
422         if (fd < 0)
423                 return;
424 
425         close(fd);
426         return;
427 }
428 
429 int mount_all(const char *jailroot) {
430         struct library *l;
431         struct mount *m;
432 
433         build_noafile();
434 
435         avl_for_each_element(&libraries, l, avl)
436                 add_mount_bind(l->path, 1, -1);
437 
438         avl_for_each_element(&mounts, m, avl)
439                 if (do_mount(jailroot, m->source, m->target, m->filesystemtype, m->mountflags,
440                              m->propflags, m->optstr, m->error, m->inner))
441                         return -1;
442 
443         return 0;
444 }
445 
446 void mount_free(void) {
447         struct mount *m, *tmp;
448 
449         avl_remove_all_elements(&mounts, m, avl, tmp) {
450                 if (m->source != (void*)(-1))
451                         free((void*)m->source);
452                 free((void*)m->target);
453                 free((void*)m->filesystemtype);
454                 free((void*)m->optstr);
455                 free(m);
456         }
457 }
458 
459 void mount_list_init(void) {
460         avl_init(&mounts, avl_strcmp, false, NULL);
461 }
462 
463 static int add_script_interp(const char *path, const char *map, int size)
464 {
465         int start = 2;
466         while (start < size && map[start] != '/') {
467                 start++;
468         }
469         if (start >= size) {
470                 ERROR("bad script interp (%s)\n", path);
471                 return -1;
472         }
473         int stop = start + 1;
474         while (stop < size && map[stop] > 0x20 && map[stop] <= 0x7e) {
475                 stop++;
476         }
477         if (stop >= size || (stop-start) > PATH_MAX) {
478                 ERROR("bad script interp (%s)\n", path);
479                 return -1;
480         }
481         char buf[PATH_MAX];
482         strncpy(buf, map+start, stop-start);
483         return add_path_and_deps(buf, 1, -1, 0);
484 }
485 
486 int add_2paths_and_deps(const char *path, const char *path2, int readonly, int error, int lib)
487 {
488         assert(path != NULL);
489         assert(path2 != NULL);
490 
491         if (lib == 0 && path[0] != '/') {
492                 ERROR("%s is not an absolute path\n", path);
493                 return error;
494         }
495 
496         char *map = NULL;
497         int fd, ret = -1;
498         if (path[0] == '/') {
499                 if (avl_find(&mounts, path2))
500                         return 0;
501                 fd = open(path, O_RDONLY|O_CLOEXEC);
502                 if (fd < 0)
503                         return error;
504                 _add_mount_bind(path, path2, readonly, error);
505         } else {
506                 if (avl_find(&libraries, path))
507                         return 0;
508                 char *fullpath;
509                 fd = lib_open(&fullpath, path);
510                 if (fd < 0)
511                         return error;
512                 if (fullpath) {
513                         alloc_library(fullpath, path);
514                         free(fullpath);
515                 }
516         }
517 
518         struct stat s;
519         if (fstat(fd, &s) == -1) {
520                 ERROR("fstat(%s) failed: %m\n", path);
521                 ret = error;
522                 goto out;
523         }
524 
525         if (!S_ISREG(s.st_mode)) {
526                 ret = 0;
527                 goto out;
528         }
529 
530         /* too small to be an ELF or a script -> "normal" file */
531         if (s.st_size < 4) {
532                 ret = 0;
533                 goto out;
534         }
535 
536         map = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
537         if (map == MAP_FAILED) {
538                 ERROR("failed to mmap %s: %m\n", path);
539                 ret = -1;
540                 goto out;
541         }
542 
543         if (map[0] == '#' && map[1] == '!') {
544                 ret = add_script_interp(path, map, s.st_size);
545                 goto out;
546         }
547 
548         if (map[0] == ELFMAG0 && map[1] == ELFMAG1 && map[2] == ELFMAG2 && map[3] == ELFMAG3) {
549                 ret = elf_load_deps(path, map);
550                 goto out;
551         }
552 
553         ret = 0;
554 
555 out:
556         if (fd >= 0)
557                 close(fd);
558         if (map)
559                 munmap(map, s.st_size);
560 
561         return ret;
562 }
563 

This page was automatically generated by LXR 0.3.1.  •  OpenWrt