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