Merge lp:~jdstrand/snap-confine/fix-udev-for-1604 into lp:~snappy-dev/snap-confine/trunk

Proposed by Jamie Strandboge
Status: Merged
Merged at revision: 125
Proposed branch: lp:~jdstrand/snap-confine/fix-udev-for-1604
Merge into: lp:~snappy-dev/snap-confine/trunk
Diff against target: 437 lines (+181/-109)
6 files modified
README (+26/-3)
debian/changelog (+21/-0)
debian/usr.bin.ubuntu-core-launcher (+4/-6)
src/80-snappy-assign.rules (+1/-1)
src/main.c (+127/-98)
src/snappy-app-dev (+2/-1)
To merge this branch: bzr merge lp:~jdstrand/snap-confine/fix-udev-for-1604
Reviewer Review Type Date Requested Status
Tyler Hicks (community) Needs Fixing
Review via email: mp+291028@code.launchpad.net

Description of the change

  * update cgroup handling for 16.04 (LP: #1564401):
    - debian/usr.bin.ubuntu-core-launcher:
      + allow creating cgroups with snap.*
      + allow ixr of 'tr'
      + remove access to /var/lib/apparmor/clicks/
    - update README to more fully explain the cgroups implementation
    - src/80-snappy-assign.rules: append an app-specific tag instead of
      adding a generic tag and snap-specific property
    - src/snappy-app-dev: convert the new tag to the directory name
    - src/main.c:
      + refactor and simplify control flow to query udev for device assignment
        instead of searching apparmor policy for a specific string
      + adjust udev query for app-specific tag
      + raise real_uid after fork() before calling /lib/udev/snappy-app-dev
        so non-root app launches work with the device cgroup

To post a comment you must log in.
Revision history for this message
Leo Arias (elopio) :
123. By Jamie Strandboge

address typo pointed out be Leo

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

Thanks Leo, pushed in r123.

Revision history for this message
Tyler Hicks (tyhicks) wrote :

See inline comments.

review: Needs Fixing
Revision history for this message
Tyler Hicks (tyhicks) wrote :

I'll also note that, due to release deadlines, I did not have sufficient time to review the greater design changes that are occurring due to this MP. I really only had a chance to review the code changes.

124. By Jamie Strandboge

merge from trunk

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

>> +const char *static_devices[] = {
>
> I may be missing something but I don't see why this array was lifted from the function that uses it and turned into a global. If it is to remain as global, it should be declared as "static const char *static_devices[]" since it isn't used outside of main.c.

Ah, yes, this should have been moved back down after I made the code simpler.

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

> This string test is incomplete for a couple reasons:
>
> 1) tagname_len could be greater than the size of the tagname buffer
> 2) the tagname buffer could be unterminated

True. I was thinking about the must_snprintf() that was called to set it would have taken care of this, but if we are doing all these checks for future-proof coding, we should do all the checks.

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

>> Is it intentional that we don't attempt to setuid(0) (or fail?) if the first conditional is not met? It seems correct to continue on if real_uid is 0 but what about when effective_uid is not 0? I guess snappy-app-dev will simply fail due to insufficient perms and everything will be ok.

Yes, it will fail due to insufficient perms and yes this was intentional. The idea here is to raise if we can but not if we can't. The testsuite will have euid as non-zero, under sudo real_uid and euid are 0, and under suid launcher euid is 0 but real_uid is not.

125. By Jamie Strandboge

move static_devices into setup_devices_cgroup(), where it should be

126. By Jamie Strandboge

src/main.c: further futreproof our tagname checks. Patch thanks to Tyler Hicks

127. By Jamie Strandboge

src/main.c: simplify execl() failure

128. By Jamie Strandboge

use __func__ in debug() statements

Revision history for this message
Jamie Strandboge (jdstrand) wrote :

