Merge lp:~jdstrand/snap-confine/coding-style into lp:~snappy-dev/snap-confine/trunk
- coding-style
- Merge into trunk
Proposed by
Jamie Strandboge
Status: | Merged |
---|---|
Merged at revision: | 101 |
Proposed branch: | lp:~jdstrand/snap-confine/coding-style |
Merge into: | lp:~snappy-dev/snap-confine/trunk |
Diff against target: |
1493 lines (+706/-669) 8 files modified
Makefile (+4/-1) debian/changelog (+4/-0) debian/control (+1/-1) src/Makefile (+20/-1) src/main.c (+492/-490) src/seccomp.c (+129/-122) src/utils.c (+52/-50) src/utils.h (+4/-4) |
To merge this branch: | bzr merge lp:~jdstrand/snap-confine/coding-style |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Vogt (community) | Approve | ||
Review via email:
|
Commit message
Description of the change
* enforce coding style:
- add syntax-check and fmt Makefile targets
- use 'indent -linux'
- debian/control: Build-Depends on indent
To post a comment you must log in.
Revision history for this message
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Jamie Strandboge (jdstrand) wrote : | # |
hrmm, yes, oh well. Thanks!
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'Makefile' | |||
2 | --- Makefile 2016-02-25 15:02:17 +0000 | |||
3 | +++ Makefile 2016-03-22 12:19:24 +0000 | |||
4 | @@ -8,5 +8,8 @@ | |||
5 | 8 | make -C src $@ | 8 | make -C src $@ |
6 | 9 | make -C tests $@ | 9 | make -C tests $@ |
7 | 10 | 10 | ||
9 | 11 | check: all | 11 | syntax-check: |
10 | 12 | make -C src syntax-check | ||
11 | 13 | |||
12 | 14 | check: all syntax-check | ||
13 | 12 | make -C tests test | 15 | make -C tests test |
14 | 13 | 16 | ||
15 | === modified file 'debian/changelog' | |||
16 | --- debian/changelog 2016-03-22 11:23:59 +0000 | |||
17 | +++ debian/changelog 2016-03-22 12:19:24 +0000 | |||
18 | @@ -1,6 +1,10 @@ | |||
19 | 1 | ubuntu-core-launcher (1.0.21) UNRELEASED; urgency=medium | 1 | ubuntu-core-launcher (1.0.21) UNRELEASED; urgency=medium |
20 | 2 | * src/main.c: setup private /dev/pts | 2 | * src/main.c: setup private /dev/pts |
21 | 3 | * debian/usr.bin.ubuntu-core-launcher: allow mounting /dev/pts | 3 | * debian/usr.bin.ubuntu-core-launcher: allow mounting /dev/pts |
22 | 4 | * enforce coding style: | ||
23 | 5 | - add syntax-check and fmt Makefile targets | ||
24 | 6 | - use 'indent -linux' | ||
25 | 7 | - debian/control: Build-Depends on indent | ||
26 | 4 | 8 | ||
27 | 5 | -- Jamie Strandboge <jamie@ubuntu.com> Tue, 22 Mar 2016 06:23:38 -0500 | 9 | -- Jamie Strandboge <jamie@ubuntu.com> Tue, 22 Mar 2016 06:23:38 -0500 |
28 | 6 | 10 | ||
29 | 7 | 11 | ||
30 | === modified file 'debian/control' | |||
31 | --- debian/control 2016-01-26 15:11:20 +0000 | |||
32 | +++ debian/control 2016-03-22 12:19:24 +0000 | |||
33 | @@ -2,7 +2,7 @@ | |||
34 | 2 | Section: utils | 2 | Section: utils |
35 | 3 | Priority: optional | 3 | Priority: optional |
36 | 4 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> | 4 | Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com> |
38 | 5 | Build-Depends: debhelper (>= 9), libseccomp-dev, libapparmor-dev, libudev-dev, dh-apparmor | 5 | Build-Depends: debhelper (>= 9), libseccomp-dev, libapparmor-dev, libudev-dev, dh-apparmor, indent |
39 | 6 | Standards-Version: 3.9.6 | 6 | Standards-Version: 3.9.6 |
40 | 7 | Vcs-Bzr: lp:~snappy-dev/ubuntu-core-launcher/trunk | 7 | Vcs-Bzr: lp:~snappy-dev/ubuntu-core-launcher/trunk |
41 | 8 | 8 | ||
42 | 9 | 9 | ||
43 | === modified file 'src/Makefile' | |||
44 | --- src/Makefile 2015-05-06 11:26:07 +0000 | |||
45 | +++ src/Makefile 2016-03-22 12:19:24 +0000 | |||
46 | @@ -2,8 +2,11 @@ | |||
47 | 2 | CFLAGS= -D_GNU_SOURCE -O2 -Wall -Werror $(shell dpkg-buildflags --get CFLAGS) | 2 | CFLAGS= -D_GNU_SOURCE -O2 -Wall -Werror $(shell dpkg-buildflags --get CFLAGS) |
48 | 3 | LD_FLAGS = $(shell dpkg-buildflags --get LDFLAGS) | 3 | LD_FLAGS = $(shell dpkg-buildflags --get LDFLAGS) |
49 | 4 | LIBS = -lapparmor -lseccomp -ludev | 4 | LIBS = -lapparmor -lseccomp -ludev |
50 | 5 | TMPDIR = ./tmp | ||
51 | 6 | FMT = indent -linux | ||
52 | 5 | 7 | ||
53 | 6 | BIN = ubuntu-core-launcher | 8 | BIN = ubuntu-core-launcher |
54 | 9 | HDRS = $(wildcard *.h) | ||
55 | 7 | SRCS = $(wildcard *.c) | 10 | SRCS = $(wildcard *.c) |
56 | 8 | OBJS = $(SRCS:.c=.o) | 11 | OBJS = $(SRCS:.c=.o) |
57 | 9 | 12 | ||
58 | @@ -15,7 +18,23 @@ | |||
59 | 15 | 18 | ||
60 | 16 | distclean: clean | 19 | distclean: clean |
61 | 17 | clean: | 20 | clean: |
63 | 18 | rm -f *.o ${BIN} | 21 | rm -f *.o $(BIN) *~ |
64 | 22 | rm -rf $(TMPDIR) | ||
65 | 23 | |||
66 | 24 | fmt: | ||
67 | 25 | for f in $(HDRS) $(SRCS); do \ | ||
68 | 26 | echo "$(FMT) $$f ... "; \ | ||
69 | 27 | $(FMT) "$$f"; \ | ||
70 | 28 | done; \ | ||
71 | 29 | |||
72 | 30 | syntax-check: | ||
73 | 31 | $(shell mkdir $(TMPDIR) 2>/dev/null) | ||
74 | 32 | for f in $(HDRS) $(SRCS); do \ | ||
75 | 33 | out=$(TMPDIR)/$$f.out; \ | ||
76 | 34 | echo "Checking '$(FMT) $$f' ... "; \ | ||
77 | 35 | $(FMT) "$$f" -o "$$out"; \ | ||
78 | 36 | diff -Naur "$$f" "$$out" || exit 1; \ | ||
79 | 37 | done; \ | ||
80 | 19 | 38 | ||
81 | 20 | install: | 39 | install: |
82 | 21 | # create dirs | 40 | # create dirs |
83 | 22 | 41 | ||
84 | === modified file 'src/main.c' | |||
85 | --- src/main.c 2016-03-21 17:33:08 +0000 | |||
86 | +++ src/main.c 2016-03-22 12:19:24 +0000 | |||
87 | @@ -41,498 +41,500 @@ | |||
88 | 41 | 41 | ||
89 | 42 | #define MAX_BUF 1000 | 42 | #define MAX_BUF 1000 |
90 | 43 | 43 | ||
480 | 44 | bool verify_appname(const char *appname) { | 44 | bool verify_appname(const char *appname) |
481 | 45 | // these chars are allowed in a appname | 45 | { |
482 | 46 | const char* whitelist_re = "^[a-z0-9][a-z0-9+._-]+$"; | 46 | // these chars are allowed in a appname |
483 | 47 | regex_t re; | 47 | const char *whitelist_re = "^[a-z0-9][a-z0-9+._-]+$"; |
484 | 48 | if (regcomp(&re, whitelist_re, REG_EXTENDED|REG_NOSUB) != 0) | 48 | regex_t re; |
485 | 49 | die("can not compile regex %s", whitelist_re); | 49 | if (regcomp(&re, whitelist_re, REG_EXTENDED | REG_NOSUB) != 0) |
486 | 50 | 50 | die("can not compile regex %s", whitelist_re); | |
487 | 51 | int status = regexec(&re, appname, 0, NULL, 0); | 51 | |
488 | 52 | regfree(&re); | 52 | int status = regexec(&re, appname, 0, NULL, 0); |
489 | 53 | 53 | regfree(&re); | |
490 | 54 | return (status == 0); | 54 | |
491 | 55 | } | 55 | return (status == 0); |
492 | 56 | 56 | } | |
493 | 57 | void run_snappy_app_dev_add(struct udev *u, const char *path, const char *appname) { | 57 | |
494 | 58 | debug("run_snappy_app_dev_add: %s %s", path, appname); | 58 | void run_snappy_app_dev_add(struct udev *u, const char *path, |
495 | 59 | struct udev_device *d = udev_device_new_from_syspath(u, path); | 59 | const char *appname) |
496 | 60 | if (d == NULL) | 60 | { |
497 | 61 | die("can not find %s", path); | 61 | debug("run_snappy_app_dev_add: %s %s", path, appname); |
498 | 62 | dev_t devnum = udev_device_get_devnum (d); | 62 | struct udev_device *d = udev_device_new_from_syspath(u, path); |
499 | 63 | udev_device_unref(d); | 63 | if (d == NULL) |
500 | 64 | 64 | die("can not find %s", path); | |
501 | 65 | int status = 0; | 65 | dev_t devnum = udev_device_get_devnum(d); |
502 | 66 | pid_t pid = fork(); | 66 | udev_device_unref(d); |
503 | 67 | if (pid == 0) { | 67 | |
504 | 68 | char buf[64]; | 68 | int status = 0; |
505 | 69 | unsigned major = MAJOR(devnum); | 69 | pid_t pid = fork(); |
506 | 70 | unsigned minor = MINOR(devnum); | 70 | if (pid == 0) { |
507 | 71 | must_snprintf(buf, sizeof(buf), "%u:%u", major, minor); | 71 | char buf[64]; |
508 | 72 | if(execl("/lib/udev/snappy-app-dev", "/lib/udev/snappy-app-dev", "add", appname, path, buf, NULL) != 0) | 72 | unsigned major = MAJOR(devnum); |
509 | 73 | die("execlp failed"); | 73 | unsigned minor = MINOR(devnum); |
510 | 74 | } | 74 | must_snprintf(buf, sizeof(buf), "%u:%u", major, minor); |
511 | 75 | if(waitpid(pid, &status, 0) < 0) | 75 | if (execl |
512 | 76 | die("waitpid failed"); | 76 | ("/lib/udev/snappy-app-dev", "/lib/udev/snappy-app-dev", |
513 | 77 | if(WIFEXITED(status) && WEXITSTATUS(status) != 0) | 77 | "add", appname, path, buf, NULL) != 0) |
514 | 78 | die("child exited with status %i", WEXITSTATUS(status)); | 78 | die("execlp failed"); |
515 | 79 | else if(WIFSIGNALED(status)) | 79 | } |
516 | 80 | die("child died with signal %i", WTERMSIG(status)); | 80 | if (waitpid(pid, &status, 0) < 0) |
517 | 81 | } | 81 | die("waitpid failed"); |
518 | 82 | 82 | if (WIFEXITED(status) && WEXITSTATUS(status) != 0) | |
519 | 83 | void setup_udev_snappy_assign(const char *appname) { | 83 | die("child exited with status %i", WEXITSTATUS(status)); |
520 | 84 | debug("setup_udev_snappy_assign"); | 84 | else if (WIFSIGNALED(status)) |
521 | 85 | 85 | die("child died with signal %i", WTERMSIG(status)); | |
522 | 86 | struct udev *u = udev_new(); | 86 | } |
523 | 87 | if (u == NULL) | 87 | |
524 | 88 | die("udev_new failed"); | 88 | void setup_udev_snappy_assign(const char *appname) |
525 | 89 | 89 | { | |
526 | 90 | const char* static_devices[] = { | 90 | debug("setup_udev_snappy_assign"); |
527 | 91 | "/sys/class/mem/null", | 91 | |
528 | 92 | "/sys/class/mem/full", | 92 | struct udev *u = udev_new(); |
529 | 93 | "/sys/class/mem/zero", | 93 | if (u == NULL) |
530 | 94 | "/sys/class/mem/random", | 94 | die("udev_new failed"); |
531 | 95 | "/sys/class/mem/urandom", | 95 | |
532 | 96 | "/sys/class/tty/tty", | 96 | const char *static_devices[] = { |
533 | 97 | "/sys/class/tty/console", | 97 | "/sys/class/mem/null", |
534 | 98 | "/sys/class/tty/ptmx", | 98 | "/sys/class/mem/full", |
535 | 99 | NULL, | 99 | "/sys/class/mem/zero", |
536 | 100 | }; | 100 | "/sys/class/mem/random", |
537 | 101 | int i; | 101 | "/sys/class/mem/urandom", |
538 | 102 | for(i=0; static_devices[i] != NULL; i++) { | 102 | "/sys/class/tty/tty", |
539 | 103 | run_snappy_app_dev_add(u, static_devices[i], appname); | 103 | "/sys/class/tty/console", |
540 | 104 | } | 104 | "/sys/class/tty/ptmx", |
541 | 105 | 105 | NULL, | |
542 | 106 | struct udev_enumerate *devices = udev_enumerate_new(u); | 106 | }; |
543 | 107 | if (devices == NULL) | 107 | int i; |
544 | 108 | die("udev_enumerate_new failed"); | 108 | for (i = 0; static_devices[i] != NULL; i++) { |
545 | 109 | 109 | run_snappy_app_dev_add(u, static_devices[i], appname); | |
546 | 110 | if (udev_enumerate_add_match_tag (devices, "snappy-assign") != 0) | 110 | } |
547 | 111 | die("udev_enumerate_add_match_tag"); | 111 | |
548 | 112 | 112 | struct udev_enumerate *devices = udev_enumerate_new(u); | |
549 | 113 | if(udev_enumerate_add_match_property (devices, "SNAPPY_APP", appname) != 0) | 113 | if (devices == NULL) |
550 | 114 | die("udev_enumerate_add_match_property"); | 114 | die("udev_enumerate_new failed"); |
551 | 115 | 115 | ||
552 | 116 | if(udev_enumerate_scan_devices(devices) != 0) | 116 | if (udev_enumerate_add_match_tag(devices, "snappy-assign") != 0) |
553 | 117 | die("udev_enumerate_scan failed"); | 117 | die("udev_enumerate_add_match_tag"); |
554 | 118 | 118 | ||
555 | 119 | struct udev_list_entry *l = udev_enumerate_get_list_entry (devices); | 119 | if (udev_enumerate_add_match_property(devices, "SNAPPY_APP", appname) != |
556 | 120 | while (l != NULL) { | 120 | 0) |
557 | 121 | const char *path = udev_list_entry_get_name (l); | 121 | die("udev_enumerate_add_match_property"); |
558 | 122 | if (path == NULL) | 122 | |
559 | 123 | die("udev_list_entry_get_name failed"); | 123 | if (udev_enumerate_scan_devices(devices) != 0) |
560 | 124 | run_snappy_app_dev_add(u, path, appname); | 124 | die("udev_enumerate_scan failed"); |
561 | 125 | l = udev_list_entry_get_next(l); | 125 | |
562 | 126 | } | 126 | struct udev_list_entry *l = udev_enumerate_get_list_entry(devices); |
563 | 127 | 127 | while (l != NULL) { | |
564 | 128 | udev_enumerate_unref(devices); | 128 | const char *path = udev_list_entry_get_name(l); |
565 | 129 | udev_unref(u); | 129 | if (path == NULL) |
566 | 130 | } | 130 | die("udev_list_entry_get_name failed"); |
567 | 131 | 131 | run_snappy_app_dev_add(u, path, appname); | |
568 | 132 | void setup_devices_cgroup(const char *appname) { | 132 | l = udev_list_entry_get_next(l); |
569 | 133 | debug("setup_devices_cgroup"); | 133 | } |
570 | 134 | 134 | ||
571 | 135 | // extra paranoia | 135 | udev_enumerate_unref(devices); |
572 | 136 | if(!verify_appname(appname)) | 136 | udev_unref(u); |
573 | 137 | die("appname %s not allowed", appname); | 137 | } |
574 | 138 | 138 | ||
575 | 139 | // create devices cgroup controller | 139 | void setup_devices_cgroup(const char *appname) |
576 | 140 | char cgroup_dir[PATH_MAX]; | 140 | { |
577 | 141 | must_snprintf(cgroup_dir, sizeof(cgroup_dir), "/sys/fs/cgroup/devices/snappy.%s/", appname); | 141 | debug("setup_devices_cgroup"); |
578 | 142 | 142 | ||
579 | 143 | if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST) | 143 | // extra paranoia |
580 | 144 | die("mkdir failed"); | 144 | if (!verify_appname(appname)) |
581 | 145 | 145 | die("appname %s not allowed", appname); | |
582 | 146 | // move ourselves into it | 146 | |
583 | 147 | char cgroup_file[PATH_MAX]; | 147 | // create devices cgroup controller |
584 | 148 | must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, "tasks"); | 148 | char cgroup_dir[PATH_MAX]; |
585 | 149 | 149 | must_snprintf(cgroup_dir, sizeof(cgroup_dir), | |
586 | 150 | char buf[128]; | 150 | "/sys/fs/cgroup/devices/snappy.%s/", appname); |
587 | 151 | must_snprintf(buf, sizeof(buf), "%i", getpid()); | 151 | |
588 | 152 | write_string_to_file(cgroup_file, buf); | 152 | if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST) |
589 | 153 | 153 | die("mkdir failed"); | |
590 | 154 | // deny by default | 154 | |
591 | 155 | must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, "devices.deny"); | 155 | // move ourselves into it |
592 | 156 | write_string_to_file(cgroup_file, "a"); | 156 | char cgroup_file[PATH_MAX]; |
593 | 157 | 157 | must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, | |
594 | 158 | } | 158 | "tasks"); |
595 | 159 | 159 | ||
596 | 160 | bool snappy_udev_setup_required(const char *appname) { | 160 | char buf[128]; |
597 | 161 | debug("snappy_udev_setup_required"); | 161 | must_snprintf(buf, sizeof(buf), "%i", getpid()); |
598 | 162 | 162 | write_string_to_file(cgroup_file, buf); | |
599 | 163 | // extra paranoia | 163 | |
600 | 164 | if(!verify_appname(appname)) | 164 | // deny by default |
601 | 165 | die("appname %s not allowed", appname); | 165 | must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir, |
602 | 166 | 166 | "devices.deny"); | |
603 | 167 | char override_file[PATH_MAX]; | 167 | write_string_to_file(cgroup_file, "a"); |
604 | 168 | must_snprintf(override_file, sizeof(override_file), "/var/lib/apparmor/clicks/%s.json.additional", appname); | 168 | |
605 | 169 | 169 | } | |
606 | 170 | // if a snap package gets unrestricted apparmor access we need to setup | 170 | |
607 | 171 | // a device cgroup. | 171 | bool snappy_udev_setup_required(const char *appname) |
608 | 172 | // | 172 | { |
609 | 173 | // the "needle" string is what gives this access so we search for that | 173 | debug("snappy_udev_setup_required"); |
610 | 174 | // here | 174 | |
611 | 175 | const char *needle = | 175 | // extra paranoia |
612 | 176 | "{" "\n" | 176 | if (!verify_appname(appname)) |
613 | 177 | " \"write_path\": [" "\n" | 177 | die("appname %s not allowed", appname); |
614 | 178 | " \"/dev/**\"" "\n" | 178 | |
615 | 179 | " ]," "\n" | 179 | char override_file[PATH_MAX]; |
616 | 180 | " \"read_path\": [" "\n" | 180 | must_snprintf(override_file, sizeof(override_file), |
617 | 181 | " \"/run/udev/data/*\"" "\n" | 181 | "/var/lib/apparmor/clicks/%s.json.additional", appname); |
618 | 182 | " ]\n" | 182 | |
619 | 183 | "}"; | 183 | // if a snap package gets unrestricted apparmor access we need to setup |
620 | 184 | debug("looking for: '%s'", needle); | 184 | // a device cgroup. |
621 | 185 | char content[strlen(needle)]; | 185 | // |
622 | 186 | 186 | // the "needle" string is what gives this access so we search for that | |
623 | 187 | int fd = open(override_file, O_CLOEXEC | O_NOFOLLOW | O_RDONLY); | 187 | // here |
624 | 188 | if (fd < 0) | 188 | const char *needle = |
625 | 189 | return false; | 189 | "{" "\n" |
626 | 190 | int n = read(fd, content, sizeof(content)); | 190 | " \"write_path\": [" "\n" |
627 | 191 | close(fd); | 191 | " \"/dev/**\"" "\n" |
628 | 192 | if (n < sizeof(content)) | 192 | " ]," "\n" |
629 | 193 | return false; | 193 | " \"read_path\": [" "\n" " \"/run/udev/data/*\"" "\n" " ]\n" "}"; |
630 | 194 | 194 | debug("looking for: '%s'", needle); | |
631 | 195 | // memcpy so that we don't have to deal with \0 in the input | 195 | char content[strlen(needle)]; |
632 | 196 | if (memcmp(content, needle, strlen(needle)) == 0) { | 196 | |
633 | 197 | debug("found needle, need to apply udev setup"); | 197 | int fd = open(override_file, O_CLOEXEC | O_NOFOLLOW | O_RDONLY); |
634 | 198 | return true; | 198 | if (fd < 0) |
635 | 199 | } | 199 | return false; |
636 | 200 | 200 | int n = read(fd, content, sizeof(content)); | |
637 | 201 | return false; | 201 | close(fd); |
638 | 202 | } | 202 | if (n < sizeof(content)) |
639 | 203 | 203 | return false; | |
640 | 204 | bool is_running_on_classic_ubuntu() { | 204 | |
641 | 205 | return (access("/var/lib/dpkg/status", F_OK) == 0); | 205 | // memcpy so that we don't have to deal with \0 in the input |
642 | 206 | } | 206 | if (memcmp(content, needle, strlen(needle)) == 0) { |
643 | 207 | 207 | debug("found needle, need to apply udev setup"); | |
644 | 208 | void setup_private_mount(const char* appname) { | 208 | return true; |
645 | 209 | uid_t uid = getuid(); | 209 | } |
646 | 210 | gid_t gid = getgid(); | 210 | |
647 | 211 | char tmpdir[MAX_BUF] = {0}; | 211 | return false; |
648 | 212 | 212 | } | |
649 | 213 | // Create a 0700 base directory, this is the base dir that is | 213 | |
650 | 214 | // protected from other users. | 214 | bool is_running_on_classic_ubuntu() |
651 | 215 | // | 215 | { |
652 | 216 | // Under that basedir, we put a 1777 /tmp dir that is then bind | 216 | return (access("/var/lib/dpkg/status", F_OK) == 0); |
653 | 217 | // mounted for the applications to use | 217 | } |
654 | 218 | must_snprintf(tmpdir, sizeof(tmpdir), "/tmp/snap.%d_%s_XXXXXX", uid, appname); | 218 | |
655 | 219 | if (mkdtemp(tmpdir) == NULL) { | 219 | void setup_private_mount(const char *appname) |
656 | 220 | die("unable to create tmpdir"); | 220 | { |
657 | 221 | } | 221 | uid_t uid = getuid(); |
658 | 222 | 222 | gid_t gid = getgid(); | |
659 | 223 | // now we create a 1777 /tmp inside our private dir | 223 | char tmpdir[MAX_BUF] = { 0 }; |
660 | 224 | mode_t old_mask = umask(0); | 224 | |
661 | 225 | char *d = strdup(tmpdir); | 225 | // Create a 0700 base directory, this is the base dir that is |
662 | 226 | if (!d) { | 226 | // protected from other users. |
663 | 227 | die("Out of memory"); | 227 | // |
664 | 228 | } | 228 | // Under that basedir, we put a 1777 /tmp dir that is then bind |
665 | 229 | must_snprintf(tmpdir, sizeof(tmpdir), "%s/tmp", d); | 229 | // mounted for the applications to use |
666 | 230 | free(d); | 230 | must_snprintf(tmpdir, sizeof(tmpdir), "/tmp/snap.%d_%s_XXXXXX", uid, |
667 | 231 | 231 | appname); | |
668 | 232 | if (mkdir(tmpdir, 01777) != 0) { | 232 | if (mkdtemp(tmpdir) == NULL) { |
669 | 233 | die("unable to create /tmp inside private dir"); | 233 | die("unable to create tmpdir"); |
670 | 234 | } | 234 | } |
671 | 235 | umask(old_mask); | 235 | // now we create a 1777 /tmp inside our private dir |
672 | 236 | 236 | mode_t old_mask = umask(0); | |
673 | 237 | // MS_BIND is there from linux 2.4 | 237 | char *d = strdup(tmpdir); |
674 | 238 | if (mount(tmpdir, "/tmp", NULL, MS_BIND, NULL) != 0) { | 238 | if (!d) { |
675 | 239 | die("unable to bind private /tmp"); | 239 | die("Out of memory"); |
676 | 240 | } | 240 | } |
677 | 241 | // MS_PRIVATE needs linux > 2.6.11 | 241 | must_snprintf(tmpdir, sizeof(tmpdir), "%s/tmp", d); |
678 | 242 | if (mount("none", "/tmp", NULL, MS_PRIVATE, NULL) != 0) { | 242 | free(d); |
679 | 243 | die("unable to make /tmp/ private"); | 243 | |
680 | 244 | } | 244 | if (mkdir(tmpdir, 01777) != 0) { |
681 | 245 | 245 | die("unable to create /tmp inside private dir"); | |
682 | 246 | // do the chown after the bind mount to avoid potential shenanigans | 246 | } |
683 | 247 | if (chown("/tmp/", uid, gid) < 0) { | 247 | umask(old_mask); |
684 | 248 | die("unable to chown tmpdir"); | 248 | |
685 | 249 | } | 249 | // MS_BIND is there from linux 2.4 |
686 | 250 | 250 | if (mount(tmpdir, "/tmp", NULL, MS_BIND, NULL) != 0) { | |
687 | 251 | // ensure we set the various TMPDIRs to our newly created tmpdir | 251 | die("unable to bind private /tmp"); |
688 | 252 | const char *tmpd[] = {"TMPDIR", "TEMPDIR", "SNAP_APP_TMPDIR", NULL}; | 252 | } |
689 | 253 | int i; | 253 | // MS_PRIVATE needs linux > 2.6.11 |
690 | 254 | for (i=0; tmpd[i] != NULL; i++) { | 254 | if (mount("none", "/tmp", NULL, MS_PRIVATE, NULL) != 0) { |
691 | 255 | if (setenv(tmpd[i], "/tmp", 1) != 0) { | 255 | die("unable to make /tmp/ private"); |
692 | 256 | die("unable to set '%s'", tmpd[i]); | 256 | } |
693 | 257 | } | 257 | // do the chown after the bind mount to avoid potential shenanigans |
694 | 258 | } | 258 | if (chown("/tmp/", uid, gid) < 0) { |
695 | 259 | } | 259 | die("unable to chown tmpdir"); |
696 | 260 | 260 | } | |
697 | 261 | void setup_private_pts() { | 261 | // ensure we set the various TMPDIRs to our newly created tmpdir |
698 | 262 | // See https://www.kernel.org/doc/Documentation/filesystems/devpts.txt | 262 | const char *tmpd[] = { "TMPDIR", "TEMPDIR", "SNAP_APP_TMPDIR", NULL }; |
699 | 263 | // | 263 | int i; |
700 | 264 | // Ubuntu by default uses devpts 'single-instance' mode where /dev/pts/ptmx | 264 | for (i = 0; tmpd[i] != NULL; i++) { |
701 | 265 | // is mounted with ptmxmode=0000. We don't want to change the startup | 265 | if (setenv(tmpd[i], "/tmp", 1) != 0) { |
702 | 266 | // scripts though, so we follow the instructions in point '4' of | 266 | die("unable to set '%s'", tmpd[i]); |
703 | 267 | // 'User-space changes' in the above doc. In other words, after | 267 | } |
704 | 268 | // unshare(CLONE_NEWNS), we mount devpts with -o newinstance,ptmxmode=0666 | 268 | } |
705 | 269 | // and then bind mount /dev/pts/ptmx onto /dev/ptmx | 269 | } |
706 | 270 | 270 | ||
707 | 271 | struct stat st; | 271 | void setup_private_pts() |
708 | 272 | 272 | { | |
709 | 273 | // Make sure /dev/pts/ptmx exists, otherwise we are in legacy mode which | 273 | // See https://www.kernel.org/doc/Documentation/filesystems/devpts.txt |
710 | 274 | // doesn't provide the isolation we require. | 274 | // |
711 | 275 | if (stat("/dev/pts/ptmx", &st) != 0) { | 275 | // Ubuntu by default uses devpts 'single-instance' mode where /dev/pts/ptmx |
712 | 276 | die("/dev/pts/ptmx does not exist"); | 276 | // is mounted with ptmxmode=0000. We don't want to change the startup |
713 | 277 | } | 277 | // scripts though, so we follow the instructions in point '4' of |
714 | 278 | // Make sure /dev/ptmx exists so we can bind mount over it | 278 | // 'User-space changes' in the above doc. In other words, after |
715 | 279 | if (stat("/dev/ptmx", &st) != 0) { | 279 | // unshare(CLONE_NEWNS), we mount devpts with -o newinstance,ptmxmode=0666 |
716 | 280 | die("/dev/ptmx does not exist"); | 280 | // and then bind mount /dev/pts/ptmx onto /dev/ptmx |
717 | 281 | } | 281 | |
718 | 282 | 282 | struct stat st; | |
719 | 283 | // Since multi-instance, use ptmxmode=0666. The other options are copied | 283 | |
720 | 284 | // from /etc/default/devpts | 284 | // Make sure /dev/pts/ptmx exists, otherwise we are in legacy mode which |
721 | 285 | if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, | 285 | // doesn't provide the isolation we require. |
722 | 286 | "newinstance,ptmxmode=0666,mode=0620,gid=5")) { | 286 | if (stat("/dev/pts/ptmx", &st) != 0) { |
723 | 287 | die("unable to mount a new instance of '/dev/pts'"); | 287 | die("/dev/pts/ptmx does not exist"); |
724 | 288 | } | 288 | } |
725 | 289 | 289 | // Make sure /dev/ptmx exists so we can bind mount over it | |
726 | 290 | if (mount("/dev/pts/ptmx", "/dev/ptmx", "none", MS_BIND, 0)) { | 290 | if (stat("/dev/ptmx", &st) != 0) { |
727 | 291 | die("unable to mount '/dev/pts/ptmx'->'/dev/ptmx'"); | 291 | die("/dev/ptmx does not exist"); |
728 | 292 | } | 292 | } |
729 | 293 | } | 293 | // Since multi-instance, use ptmxmode=0666. The other options are copied |
730 | 294 | 294 | // from /etc/default/devpts | |
731 | 295 | void setup_snappy_os_mounts() { | 295 | if (mount("devpts", "/dev/pts", "devpts", MS_MGC_VAL, |
732 | 296 | debug("setup_snappy_os_mounts()\n"); | 296 | "newinstance,ptmxmode=0666,mode=0620,gid=5")) { |
733 | 297 | 297 | die("unable to mount a new instance of '/dev/pts'"); | |
734 | 298 | // FIXME: hardcoded "ubuntu-core.*" | 298 | } |
735 | 299 | glob_t glob_res; | 299 | |
736 | 300 | if (glob("/snaps/ubuntu-core*/current/", 0, NULL, &glob_res) != 0) { | 300 | if (mount("/dev/pts/ptmx", "/dev/ptmx", "none", MS_BIND, 0)) { |
737 | 301 | die("can not find a snappy os"); | 301 | die("unable to mount '/dev/pts/ptmx'->'/dev/ptmx'"); |
738 | 302 | } | 302 | } |
739 | 303 | if ((glob_res.gl_pathc =! 1)) { | 303 | } |
740 | 304 | die("expected 1 os snap, found %i", (int)glob_res.gl_pathc); | 304 | |
741 | 305 | } | 305 | void setup_snappy_os_mounts() |
742 | 306 | char *mountpoint = glob_res.gl_pathv[0]; | 306 | { |
743 | 307 | 307 | debug("setup_snappy_os_mounts()\n"); | |
744 | 308 | // we mount some whitelisted directories | 308 | |
745 | 309 | // | 309 | // FIXME: hardcoded "ubuntu-core.*" |
746 | 310 | // Note that we do not mount "/etc/" from snappy. We could do that, | 310 | glob_t glob_res; |
747 | 311 | // but if we do we need to ensure that data like /etc/{hostname,hosts, | 311 | if (glob("/snaps/ubuntu-core*/current/", 0, NULL, &glob_res) != 0) { |
748 | 312 | // passwd,groups} is in sync between the two systems (probably via | 312 | die("can not find a snappy os"); |
749 | 313 | // selected bind mounts of those files). | 313 | } |
750 | 314 | const char *mounts[] = {"/bin", "/sbin", "/lib", "/lib64", "/usr"}; | 314 | if ((glob_res.gl_pathc = !1)) { |
751 | 315 | for (int i=0; i < sizeof(mounts)/sizeof(char*); i++) { | 315 | die("expected 1 os snap, found %i", (int)glob_res.gl_pathc); |
752 | 316 | // we mount the OS snap /bin over the real /bin in this NS | 316 | } |
753 | 317 | const char *dst = mounts[i]; | 317 | char *mountpoint = glob_res.gl_pathv[0]; |
754 | 318 | 318 | ||
755 | 319 | char buf[512]; | 319 | // we mount some whitelisted directories |
756 | 320 | must_snprintf(buf, sizeof(buf), "%s%s", mountpoint, dst); | 320 | // |
757 | 321 | const char *src = buf; | 321 | // Note that we do not mount "/etc/" from snappy. We could do that, |
758 | 322 | 322 | // but if we do we need to ensure that data like /etc/{hostname,hosts, | |
759 | 323 | debug("mounting %s -> %s\n", src, dst); | 323 | // passwd,groups} is in sync between the two systems (probably via |
760 | 324 | if (mount(src, dst, NULL, MS_BIND, NULL) != 0) { | 324 | // selected bind mounts of those files). |
761 | 325 | die("unable to bind %s to %s", src, dst); | 325 | const char *mounts[] = { "/bin", "/sbin", "/lib", "/lib64", "/usr" }; |
762 | 326 | } | 326 | for (int i = 0; i < sizeof(mounts) / sizeof(char *); i++) { |
763 | 327 | } | 327 | // we mount the OS snap /bin over the real /bin in this NS |
764 | 328 | 328 | const char *dst = mounts[i]; | |
765 | 329 | globfree(&glob_res); | 329 | |
766 | 330 | } | 330 | char buf[512]; |
767 | 331 | 331 | must_snprintf(buf, sizeof(buf), "%s%s", mountpoint, dst); | |
768 | 332 | void setup_slave_mount_namespace() { | 332 | const char *src = buf; |
769 | 333 | // unshare() and CLONE_NEWNS require linux >= 2.6.16 and glibc >= 2.14 | 333 | |
770 | 334 | // if using an older glibc, you'd need -D_BSD_SOURCE or -D_SVID_SORUCE. | 334 | debug("mounting %s -> %s\n", src, dst); |
771 | 335 | if (unshare(CLONE_NEWNS) < 0) { | 335 | if (mount(src, dst, NULL, MS_BIND, NULL) != 0) { |
772 | 336 | die("unable to set up mount namespace"); | 336 | die("unable to bind %s to %s", src, dst); |
773 | 337 | } | 337 | } |
774 | 338 | 338 | } | |
775 | 339 | // make our "/" a rslave of the real "/". this means that | 339 | |
776 | 340 | // mounts from the host "/" get propagated to our namespace | 340 | globfree(&glob_res); |
777 | 341 | // (i.e. we see new media mounts) | 341 | } |
778 | 342 | if (mount("none", "/", NULL, MS_REC|MS_SLAVE, NULL) != 0) { | 342 | |
779 | 343 | die("can not make make / rslave"); | 343 | void setup_slave_mount_namespace() |
780 | 344 | } | 344 | { |
781 | 345 | } | 345 | // unshare() and CLONE_NEWNS require linux >= 2.6.16 and glibc >= 2.14 |
782 | 346 | 346 | // if using an older glibc, you'd need -D_BSD_SOURCE or -D_SVID_SORUCE. | |
783 | 347 | void mkpath(const char *const path) { | 347 | if (unshare(CLONE_NEWNS) < 0) { |
784 | 348 | // If asked to create an empty path, return immediately. | 348 | die("unable to set up mount namespace"); |
785 | 349 | if (strlen(path) == 0) { | 349 | } |
786 | 350 | return; | 350 | // make our "/" a rslave of the real "/". this means that |
787 | 351 | } | 351 | // mounts from the host "/" get propagated to our namespace |
788 | 352 | 352 | // (i.e. we see new media mounts) | |
789 | 353 | // We're going to use strtok_r, which needs to modify the path, so we'll make | 353 | if (mount("none", "/", NULL, MS_REC | MS_SLAVE, NULL) != 0) { |
790 | 354 | // a copy of it. | 354 | die("can not make make / rslave"); |
791 | 355 | char *path_copy = strdup(path); | 355 | } |
792 | 356 | if (path_copy == NULL) { | 356 | } |
793 | 357 | die("failed to create user data directory"); | 357 | |
794 | 358 | } | 358 | void mkpath(const char *const path) |
795 | 359 | 359 | { | |
796 | 360 | // Open flags to use while we walk the user data path: | 360 | // If asked to create an empty path, return immediately. |
797 | 361 | // - Don't follow symlinks | 361 | if (strlen(path) == 0) { |
798 | 362 | // - Don't allow child access to file descriptor | 362 | return; |
799 | 363 | // - Only open a directory (fail otherwise) | 363 | } |
800 | 364 | int open_flags = O_NOFOLLOW | O_CLOEXEC | O_DIRECTORY; | 364 | // We're going to use strtok_r, which needs to modify the path, so we'll make |
801 | 365 | 365 | // a copy of it. | |
802 | 366 | // We're going to create each path segment via openat/mkdirat calls instead | 366 | char *path_copy = strdup(path); |
803 | 367 | // of mkdir calls, to avoid following symlinks and placing the user data | 367 | if (path_copy == NULL) { |
804 | 368 | // directory somewhere we never intended for it to go. The first step is to | 368 | die("failed to create user data directory"); |
805 | 369 | // get an initial file descriptor. | 369 | } |
806 | 370 | int fd = AT_FDCWD; | 370 | // Open flags to use while we walk the user data path: |
807 | 371 | if (path_copy[0] == '/') { | 371 | // - Don't follow symlinks |
808 | 372 | fd = open("/", open_flags); | 372 | // - Don't allow child access to file descriptor |
809 | 373 | if (fd < 0) { | 373 | // - Only open a directory (fail otherwise) |
810 | 374 | free(path_copy); | 374 | int open_flags = O_NOFOLLOW | O_CLOEXEC | O_DIRECTORY; |
811 | 375 | die("failed to create user data directory"); | 375 | |
812 | 376 | } | 376 | // We're going to create each path segment via openat/mkdirat calls instead |
813 | 377 | } | 377 | // of mkdir calls, to avoid following symlinks and placing the user data |
814 | 378 | 378 | // directory somewhere we never intended for it to go. The first step is to | |
815 | 379 | // strtok_r needs a pointer to keep track of where it is in the string. | 379 | // get an initial file descriptor. |
816 | 380 | char *path_walker; | 380 | int fd = AT_FDCWD; |
817 | 381 | 381 | if (path_copy[0] == '/') { | |
818 | 382 | // Initialize tokenizer and obtain first path segment. | 382 | fd = open("/", open_flags); |
819 | 383 | char *path_segment = strtok_r(path_copy, "/", &path_walker); | 383 | if (fd < 0) { |
820 | 384 | while (path_segment) { | 384 | free(path_copy); |
821 | 385 | // Try to create the directory. It's okay if it already existed, but any | 385 | die("failed to create user data directory"); |
822 | 386 | // other error is fatal. | 386 | } |
823 | 387 | if (mkdirat(fd, path_segment, 0755) < 0 && errno != EEXIST) { | 387 | } |
824 | 388 | close(fd); | 388 | // strtok_r needs a pointer to keep track of where it is in the string. |
825 | 389 | free(path_copy); | 389 | char *path_walker; |
826 | 390 | die("failed to create user data directory"); | 390 | |
827 | 391 | } | 391 | // Initialize tokenizer and obtain first path segment. |
828 | 392 | 392 | char *path_segment = strtok_r(path_copy, "/", &path_walker); | |
829 | 393 | // Open the parent directory we just made (and close the previous one) so | 393 | while (path_segment) { |
830 | 394 | // we can continue down the path. | 394 | // Try to create the directory. It's okay if it already existed, but any |
831 | 395 | int previous_fd = fd; | 395 | // other error is fatal. |
832 | 396 | fd = openat(fd, path_segment, open_flags); | 396 | if (mkdirat(fd, path_segment, 0755) < 0 && errno != EEXIST) { |
833 | 397 | close(previous_fd); | 397 | close(fd); |
834 | 398 | if (fd < 0) { | 398 | free(path_copy); |
835 | 399 | free(path_copy); | 399 | die("failed to create user data directory"); |
836 | 400 | die("failed to create user data directory"); | 400 | } |
837 | 401 | } | 401 | // Open the parent directory we just made (and close the previous one) so |
838 | 402 | 402 | // we can continue down the path. | |
839 | 403 | // Obtain the next path segment. | 403 | int previous_fd = fd; |
840 | 404 | path_segment = strtok_r(NULL, "/", &path_walker); | 404 | fd = openat(fd, path_segment, open_flags); |
841 | 405 | } | 405 | close(previous_fd); |
842 | 406 | 406 | if (fd < 0) { | |
843 | 407 | // Close the descriptor for the final directory in the path. | 407 | free(path_copy); |
844 | 408 | close(fd); | 408 | die("failed to create user data directory"); |
845 | 409 | 409 | } | |
846 | 410 | free(path_copy); | 410 | // Obtain the next path segment. |
847 | 411 | } | 411 | path_segment = strtok_r(NULL, "/", &path_walker); |
848 | 412 | 412 | } | |
849 | 413 | void setup_user_data() { | 413 | |
850 | 414 | const char *user_data = getenv("SNAP_USER_DATA"); | 414 | // Close the descriptor for the final directory in the path. |
851 | 415 | 415 | close(fd); | |
852 | 416 | // If $SNAP_USER_DATA wasn't defined, check the deprecated | 416 | |
853 | 417 | // $SNAP_APP_USER_DATA_PATH. | 417 | free(path_copy); |
854 | 418 | if (user_data == NULL) { | 418 | } |
855 | 419 | user_data = getenv("SNAP_APP_USER_DATA_PATH"); | 419 | |
856 | 420 | // If it's still not defined, there's nothing to do. No need to die, | 420 | void setup_user_data() |
857 | 421 | // there's simply no directory to create. | 421 | { |
858 | 422 | if (user_data == NULL) { | 422 | const char *user_data = getenv("SNAP_USER_DATA"); |
859 | 423 | return; | 423 | |
860 | 424 | } | 424 | // If $SNAP_USER_DATA wasn't defined, check the deprecated |
861 | 425 | } | 425 | // $SNAP_APP_USER_DATA_PATH. |
862 | 426 | 426 | if (user_data == NULL) { | |
863 | 427 | // Only support absolute paths. | 427 | user_data = getenv("SNAP_APP_USER_DATA_PATH"); |
864 | 428 | if (user_data[0] != '/') { | 428 | // If it's still not defined, there's nothing to do. No need to die, |
865 | 429 | die("user data directory must be an absolute path"); | 429 | // there's simply no directory to create. |
866 | 430 | } | 430 | if (user_data == NULL) { |
867 | 431 | 431 | return; | |
868 | 432 | mkpath(user_data); | 432 | } |
869 | 433 | } | ||
870 | 434 | // Only support absolute paths. | ||
871 | 435 | if (user_data[0] != '/') { | ||
872 | 436 | die("user data directory must be an absolute path"); | ||
873 | 437 | } | ||
874 | 438 | |||
875 | 439 | mkpath(user_data); | ||
876 | 433 | } | 440 | } |
877 | 434 | 441 | ||
878 | 435 | int main(int argc, char **argv) | 442 | int main(int argc, char **argv) |
879 | 436 | { | 443 | { |
981 | 437 | const int NR_ARGS = 3; | 444 | const int NR_ARGS = 3; |
982 | 438 | if(argc < NR_ARGS+1) | 445 | if (argc < NR_ARGS + 1) |
983 | 439 | die("Usage: %s <appname> <apparmor> <binary>", argv[0]); | 446 | die("Usage: %s <appname> <apparmor> <binary>", argv[0]); |
984 | 440 | 447 | ||
985 | 441 | const char *appname = argv[1]; | 448 | const char *appname = argv[1]; |
986 | 442 | const char *aa_profile = argv[2]; | 449 | const char *aa_profile = argv[2]; |
987 | 443 | const char *binary = argv[3]; | 450 | const char *binary = argv[3]; |
988 | 444 | unsigned real_uid = getuid(); | 451 | unsigned real_uid = getuid(); |
989 | 445 | unsigned real_gid = getgid(); | 452 | unsigned real_gid = getgid(); |
990 | 446 | 453 | ||
991 | 447 | if(!verify_appname(appname)) | 454 | if (!verify_appname(appname)) |
992 | 448 | die("appname %s not allowed", appname); | 455 | die("appname %s not allowed", appname); |
993 | 449 | 456 | ||
994 | 450 | // this code always needs to run as root for the cgroup/udev setup, | 457 | // this code always needs to run as root for the cgroup/udev setup, |
995 | 451 | // however for the tests we allow it to run as non-root | 458 | // however for the tests we allow it to run as non-root |
996 | 452 | if(geteuid() != 0 && getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) { | 459 | if (geteuid() != 0 && getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) { |
997 | 453 | die("need to run as root or suid"); | 460 | die("need to run as root or suid"); |
998 | 454 | } | 461 | } |
999 | 455 | 462 | ||
1000 | 456 | if(geteuid() == 0) { | 463 | if (geteuid() == 0) { |
1001 | 457 | 464 | ||
1002 | 458 | // ensure we run in our own slave mount namespace, this will | 465 | // ensure we run in our own slave mount namespace, this will |
1003 | 459 | // create a new mount namespace and make it a slave of "/" | 466 | // create a new mount namespace and make it a slave of "/" |
1004 | 460 | // | 467 | // |
1005 | 461 | // Note that this means that no mount actions inside our | 468 | // Note that this means that no mount actions inside our |
1006 | 462 | // namespace are propagated to the main "/". We need this | 469 | // namespace are propagated to the main "/". We need this |
1007 | 463 | // both for the private /tmp we create and for the bind | 470 | // both for the private /tmp we create and for the bind |
1008 | 464 | // mounts we do on a classic ubuntu system | 471 | // mounts we do on a classic ubuntu system |
1009 | 465 | // | 472 | // |
1010 | 466 | // This also means you can't run an automount daemon unter | 473 | // This also means you can't run an automount daemon unter |
1011 | 467 | // this launcher | 474 | // this launcher |
1012 | 468 | setup_slave_mount_namespace(); | 475 | setup_slave_mount_namespace(); |
1013 | 469 | 476 | ||
1014 | 470 | // do the mounting if run on a non-native snappy system | 477 | // do the mounting if run on a non-native snappy system |
1015 | 471 | if(is_running_on_classic_ubuntu()) { | 478 | if (is_running_on_classic_ubuntu()) { |
1016 | 472 | setup_snappy_os_mounts(); | 479 | setup_snappy_os_mounts(); |
1017 | 473 | } | 480 | } |
1018 | 474 | 481 | // set up private mounts | |
1019 | 475 | // set up private mounts | 482 | setup_private_mount(appname); |
1020 | 476 | setup_private_mount(appname); | 483 | |
1021 | 477 | 484 | // set up private /dev/pts | |
1022 | 478 | // set up private /dev/pts | 485 | setup_private_pts(); |
1023 | 479 | setup_private_pts(); | 486 | |
1024 | 480 | 487 | // this needs to happen as root | |
1025 | 481 | // this needs to happen as root | 488 | if (snappy_udev_setup_required(appname)) { |
1026 | 482 | if(snappy_udev_setup_required(appname)) { | 489 | setup_devices_cgroup(appname); |
1027 | 483 | setup_devices_cgroup(appname); | 490 | setup_udev_snappy_assign(appname); |
1028 | 484 | setup_udev_snappy_assign(appname); | 491 | } |
1029 | 485 | } | 492 | // the rest does not so temporarily drop privs back to calling user |
1030 | 486 | 493 | // (we'll permanently drop after loading seccomp) | |
1031 | 487 | // the rest does not so temporarily drop privs back to calling user | 494 | if (setegid(real_gid) != 0) |
1032 | 488 | // (we'll permanently drop after loading seccomp) | 495 | die("setegid failed"); |
1033 | 489 | if (setegid(real_gid) != 0) | 496 | if (seteuid(real_uid) != 0) |
1034 | 490 | die("setegid failed"); | 497 | die("seteuid failed"); |
1035 | 491 | if (seteuid(real_uid) != 0) | 498 | |
1036 | 492 | die("seteuid failed"); | 499 | if (real_gid != 0 && geteuid() == 0) |
1037 | 493 | 500 | die("dropping privs did not work"); | |
1038 | 494 | if(real_gid != 0 && geteuid() == 0) | 501 | if (real_uid != 0 && getegid() == 0) |
1039 | 495 | die("dropping privs did not work"); | 502 | die("dropping privs did not work"); |
1040 | 496 | if(real_uid != 0 && getegid() == 0) | 503 | } |
1041 | 497 | die("dropping privs did not work"); | 504 | // Ensure that the user data path exists. |
1042 | 498 | } | 505 | setup_user_data(); |
1043 | 499 | 506 | ||
1044 | 500 | // Ensure that the user data path exists. | 507 | // https://wiki.ubuntu.com/SecurityTeam/Specifications/SnappyConfinement |
1045 | 501 | setup_user_data(); | 508 | |
1046 | 502 | 509 | int rc = 0; | |
1047 | 503 | // https://wiki.ubuntu.com/SecurityTeam/Specifications/SnappyConfinement | 510 | // set apparmor rules |
1048 | 504 | 511 | rc = aa_change_onexec(aa_profile); | |
1049 | 505 | int rc = 0; | 512 | if (rc != 0) { |
1050 | 506 | // set apparmor rules | 513 | if (getenv("SNAPPY_LAUNCHER_INSIDE_TESTS") == NULL) |
1051 | 507 | rc = aa_change_onexec(aa_profile); | 514 | die("aa_change_onexec failed with %i", rc); |
1052 | 508 | if (rc != 0) { | 515 | } |
1053 | 509 | if (getenv("SNAPPY_LAUNCHER_INSIDE_TESTS") == NULL) | 516 | // set seccomp |
1054 | 510 | die("aa_change_onexec failed with %i", rc); | 517 | rc = seccomp_load_filters(aa_profile); |
1055 | 511 | } | 518 | if (rc != 0) |
1056 | 512 | 519 | die("seccomp_load_filters failed with %i", rc); | |
1057 | 513 | // set seccomp | 520 | |
1058 | 514 | rc = seccomp_load_filters(aa_profile); | 521 | // Permanently drop if not root |
1059 | 515 | if (rc != 0) | 522 | if (geteuid() == 0) { |
1060 | 516 | die("seccomp_load_filters failed with %i", rc); | 523 | // Note that we do not call setgroups() here because its ok |
1061 | 517 | 524 | // that the user keeps the groups he already belongs to | |
1062 | 518 | // Permanently drop if not root | 525 | if (setgid(real_gid) != 0) |
1063 | 519 | if (geteuid() == 0) { | 526 | die("setgid failed"); |
1064 | 520 | // Note that we do not call setgroups() here because its ok | 527 | if (setuid(real_uid) != 0) |
1065 | 521 | // that the user keeps the groups he already belongs to | 528 | die("setuid failed"); |
1066 | 522 | if (setgid(real_gid) != 0) | 529 | |
1067 | 523 | die("setgid failed"); | 530 | if (real_gid != 0 && (getuid() == 0 || geteuid() == 0)) |
1068 | 524 | if (setuid(real_uid) != 0) | 531 | die("permanently dropping privs did not work"); |
1069 | 525 | die("setuid failed"); | 532 | if (real_uid != 0 && (getgid() == 0 || getegid() == 0)) |
1070 | 526 | 533 | die("permanently dropping privs did not work"); | |
1071 | 527 | if(real_gid != 0 && (getuid() == 0 || geteuid() == 0)) | 534 | } |
1072 | 528 | die("permanently dropping privs did not work"); | 535 | // and exec the new binary |
1073 | 529 | if(real_uid != 0 && (getgid() == 0 || getegid() == 0)) | 536 | argv[NR_ARGS] = (char *)binary, |
1074 | 530 | die("permanently dropping privs did not work"); | 537 | execv(binary, (char *const *)&argv[NR_ARGS]); |
1075 | 531 | } | 538 | perror("execv failed"); |
1076 | 532 | 539 | return 1; | |
976 | 533 | // and exec the new binary | ||
977 | 534 | argv[NR_ARGS] = (char*)binary, | ||
978 | 535 | execv(binary, (char *const*)&argv[NR_ARGS]); | ||
979 | 536 | perror("execv failed"); | ||
980 | 537 | return 1; | ||
1077 | 538 | } | 540 | } |
1078 | 539 | 541 | ||
1079 | === modified file 'src/seccomp.c' | |||
1080 | --- src/seccomp.c 2016-03-21 17:22:39 +0000 | |||
1081 | +++ src/seccomp.c 2016-03-22 12:19:24 +0000 | |||
1082 | @@ -29,133 +29,140 @@ | |||
1083 | 29 | char *filter_profile_dir = "/var/lib/snappy/seccomp/profiles/"; | 29 | char *filter_profile_dir = "/var/lib/snappy/seccomp/profiles/"; |
1084 | 30 | 30 | ||
1085 | 31 | // strip whitespace from the end of the given string (inplace) | 31 | // strip whitespace from the end of the given string (inplace) |
1091 | 32 | size_t trim_right(char *s, size_t slen) { | 32 | size_t trim_right(char *s, size_t slen) |
1092 | 33 | while(slen > 0 && isspace(s[slen - 1])) { | 33 | { |
1093 | 34 | s[--slen] = 0; | 34 | while (slen > 0 && isspace(s[slen - 1])) { |
1094 | 35 | } | 35 | s[--slen] = 0; |
1095 | 36 | return slen; | 36 | } |
1096 | 37 | return slen; | ||
1097 | 37 | } | 38 | } |
1098 | 38 | 39 | ||
1099 | 39 | int seccomp_load_filters(const char *filter_profile) | 40 | int seccomp_load_filters(const char *filter_profile) |
1100 | 40 | { | 41 | { |
1205 | 41 | debug("seccomp_load_filters %s", filter_profile); | 42 | debug("seccomp_load_filters %s", filter_profile); |
1206 | 42 | int rc = 0; | 43 | int rc = 0; |
1207 | 43 | int syscall_nr = -1; | 44 | int syscall_nr = -1; |
1208 | 44 | scmp_filter_ctx ctx = NULL; | 45 | scmp_filter_ctx ctx = NULL; |
1209 | 45 | FILE *f = NULL; | 46 | FILE *f = NULL; |
1210 | 46 | size_t lineno = 0; | 47 | size_t lineno = 0; |
1211 | 47 | 48 | ||
1212 | 48 | ctx = seccomp_init(SCMP_ACT_KILL); | 49 | ctx = seccomp_init(SCMP_ACT_KILL); |
1213 | 49 | if (ctx == NULL) | 50 | if (ctx == NULL) |
1214 | 50 | return ENOMEM; | 51 | return ENOMEM; |
1215 | 51 | 52 | ||
1216 | 52 | // Disable NO_NEW_PRIVS because it interferes with exec transitions in | 53 | // Disable NO_NEW_PRIVS because it interferes with exec transitions in |
1217 | 53 | // AppArmor. Unfortunately this means that security policies must be very | 54 | // AppArmor. Unfortunately this means that security policies must be very |
1218 | 54 | // careful to not allow the following otherwise apps can escape the snadbox: | 55 | // careful to not allow the following otherwise apps can escape the snadbox: |
1219 | 55 | // - seccomp syscall | 56 | // - seccomp syscall |
1220 | 56 | // - prctl with PR_SET_SECCOMP | 57 | // - prctl with PR_SET_SECCOMP |
1221 | 57 | // - ptrace (trace) in AppArmor | 58 | // - ptrace (trace) in AppArmor |
1222 | 58 | // - capability sys_admin in AppArmor | 59 | // - capability sys_admin in AppArmor |
1223 | 59 | // Note that with NO_NEW_PRIVS disabled, CAP_SYS_ADMIN is required to change | 60 | // Note that with NO_NEW_PRIVS disabled, CAP_SYS_ADMIN is required to change |
1224 | 60 | // the seccomp sandbox. | 61 | // the seccomp sandbox. |
1225 | 61 | if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) { | 62 | if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) { |
1226 | 62 | rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0); | 63 | rc = seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0); |
1227 | 63 | if (rc != 0) { | 64 | if (rc != 0) { |
1228 | 64 | fprintf(stderr, "Cannot disable nnp\n"); | 65 | fprintf(stderr, "Cannot disable nnp\n"); |
1229 | 65 | return -1; | 66 | return -1; |
1230 | 66 | } | 67 | } |
1231 | 67 | } | 68 | } |
1232 | 68 | 69 | ||
1233 | 69 | if (getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL) | 70 | if (getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR") != NULL) |
1234 | 70 | filter_profile_dir = getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR"); | 71 | filter_profile_dir = |
1235 | 71 | 72 | getenv("SNAPPY_LAUNCHER_SECCOMP_PROFILE_DIR"); | |
1236 | 72 | char profile_path[128]; | 73 | |
1237 | 73 | if (snprintf(profile_path, sizeof(profile_path), "%s/%s", filter_profile_dir, filter_profile) < 0) { | 74 | char profile_path[128]; |
1238 | 74 | goto out; | 75 | if (snprintf |
1239 | 75 | } | 76 | (profile_path, sizeof(profile_path), "%s/%s", filter_profile_dir, |
1240 | 76 | 77 | filter_profile) < 0) { | |
1241 | 77 | f = fopen(profile_path, "r"); | 78 | goto out; |
1242 | 78 | if (f == NULL) { | 79 | } |
1243 | 79 | fprintf(stderr, "Can not open %s (%s)\n", profile_path, strerror(errno)); | 80 | |
1244 | 80 | return -1; | 81 | f = fopen(profile_path, "r"); |
1245 | 81 | } | 82 | if (f == NULL) { |
1246 | 82 | // 80 characters + '\n' + '\0' | 83 | fprintf(stderr, "Can not open %s (%s)\n", profile_path, |
1247 | 83 | char buf[82]; | 84 | strerror(errno)); |
1248 | 84 | while (fgets(buf, sizeof(buf), f) != NULL) | 85 | return -1; |
1249 | 85 | { | 86 | } |
1250 | 86 | size_t len; | 87 | // 80 characters + '\n' + '\0' |
1251 | 87 | 88 | char buf[82]; | |
1252 | 88 | lineno++; | 89 | while (fgets(buf, sizeof(buf), f) != NULL) { |
1253 | 89 | 90 | size_t len; | |
1254 | 90 | // comment, ignore | 91 | |
1255 | 91 | if(buf[0] == '#') | 92 | lineno++; |
1256 | 92 | continue; | 93 | |
1257 | 93 | 94 | // comment, ignore | |
1258 | 94 | // ensure the entire line was read | 95 | if (buf[0] == '#') |
1259 | 95 | len = strlen(buf); | 96 | continue; |
1260 | 96 | if (len == 0) | 97 | |
1261 | 97 | continue; | 98 | // ensure the entire line was read |
1262 | 98 | else if (buf[len - 1] != '\n' && len > (sizeof(buf) - 2)) { | 99 | len = strlen(buf); |
1263 | 99 | fprintf(stderr, "seccomp filter line %zu was too long (%zu characters max)\n", lineno, sizeof(buf) - 2); | 100 | if (len == 0) |
1264 | 100 | rc = -1; | 101 | continue; |
1265 | 101 | goto out; | 102 | else if (buf[len - 1] != '\n' && len > (sizeof(buf) - 2)) { |
1266 | 102 | } | 103 | fprintf(stderr, |
1267 | 103 | 104 | "seccomp filter line %zu was too long (%zu characters max)\n", | |
1268 | 104 | // kill final newline | 105 | lineno, sizeof(buf) - 2); |
1269 | 105 | len = trim_right(buf, len); | 106 | rc = -1; |
1270 | 106 | if (len == 0) | 107 | goto out; |
1271 | 107 | continue; | 108 | } |
1272 | 108 | 109 | // kill final newline | |
1273 | 109 | // check for special "@unrestricted" command | 110 | len = trim_right(buf, len); |
1274 | 110 | if (strncmp(buf, "@unrestricted", sizeof(buf)) == 0) | 111 | if (len == 0) |
1275 | 111 | goto out; | 112 | continue; |
1276 | 112 | 113 | ||
1277 | 113 | // syscall not available on this arch/kernel | 114 | // check for special "@unrestricted" command |
1278 | 114 | // as this is a syscall whitelist its ok and the error can be ignored | 115 | if (strncmp(buf, "@unrestricted", sizeof(buf)) == 0) |
1279 | 115 | syscall_nr = seccomp_syscall_resolve_name(buf); | 116 | goto out; |
1280 | 116 | if (syscall_nr == __NR_SCMP_ERROR) | 117 | |
1281 | 117 | continue; | 118 | // syscall not available on this arch/kernel |
1282 | 118 | 119 | // as this is a syscall whitelist its ok and the error can be ignored | |
1283 | 119 | // a normal line with a syscall | 120 | syscall_nr = seccomp_syscall_resolve_name(buf); |
1284 | 120 | rc = seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, syscall_nr, 0); | 121 | if (syscall_nr == __NR_SCMP_ERROR) |
1285 | 121 | if (rc != 0) { | 122 | continue; |
1286 | 122 | rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscall_nr, 0); | 123 | |
1287 | 123 | if (rc != 0) { | 124 | // a normal line with a syscall |
1288 | 124 | fprintf(stderr, "seccomp_rule_add failed with %i for '%s'\n", rc, buf); | 125 | rc = seccomp_rule_add_exact(ctx, SCMP_ACT_ALLOW, syscall_nr, 0); |
1289 | 125 | goto out; | 126 | if (rc != 0) { |
1290 | 126 | } | 127 | rc = seccomp_rule_add(ctx, SCMP_ACT_ALLOW, syscall_nr, |
1291 | 127 | } | 128 | 0); |
1292 | 128 | } | 129 | if (rc != 0) { |
1293 | 129 | 130 | fprintf(stderr, | |
1294 | 130 | // raise privileges to load seccomp policy since we don't have nnp | 131 | "seccomp_rule_add failed with %i for '%s'\n", |
1295 | 131 | if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) { | 132 | rc, buf); |
1296 | 132 | if (seteuid(0) != 0) | 133 | goto out; |
1297 | 133 | die("seteuid failed"); | 134 | } |
1298 | 134 | if (geteuid() != 0) | 135 | } |
1299 | 135 | die("raising privs before seccomp_load did not work"); | 136 | } |
1300 | 136 | } | 137 | |
1301 | 137 | 138 | // raise privileges to load seccomp policy since we don't have nnp | |
1302 | 138 | // load it into the kernel | 139 | if (getenv("UBUNTU_CORE_LAUNCHER_NO_ROOT") == NULL) { |
1303 | 139 | rc = seccomp_load(ctx); | 140 | if (seteuid(0) != 0) |
1304 | 140 | 141 | die("seteuid failed"); | |
1305 | 141 | if (rc != 0) { | 142 | if (geteuid() != 0) |
1306 | 142 | fprintf(stderr, "seccomp_load failed with %i\n", rc); | 143 | die("raising privs before seccomp_load did not work"); |
1307 | 143 | goto out; | 144 | } |
1308 | 144 | } | 145 | // load it into the kernel |
1309 | 146 | rc = seccomp_load(ctx); | ||
1310 | 147 | |||
1311 | 148 | if (rc != 0) { | ||
1312 | 149 | fprintf(stderr, "seccomp_load failed with %i\n", rc); | ||
1313 | 150 | goto out; | ||
1314 | 151 | } | ||
1315 | 145 | 152 | ||
1316 | 146 | out: | 153 | out: |
1325 | 147 | // drop privileges again | 154 | // drop privileges again |
1326 | 148 | if (geteuid() == 0) { | 155 | if (geteuid() == 0) { |
1327 | 149 | unsigned real_uid = getuid(); | 156 | unsigned real_uid = getuid(); |
1328 | 150 | if (seteuid(real_uid) != 0) | 157 | if (seteuid(real_uid) != 0) |
1329 | 151 | die("seteuid failed"); | 158 | die("seteuid failed"); |
1330 | 152 | if (real_uid != 0 && geteuid() == 0) | 159 | if (real_uid != 0 && geteuid() == 0) |
1331 | 153 | die("dropping privs after seccomp_load did not work"); | 160 | die("dropping privs after seccomp_load did not work"); |
1332 | 154 | } | 161 | } |
1333 | 155 | 162 | ||
1339 | 156 | if (f != NULL) { | 163 | if (f != NULL) { |
1340 | 157 | fclose(f); | 164 | fclose(f); |
1341 | 158 | } | 165 | } |
1342 | 159 | seccomp_release(ctx); | 166 | seccomp_release(ctx); |
1343 | 160 | return rc; | 167 | return rc; |
1344 | 161 | } | 168 | } |
1345 | 162 | 169 | ||
1346 | === modified file 'src/utils.c' | |||
1347 | --- src/utils.c 2015-06-11 10:57:01 +0000 | |||
1348 | +++ src/utils.c 2016-03-22 12:19:24 +0000 | |||
1349 | @@ -24,64 +24,66 @@ | |||
1350 | 24 | 24 | ||
1351 | 25 | void die(const char *msg, ...) | 25 | void die(const char *msg, ...) |
1352 | 26 | { | 26 | { |
1357 | 27 | va_list va; | 27 | va_list va; |
1358 | 28 | va_start(va, msg); | 28 | va_start(va, msg); |
1359 | 29 | vfprintf(stderr, msg, va); | 29 | vfprintf(stderr, msg, va); |
1360 | 30 | va_end(va); | 30 | va_end(va); |
1361 | 31 | 31 | ||
1368 | 32 | if (errno != 0) { | 32 | if (errno != 0) { |
1369 | 33 | perror(". errmsg"); | 33 | perror(". errmsg"); |
1370 | 34 | } else { | 34 | } else { |
1371 | 35 | fprintf(stderr, "\n"); | 35 | fprintf(stderr, "\n"); |
1372 | 36 | } | 36 | } |
1373 | 37 | exit(1); | 37 | exit(1); |
1374 | 38 | } | 38 | } |
1375 | 39 | 39 | ||
1376 | 40 | bool error(const char *msg, ...) | 40 | bool error(const char *msg, ...) |
1377 | 41 | { | 41 | { |
1382 | 42 | va_list va; | 42 | va_list va; |
1383 | 43 | va_start(va, msg); | 43 | va_start(va, msg); |
1384 | 44 | vfprintf(stderr, msg, va); | 44 | vfprintf(stderr, msg, va); |
1385 | 45 | va_end(va); | 45 | va_end(va); |
1386 | 46 | 46 | ||
1388 | 47 | return false; | 47 | return false; |
1389 | 48 | } | 48 | } |
1390 | 49 | 49 | ||
1391 | 50 | void debug(const char *msg, ...) | 50 | void debug(const char *msg, ...) |
1392 | 51 | { | 51 | { |
1428 | 52 | if(getenv("UBUNTU_CORE_LAUNCHER_DEBUG") == NULL) | 52 | if (getenv("UBUNTU_CORE_LAUNCHER_DEBUG") == NULL) |
1429 | 53 | return; | 53 | return; |
1430 | 54 | 54 | ||
1431 | 55 | va_list va; | 55 | va_list va; |
1432 | 56 | va_start(va, msg); | 56 | va_start(va, msg); |
1433 | 57 | fprintf(stderr, "DEBUG: "); | 57 | fprintf(stderr, "DEBUG: "); |
1434 | 58 | vfprintf(stderr, msg, va); | 58 | vfprintf(stderr, msg, va); |
1435 | 59 | fprintf(stderr, "\n"); | 59 | fprintf(stderr, "\n"); |
1436 | 60 | va_end(va); | 60 | va_end(va); |
1437 | 61 | } | 61 | } |
1438 | 62 | 62 | ||
1439 | 63 | void write_string_to_file(const char *filepath, const char *buf) { | 63 | void write_string_to_file(const char *filepath, const char *buf) |
1440 | 64 | debug("write_string_to_file %s %s", filepath, buf); | 64 | { |
1441 | 65 | FILE *f = fopen(filepath, "w"); | 65 | debug("write_string_to_file %s %s", filepath, buf); |
1442 | 66 | if (f == NULL) | 66 | FILE *f = fopen(filepath, "w"); |
1443 | 67 | die("fopen %s failed", filepath); | 67 | if (f == NULL) |
1444 | 68 | if (fwrite(buf, strlen(buf), 1, f) != 1) | 68 | die("fopen %s failed", filepath); |
1445 | 69 | die("fwrite failed"); | 69 | if (fwrite(buf, strlen(buf), 1, f) != 1) |
1446 | 70 | if (fflush(f) != 0) | 70 | die("fwrite failed"); |
1447 | 71 | die("fflush failed"); | 71 | if (fflush(f) != 0) |
1448 | 72 | fclose(f); | 72 | die("fflush failed"); |
1449 | 73 | } | 73 | fclose(f); |
1450 | 74 | 74 | } | |
1451 | 75 | int must_snprintf(char *str, size_t size, const char *format, ...) { | 75 | |
1452 | 76 | int n = -1; | 76 | int must_snprintf(char *str, size_t size, const char *format, ...) |
1453 | 77 | 77 | { | |
1454 | 78 | va_list va; | 78 | int n = -1; |
1455 | 79 | va_start(va, format); | 79 | |
1456 | 80 | n = vsnprintf(str, size, format, va); | 80 | va_list va; |
1457 | 81 | va_end(va); | 81 | va_start(va, format); |
1458 | 82 | 82 | n = vsnprintf(str, size, format, va); | |
1459 | 83 | if(n < 0 || n >= size) | 83 | va_end(va); |
1460 | 84 | die("failed to snprintf %s", str); | 84 | |
1461 | 85 | 85 | if (n < 0 || n >= size) | |
1462 | 86 | return n; | 86 | die("failed to snprintf %s", str); |
1463 | 87 | |||
1464 | 88 | return n; | ||
1465 | 87 | } | 89 | } |
1466 | 88 | 90 | ||
1467 | === modified file 'src/utils.h' | |||
1468 | --- src/utils.h 2015-05-20 11:10:29 +0000 | |||
1469 | +++ src/utils.h 2016-03-22 12:19:24 +0000 | |||
1470 | @@ -20,19 +20,19 @@ | |||
1471 | 20 | #define CORE_LAUNCHER_UTILS_H | 20 | #define CORE_LAUNCHER_UTILS_H |
1472 | 21 | 21 | ||
1473 | 22 | __attribute__ ((noreturn)) | 22 | __attribute__ ((noreturn)) |
1475 | 23 | __attribute__ ((format (printf, 1, 2))) | 23 | __attribute__ ((format(printf, 1, 2))) |
1476 | 24 | void die(const char *fmt, ...); | 24 | void die(const char *fmt, ...); |
1477 | 25 | 25 | ||
1479 | 26 | __attribute__ ((format (printf, 1, 2))) | 26 | __attribute__ ((format(printf, 1, 2))) |
1480 | 27 | bool error(const char *fmt, ...); | 27 | bool error(const char *fmt, ...); |
1481 | 28 | 28 | ||
1483 | 29 | __attribute__ ((format (printf, 1, 2))) | 29 | __attribute__ ((format(printf, 1, 2))) |
1484 | 30 | void debug(const char *fmt, ...); | 30 | void debug(const char *fmt, ...); |
1485 | 31 | 31 | ||
1486 | 32 | void write_string_to_file(const char *filepath, const char *buf); | 32 | void write_string_to_file(const char *filepath, const char *buf); |
1487 | 33 | 33 | ||
1488 | 34 | // snprintf version that dies on any error condition | 34 | // snprintf version that dies on any error condition |
1490 | 35 | __attribute__ ((format (printf, 3, 4))) | 35 | __attribute__ ((format(printf, 3, 4))) |
1491 | 36 | int must_snprintf(char *str, size_t size, const char *format, ...); | 36 | int must_snprintf(char *str, size_t size, const char *format, ...); |
1492 | 37 | 37 | ||
1493 | 38 | #endif | 38 | #endif |
Looks great, thanks for this. Unfortunate that we loose the bzr blame info but thats life.