Thanks for the review!

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README'
2--- README 2016-04-13 21:10:41 +0000
3+++ README 2016-04-14 23:03:26 +0000
4@@ -46,9 +46,32 @@
5 == devices cgroup ==
6
7 It works like this:
8-- on install of snaps with a special hardware:\n assign yaml udev rules are generated that add tags and properties to the matching hardware
9-- this launcher creates a device cgroup that is deny-all by default, then adds itself to the group and also adds all devices that match the given snappy-assign tag and the appname property
10-- the app is executed and now the normal device permissions/apparmor rules apply
11+- on install of snaps with a special hardware: assign yaml udev rules are
12+ generated that add tags to matching hardware. These assign rules are added to
13+ udev via /etc/udev/rules.d/70-snap.... for each app within a snap. The tags
14+ are of the form 'snap_<snap name>_<app>'.
15+- when an application is launched, the launcher queries udev to detect if any
16+ devices are tagged for this application. If no devices are tagged for this
17+ application, a device cgroup is not setup
18+- if there are devices tagged for this application, the launcher creates a
19+ device cgroup in /sys/fs/cgroups/devices/snap.<snap name>.<app> and adds
20+ itself to this cgroup. It then sets the cgroup as deny-all by default, adds
21+ some common devices (eg, /dev/null, /dev/zero, etc) and any devices tagged
22+ for use by this application using /lib/udev/snappy-app-dev
23+- the app is executed and now the normal device permissions/apparmor rules
24+ apply
25+- udev match rules in /lib/udev/rules.d/80-snapy-assign.rules are in place to
26+ run /lib/udev/snappy-app-dev to handle device events for devices tagged with
27+ snap_*.
28+
29+Note, /sys/fs/cgroups/devices/snap.<snap name>.<app> is not (currently) removed
30+on unassignment and the contents of the cgroup for the app are managed entirely
31+by the launcher. When an application is started, the cgroup is reset by
32+removing all previously added devices and then the list of assigned devices is
33+built back up before launch. In this manner, devices can be assigned, changed,
34+and unassigned and the app will always get the correct device added to the
35+cgroup, but what is in /sys/fs/cgroups/devices/snap.<snap name>.<app> will not
36+reflect assignment/unassignment until after the application is started.
37
38
39 == private /tmp ==
40
41=== modified file 'debian/changelog'
42--- debian/changelog 2016-04-14 22:18:12 +0000
43+++ debian/changelog 2016-04-14 23:03:26 +0000
44@@ -1,3 +1,24 @@
45+ubuntu-core-launcher (1.0.25) UNRELEASED; urgency=medium
46+
47+ * update cgroup handling for 16.04 (LP: #1564401):
48+ - debian/usr.bin.ubuntu-core-launcher:
49+ + allow creating cgroups with snap.*
50+ + allow ixr of 'tr'
51+ + remove access to /var/lib/apparmor/clicks/
52+ - update README to more fully explain the cgroups implementation
53+ - src/80-snappy-assign.rules: append an app-specific tag instead of
54+ adding a generic tag and snap-specific property
55+ - src/snappy-app-dev: convert the new tag to the directory name
56+ - src/main.c:
57+ + refactor and simplify control flow to query udev for device assignment
58+ instead of searching apparmor policy for a specific string
59+ + adjust udev query for app-specific tag
60+ + raise real_uid after fork() before calling /lib/udev/snappy-app-dev
61+ so non-root app launches work with the device cgroup
62+
63+
64+ -- Jamie Strandboge <jamie@ubuntu.com> Thu, 14 Apr 2016 17:18:54 -0500
65+
66 ubuntu-core-launcher (1.0.24) xenial; urgency=medium
67
68 [ Michael Vogt ]
69
70=== modified file 'debian/usr.bin.ubuntu-core-launcher'
71--- debian/usr.bin.ubuntu-core-launcher 2016-04-11 23:10:01 +0000
72+++ debian/usr.bin.ubuntu-core-launcher 2016-04-14 23:03:26 +0000
73@@ -26,15 +26,16 @@
74 # cgroups
75 capability sys_admin,
76 capability dac_override,
77- /sys/fs/cgroup/devices/snappy.*/ w,
78- /sys/fs/cgroup/devices/snappy.*/tasks w,
79- /sys/fs/cgroup/devices/snappy.*/devices.{allow,deny} w,
80+ /sys/fs/cgroup/devices/snap{,py}.*/ w,
81+ /sys/fs/cgroup/devices/snap{,py}.*/tasks w,
82+ /sys/fs/cgroup/devices/snap{,py}.*/devices.{allow,deny} w,
83
84 # querying udev
85 /etc/udev/udev.conf r,
86 /sys/devices/**/uevent r,
87 /lib/udev/snappy-app-dev ixr, # drop
88 /run/udev/** rw,
89+ /{,usr/}bin/tr ixr,
90
91 # priv dropping
92 capability setuid,
93@@ -66,9 +67,6 @@
94 # reading seccomp filters
95 /var/lib/snapd/seccomp/profiles/* r,
96
97- # read apparmor to figure out if we need cgroups
98- /var/lib/apparmor/clicks/* r,
99-
100 # set up snap-specific private /tmp dir
101 capability chown,
102 /tmp/ w,
103
104=== modified file 'src/80-snappy-assign.rules'
105--- src/80-snappy-assign.rules 2015-04-18 14:54:33 +0000
106+++ src/80-snappy-assign.rules 2016-04-14 23:03:26 +0000
107@@ -1,2 +1,2 @@
108 # add/remove snap package access to assigned devices
109-TAG=="snappy-assign", RUN+="/lib/udev/snappy-app-dev $env{ACTION} $env{SNAPPY_APP} $devpath $major:$minor"
110+TAG=="snap_*", RUN+="/lib/udev/snappy-app-dev $env{ACTION} $env{TAG} $devpath $major:$minor"
111
112=== modified file 'src/main.c'
113--- src/main.c 2016-04-14 20:47:41 +0000
114+++ src/main.c 2016-04-14 23:03:26 +0000
115@@ -38,6 +38,8 @@
116 #include <fcntl.h>
117 #include <glob.h>
118
119+#include <ctype.h>
120+
121 #include "libudev.h"
122
123 #include "utils.h"
124@@ -45,6 +47,14 @@
125
126 #define MAX_BUF 1000
127
128+struct snappy_udev {
129+ struct udev *udev;
130+ struct udev_enumerate *devices;
131+ struct udev_list_entry *assigned;
132+ char tagname[MAX_BUF];
133+ size_t tagname_len;
134+};
135+
136 bool verify_appname(const char *appname)
137 {
138 // these chars are allowed in a appname
139@@ -59,11 +69,22 @@
140 return (status == 0);
141 }
142
143-void run_snappy_app_dev_add(struct udev *u, const char *path,
144- const char *appname)
145+void run_snappy_app_dev_add(struct snappy_udev *udev_s, const char *path)
146 {
147- debug("run_snappy_app_dev_add: %s %s", path, appname);
148- struct udev_device *d = udev_device_new_from_syspath(u, path);
149+ if (udev_s == NULL)
150+ die("snappy_udev is NULL");
151+ if (udev_s->udev == NULL)
152+ die("snappy_udev->udev is NULL");
153+ if (udev_s->tagname_len == 0
154+ || udev_s->tagname_len >= MAX_BUF
155+ || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len
156+ || udev_s->tagname[udev_s->tagname_len] != '\0')
157+ die("snappy_udev->tagname has invalid length");
158+
159+ debug("%s: %s %s", __func__, path, udev_s->tagname);
160+
161+ struct udev_device *d =
162+ udev_device_new_from_syspath(udev_s->udev, path);
163 if (d == NULL)
164 die("can not find %s", path);
165 dev_t devnum = udev_device_get_devnum(d);
166@@ -75,14 +96,21 @@
167 die("could not fork");
168 }
169 if (pid == 0) {
170+ uid_t real_uid, effective_uid, saved_uid;
171+ if (getresuid(&real_uid, &effective_uid, &saved_uid) != 0)
172+ die("could not find user IDs");
173+ // can't update the cgroup unless the real_uid is 0, euid as
174+ // 0 is not enough
175+ if (real_uid != 0 && effective_uid == 0)
176+ if (setuid(0) != 0)
177+ die("setuid failed");
178 char buf[64];
179 unsigned major = MAJOR(devnum);
180 unsigned minor = MINOR(devnum);
181 must_snprintf(buf, sizeof(buf), "%u:%u", major, minor);
182- if (execl
183- ("/lib/udev/snappy-app-dev", "/lib/udev/snappy-app-dev",
184- "add", appname, path, buf, NULL) != 0)
185- die("execlp failed");
186+ execl("/lib/udev/snappy-app-dev", "/lib/udev/snappy-app-dev",
187+ "add", udev_s->tagname, path, buf, NULL);
188+ die("execl failed");
189 }
190 if (waitpid(pid, &status, 0) < 0)
191 die("waitpid failed");
192@@ -92,14 +120,64 @@
193 die("child died with signal %i", WTERMSIG(status));
194 }
195
196-void setup_udev_snappy_assign(const char *appname)
197+/*
198+ * snappy_udev_init() - setup the snappy_udev structure. Return 0 if devices
199+ * are assigned, else return -1. Callers should use snappy_udev_cleanup() to
200+ * cleanup.
201+ */
202+int snappy_udev_init(const char *appname, struct snappy_udev *udev_s)
203 {
204- debug("setup_udev_snappy_assign");
205-
206- struct udev *u = udev_new();
207- if (u == NULL)
208+ debug("%s", __func__);
209+ int rc = 0;
210+
211+ // extra paranoia
212+ if (!verify_appname(appname))
213+ die("appname %s not allowed", appname);
214+
215+ udev_s->tagname[0] = '\0';
216+ udev_s->tagname_len = 0;
217+ // TAG+="snap_<appname>" (udev doesn't like '.' in the tag name)
218+ udev_s->tagname_len = must_snprintf(udev_s->tagname, MAX_BUF,
219+ "snap_%s", appname);
220+ for (int i = 0; i < udev_s->tagname_len; i++)
221+ if (udev_s->tagname[i] == '.')
222+ udev_s->tagname[i] = '_';
223+
224+ udev_s->udev = udev_new();
225+ if (udev_s->udev == NULL)
226 die("udev_new failed");
227
228+ udev_s->devices = udev_enumerate_new(udev_s->udev);
229+ if (udev_s->devices == NULL)
230+ die("udev_enumerate_new failed");
231+
232+ if (udev_enumerate_add_match_tag(udev_s->devices, udev_s->tagname) != 0)
233+ die("udev_enumerate_add_match_tag");
234+
235+ if (udev_enumerate_scan_devices(udev_s->devices) != 0)
236+ die("udev_enumerate_scan failed");
237+
238+ udev_s->assigned = udev_enumerate_get_list_entry(udev_s->devices);
239+ if (udev_s->assigned == NULL)
240+ rc = -1;
241+
242+ return rc;
243+}
244+
245+void snappy_udev_cleanup(struct snappy_udev *udev_s)
246+{
247+ // udev_s->assigned does not need to be unreferenced since it is a
248+ // pointer into udev_s->devices
249+ if (udev_s->devices != NULL)
250+ udev_enumerate_unref(udev_s->devices);
251+ if (udev_s->udev != NULL)
252+ udev_unref(udev_s->udev);
253+}
254+
255+void setup_devices_cgroup(const char *appname, struct snappy_udev *udev_s)
256+{
257+ debug("%s", __func__);
258+ // Devices that must always be present
259 const char *static_devices[] = {
260 "/sys/class/mem/null",
261 "/sys/class/mem/full",
262@@ -111,50 +189,29 @@
263 "/sys/class/tty/ptmx",
264 NULL,
265 };
266- int i;
267- for (i = 0; static_devices[i] != NULL; i++) {
268- run_snappy_app_dev_add(u, static_devices[i], appname);
269- }
270-
271- struct udev_enumerate *devices = udev_enumerate_new(u);
272- if (devices == NULL)
273- die("udev_enumerate_new failed");
274-
275- if (udev_enumerate_add_match_tag(devices, "snappy-assign") != 0)
276- die("udev_enumerate_add_match_tag");
277-
278- if (udev_enumerate_add_match_property(devices, "SNAPPY_APP", appname) !=
279- 0)
280- die("udev_enumerate_add_match_property");
281-
282- if (udev_enumerate_scan_devices(devices) != 0)
283- die("udev_enumerate_scan failed");
284-
285- struct udev_list_entry *l = udev_enumerate_get_list_entry(devices);
286- while (l != NULL) {
287- const char *path = udev_list_entry_get_name(l);
288- if (path == NULL)
289- die("udev_list_entry_get_name failed");
290- run_snappy_app_dev_add(u, path, appname);
291- l = udev_list_entry_get_next(l);
292- }
293-
294- udev_enumerate_unref(devices);
295- udev_unref(u);
296-}
297-
298-void setup_devices_cgroup(const char *appname)
299-{
300- debug("setup_devices_cgroup");
301
302 // extra paranoia
303 if (!verify_appname(appname))
304 die("appname %s not allowed", appname);
305+ if (udev_s == NULL)
306+ die("snappy_udev is NULL");
307+ if (udev_s->udev == NULL)
308+ die("snappy_udev->udev is NULL");
309+ if (udev_s->devices == NULL)
310+ die("snappy_udev->devices is NULL");
311+ if (udev_s->assigned == NULL)
312+ die("snappy_udev->assigned is NULL");
313+ if (udev_s->tagname_len == 0
314+ || udev_s->tagname_len >= MAX_BUF
315+ || strnlen(udev_s->tagname, MAX_BUF) != udev_s->tagname_len
316+ || udev_s->tagname[udev_s->tagname_len] != '\0')
317+ die("snappy_udev->tagname has invalid length");
318
319 // create devices cgroup controller
320 char cgroup_dir[PATH_MAX];
321+
322 must_snprintf(cgroup_dir, sizeof(cgroup_dir),
323- "/sys/fs/cgroup/devices/snappy.%s/", appname);
324+ "/sys/fs/cgroup/devices/snap.%s/", appname);
325
326 if (mkdir(cgroup_dir, 0755) < 0 && errno != EEXIST)
327 die("mkdir failed");
328@@ -168,55 +225,26 @@
329 must_snprintf(buf, sizeof(buf), "%i", getpid());
330 write_string_to_file(cgroup_file, buf);
331
332- // deny by default
333+ // deny by default. Write 'a' to devices.deny to remove all existing
334+ // devices that were added in previous launcher invocations, then add
335+ // the static and assigned devices. This ensures that at application
336+ // launch the cgroup only has what is currently assigned.
337 must_snprintf(cgroup_file, sizeof(cgroup_file), "%s%s", cgroup_dir,
338 "devices.deny");
339 write_string_to_file(cgroup_file, "a");
340
341-}
342-
343-bool snappy_udev_setup_required(const char *appname)
344-{
345- debug("snappy_udev_setup_required");
346-
347- // extra paranoia
348- if (!verify_appname(appname))
349- die("appname %s not allowed", appname);
350-
351- char override_file[PATH_MAX];
352- must_snprintf(override_file, sizeof(override_file),
353- "/var/lib/apparmor/clicks/%s.json.additional", appname);
354-
355- // if a snap package gets unrestricted apparmor access we need to setup
356- // a device cgroup.
357- //
358- // the "needle" string is what gives this access so we search for that
359- // here
360- const char *needle =
361- "{" "\n"
362- " \"write_path\": [" "\n"
363- " \"/dev/**\"" "\n"
364- " ]," "\n"
365- " \"read_path\": [" "\n" " \"/run/udev/data/*\"" "\n" " ]\n" "}";
366- debug("looking for: '%s'", needle);
367- char content[strlen(needle)];
368-
369- int fd = open(override_file, O_CLOEXEC | O_NOFOLLOW | O_RDONLY);
370- if (fd < 0)
371- return false;
372- int n = read(fd, content, sizeof(content));
373- if (close(fd) != 0)
374- die("could not close override");
375- if (n < sizeof(content))
376- return false;
377-
378- // memcpy so that we don't have to deal with \0 in the input
379- if (memcmp(content, needle, strlen(needle)) == 0) {
380- debug("found needle, need to apply udev setup");
381- return true;
382+ // add the common devices
383+ for (int i = 0; static_devices[i] != NULL; i++)
384+ run_snappy_app_dev_add(udev_s, static_devices[i]);
385+
386+ // add the assigned devices
387+ while (udev_s->assigned != NULL) {
388+ const char *path = udev_list_entry_get_name(udev_s->assigned);
389+ if (path == NULL)
390+ die("udev_list_entry_get_name failed");
391+ run_snappy_app_dev_add(udev_s, path);
392+ udev_s->assigned = udev_list_entry_get_next(udev_s->assigned);
393 }
394-
395- return false;
396 }
397
398 bool is_running_on_classic_ubuntu()
399@@ -313,7 +341,7 @@
400
401 void setup_snappy_os_mounts()
402 {
403- debug("setup_snappy_os_mounts()\n");
404+ debug("%s", __func__);
405
406 // FIXME: hardcoded "ubuntu-core.*"
407 glob_t glob_res;
408@@ -503,10 +531,11 @@
409 setup_private_pts();
410
411 // this needs to happen as root
412- if (snappy_udev_setup_required(appname)) {
413- setup_devices_cgroup(appname);
414- setup_udev_snappy_assign(appname);
415- }
416+ struct snappy_udev udev_s;
417+ if (snappy_udev_init(appname, &udev_s) == 0)
418+ setup_devices_cgroup(appname, &udev_s);
419+ snappy_udev_cleanup(&udev_s);
420+
421 // the rest does not so temporarily drop privs back to calling
422 // user (we'll permanently drop after loading seccomp)
423 if (setegid(real_gid) != 0)
424
425=== modified file 'src/snappy-app-dev'
426--- src/snappy-app-dev 2015-04-17 22:23:37 +0000
427+++ src/snappy-app-dev 2016-04-14 23:03:26 +0000
428@@ -15,7 +15,8 @@
429 [ -n "$DEVPATH" ] || { echo "no devpath given" >&2; exit 1; }
430 [ -n "$MAJMIN" ] || { echo "no major/minor given" >&2; exit 0; }
431
432-app_dev_cgroup="/sys/fs/cgroup/devices/snappy.$APPNAME"
433+APPNAME=`echo $APPNAME | tr '_' '.'`
434+app_dev_cgroup="/sys/fs/cgroup/devices/$APPNAME"
435
436 # check if it's a block or char dev
437 if [ "${DEVPATH#*/block/}" != "$DEVPATH" ]; then

Subscribers

People subscribed via source and target branches