-
Notifications
You must be signed in to change notification settings - Fork 680
Expand file tree
/
Copy pathudev-support.c
More file actions
390 lines (359 loc) · 15.3 KB
/
Copy pathudev-support.c
File metadata and controls
390 lines (359 loc) · 15.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
/*
* Copyright (C) 2015-2020 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <ctype.h>
#include <dlfcn.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>
#include <libudev.h>
#include "../libsnap-confine-private/cgroup-support.h"
#include "../libsnap-confine-private/cleanup-funcs.h"
#include "../libsnap-confine-private/device-cgroup-support.h"
#include "../libsnap-confine-private/snap.h"
#include "../libsnap-confine-private/string-utils.h"
#include "../libsnap-confine-private/utils.h"
#include "mount-support-hybris.h"
#include "udev-support.h"
/* Allow access to common devices. */
static void sc_udev_allow_common(sc_device_cgroup *cgroup) {
/* The devices we add here have static number allocation.
* https://www.kernel.org/doc/html/v4.11/admin-guide/devices.html */
sc_device_cgroup_allow(cgroup, S_IFCHR, 1, 3); // /dev/null
sc_device_cgroup_allow(cgroup, S_IFCHR, 1, 5); // /dev/zero
sc_device_cgroup_allow(cgroup, S_IFCHR, 1, 7); // /dev/full
sc_device_cgroup_allow(cgroup, S_IFCHR, 1, 8); // /dev/random
sc_device_cgroup_allow(cgroup, S_IFCHR, 1, 9); // /dev/urandom
sc_device_cgroup_allow(cgroup, S_IFCHR, 5, 0); // /dev/tty
sc_device_cgroup_allow(cgroup, S_IFCHR, 5, 1); // /dev/console
sc_device_cgroup_allow(cgroup, S_IFCHR, 5, 2); // /dev/ptmx
}
/** Allow access to current and future PTY slaves.
*
* We unconditionally add them since we use a devpts newinstance. Unix98 PTY
* slaves major are 136-143.
*
* See also:
* https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
**/
static void sc_udev_allow_pty_slaves(sc_device_cgroup *cgroup) {
for (unsigned pty_major = 136; pty_major <= 143; pty_major++) {
sc_device_cgroup_allow(cgroup, S_IFCHR, pty_major, SC_DEVICE_MINOR_ANY);
}
}
/** Allow access to Nvidia devices.
*
* Nvidia modules are proprietary and therefore aren't in sysfs and can't be
* udev tagged. For now, just add existing nvidia devices to the cgroup
* unconditionally (AppArmor will still mediate the access). We'll want to
* rethink this if snapd needs to mediate access to other proprietary devices.
*
* Device major and minor numbers are described in (though nvidia-uvm currently
* isn't listed):
*
* https://www.kernel.org/doc/Documentation/admin-guide/devices.txt
**/
static void sc_udev_allow_nvidia(sc_device_cgroup *cgroup) {
struct stat sbuf;
/* Allow access to /dev/nvidia0 through /dev/nvidia254 */
for (unsigned nv_minor = 0; nv_minor < 255; nv_minor++) {
char nv_path[15] = {0}; // /dev/nvidiaXXX
sc_must_snprintf(nv_path, sizeof(nv_path), "/dev/nvidia%u", nv_minor);
/* Stop trying to find devices after one is not found. In this manner,
* we'll add /dev/nvidia0 and /dev/nvidia1 but stop trying to find
* nvidia3 - nvidia254 if nvidia2 is not found. */
if (stat(nv_path, &sbuf) < 0) {
break;
}
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
if (stat("/dev/nvidiactl", &sbuf) == 0) {
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
if (stat("/dev/nvidia-uvm", &sbuf) == 0) {
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
if (stat("/dev/nvidia-modeset", &sbuf) == 0) {
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
}
/** Allow access to hybris devices.
*
* Required by Halium-based GNU/Linux adaptations to make use of certain device nodes.
*
* Note: Binder devices on newer Android kernels reside inside of their own binderfs mountpount.
**/
static void sc_udev_allow_hybris(sc_device_cgroup *cgroup) {
/* Only go on here if this has been identified as a Halium/libhybris system
*
* In case the host happens to have binder available, but isn't identified as
* a system requiring it to drive host-residing Android drivers, then return early,
* otherwise we would open a hole between confined apps and unconfined Anbox or other
* which causes them to communicate over a potentially unmediated IPC interface.
* So only proceed if this has been identified as a Halium distribution.
*/
if (!sc_mount_is_halium_system()) {
return;
}
static const char *binder_paths[] = {"/dev/binderfs/binder", "/dev/binderfs/hwbinder", "/dev/binder",
"/dev/hwbinder"};
// If everything looks alright, allow access to binder IPC via the device cgroup
for (long unsigned int i = 0; i < sizeof(binder_paths) / sizeof(binder_paths[0]); i++) {
struct stat sbuf;
if (stat(binder_paths[i], &sbuf) == 0) {
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
}
}
/**
* Allow access to /dev/uhid.
*
* Currently /dev/uhid isn't represented in sysfs, so add it to the device
* cgroup if it exists and let AppArmor handle the mediation.
**/
static void sc_udev_allow_uhid(sc_device_cgroup *cgroup) {
struct stat sbuf;
if (stat("/dev/uhid", &sbuf) == 0) {
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
}
/**
* Allow access to /dev/net/tun
*
* When CONFIG_TUN=m, /dev/net/tun will exist but using it doesn't
* autoload the tun module but also /dev/net/tun isn't udev tagged
* until it is loaded. To work around this, if /dev/net/tun exists, add
* it unconditionally to the cgroup and rely on AppArmor to mediate the
* access. LP: #1859084
**/
static void sc_udev_allow_dev_net_tun(sc_device_cgroup *cgroup) {
struct stat sbuf;
if (stat("/dev/net/tun", &sbuf) == 0) {
sc_device_cgroup_allow(cgroup, S_IFCHR, major(sbuf.st_rdev), minor(sbuf.st_rdev));
}
}
/**
* Allow access to assigned devices.
*
* The snapd udev security backend uses udev rules to tag matching devices with
* tags corresponding to snap applications. Here we interrogate udev and allow
* access to all assigned devices.
**/
static void sc_udev_allow_assigned_device(sc_device_cgroup *cgroup, struct udev_device *device) {
const char *path = udev_device_get_syspath(device);
dev_t devnum = udev_device_get_devnum(device);
unsigned int major = major(devnum);
unsigned int minor = minor(devnum);
/* The manual page of udev_device_get_devnum says:
* > On success, udev_device_get_devnum() returns the device type of
* > the passed device. On failure, a device type with minor and major
* > number set to 0 is returned. */
if (major == 0 && minor == 0) {
debug("cannot get major/minor numbers for syspath %s", path);
return;
}
/* devnode is bound to the lifetime of the device and we cannot release
* it separately. */
const char *devnode = udev_device_get_devnode(device);
if (devnode == NULL) {
debug("cannot find /dev node from udev device");
return;
}
debug("inspecting type of device: %s", devnode);
struct stat file_info;
if (stat(devnode, &file_info) < 0) {
debug("cannot stat %s", devnode);
return;
}
int devtype = file_info.st_mode & S_IFMT;
if (devtype == S_IFBLK || devtype == S_IFCHR) {
sc_device_cgroup_allow(cgroup, devtype, major, minor);
}
}
static void sc_udev_setup_acls_common(sc_device_cgroup *cgroup) {
/* Allow access to various devices. */
sc_udev_allow_common(cgroup);
sc_udev_allow_pty_slaves(cgroup);
sc_udev_allow_nvidia(cgroup);
sc_udev_allow_hybris(cgroup);
sc_udev_allow_uhid(cgroup);
sc_udev_allow_dev_net_tun(cgroup);
}
static char *sc_security_to_udev_tag(const char *security_tag) {
char *udev_tag = sc_strdup(security_tag);
for (char *c = strchr(udev_tag, '.'); c != NULL; c = strchr(c, '.')) {
*c = '_';
}
return udev_tag;
}
static void sc_cleanup_udev(struct udev **udev) {
if (udev != NULL && *udev != NULL) {
udev_unref(*udev);
*udev = NULL;
}
}
static void sc_cleanup_udev_enumerate(struct udev_enumerate **enumerate) {
if (enumerate != NULL && *enumerate != NULL) {
udev_enumerate_unref(*enumerate);
*enumerate = NULL;
}
}
/* __sc_udev_device_has_current_tag will be filled at runtime if the libudev has
* this symbol.
*
* Note that we could try to define udev_device_has_current_tag with a weak
* attribute, which should in the normal case be the filled by ld.so when
* loading snap-confined. However this was observed to work in practice only
* when the binary itself is build with recent enough toolchain (eg. gcc &
* binutils on Ubuntu 20.04)
*/
static int (*__sc_udev_device_has_current_tag)(struct udev_device *udev_device, const char *tag) = NULL;
static void setup_current_tags_support(void) {
void *lib = dlopen("libudev.so.1", RTLD_NOW);
if (lib == NULL) {
debug("cannot load libudev.so.1: %s", dlerror());
/* bit unexpected as we use the library from the host and it's stable */
return;
}
/* check whether we have the symbol introduced in systemd v247 to inspect
* the CURRENT_TAGS property */
void *sym = dlsym(lib, "udev_device_has_current_tag");
if (sym == NULL) {
debug("cannot find current tags symbol: %s", dlerror());
/* symbol is not found in the library version */
(void)dlclose(lib);
return;
}
debug("libudev has current tags support");
__sc_udev_device_has_current_tag = sym;
/* lib goes out of scope and is leaked but we need sym and hence
* lib to be valid for the entire lifetime of the application
* lifecycle so this is fine. */
/* coverity[leaked_storage] */
}
void sc_setup_device_cgroup(const char *security_tag, sc_device_cgroup_mode mode) {
debug("setting up device cgroup, mode \"%s\"", mode == SC_DEVICE_CGROUP_MODE_REQUIRED ? "required" : "optional");
setup_current_tags_support();
if (__sc_udev_device_has_current_tag == NULL) {
debug("no current tags support present");
}
/* Derive the udev tag from the snap security tag.
*
* Because udev does not allow for dots in tag names, those are replaced by
* underscores in snapd. We just match that behavior. */
char *udev_tag SC_CLEANUP(sc_cleanup_string) = NULL;
udev_tag = sc_security_to_udev_tag(security_tag);
/* Use udev APIs to talk to udev-the-daemon to determine the list of
* "devices" with that tag assigned. The list may be empty, in which case
* there's no udev tagging in effect and we must refrain from constructing
* the cgroup as it would interfere with the execution of a program. */
struct udev SC_CLEANUP(sc_cleanup_udev) *udev = NULL;
udev = udev_new();
if (udev == NULL) {
die("cannot connect to udev");
}
struct udev_enumerate SC_CLEANUP(sc_cleanup_udev_enumerate) *devices = NULL;
devices = udev_enumerate_new(udev);
if (devices == NULL) {
die("cannot create udev device enumeration");
}
if (udev_enumerate_add_match_tag(devices, udev_tag) < 0) {
die("cannot add tag match to udev device enumeration");
}
if (udev_enumerate_scan_devices(devices) < 0) {
die("cannot enumerate udev devices");
}
/* NOTE: udev_list_entry is bound to life-cycle of the used udev_enumerate */
struct udev_list_entry *assigned;
assigned = udev_enumerate_get_list_entry(devices);
if (assigned == NULL) {
if (mode == SC_DEVICE_CGROUP_MODE_OPTIONAL) {
/* NOTE: Nothing is assigned, don't create or use the device cgroup. */
debug("no devices tagged with %s, skipping device cgroup setup", udev_tag);
return;
} else {
/* the device cgroup was requested to be set up despite of no
* devices being assigned to this snap */
debug("no devices tagged with %s, but device cgroup is required, proceeding with setup", udev_tag);
}
}
/* cgroup wrapper is lazily initialized when devices are actually
* assigned */
sc_device_cgroup *cgroup SC_CLEANUP(sc_device_cgroup_cleanup) = NULL;
if (mode == SC_DEVICE_CGROUP_MODE_REQUIRED) {
/* Normally the cgroup setup is done lazily, but since device cgroup is
* required, prepare for mediation of device access regardless of
* devices being properly tagged. */
cgroup = sc_device_cgroup_new(security_tag, 0);
/* Setup the device group access control list */
sc_udev_setup_acls_common(cgroup);
}
for (struct udev_list_entry *entry = assigned; entry != NULL; entry = udev_list_entry_get_next(entry)) {
const char *path = udev_list_entry_get_name(entry);
if (path == NULL) {
die("udev_list_entry_get_name failed");
}
struct udev_device *device = udev_device_new_from_syspath(udev, path);
/** This is a non-fatal error as devices can disappear asynchronously
* and on slow devices we may indeed observe a device that no longer
* exists.
*
* Similar debug + continue pattern repeats in all the udev calls in
* this function. Related to LP: #1881209 */
if (device == NULL) {
debug("cannot find device from syspath %s", path);
continue;
}
/* If we are able to query if the device has a current tag,
* do so and if there are no current tags, continue to prevent
* allowing assigned devices to the cgroup - this has the net
* desired effect of not re-creating device cgroups that were
* previously created/setup but should no longer be setup due
* to interface disconnection, etc. */
if (__sc_udev_device_has_current_tag != NULL) {
if (__sc_udev_device_has_current_tag(device, udev_tag) <= 0) {
debug("device %s has no matching current tag", path);
udev_device_unref(device);
continue;
}
debug("device %s has matching current tag", path);
}
if (cgroup == NULL) {
/* Lazy initialization of cgroup wrapper only when we are sure that
* there are devices assigned to this snap */
cgroup = sc_device_cgroup_new(security_tag, 0);
/* Setup the device group access control list */
sc_udev_setup_acls_common(cgroup);
}
sc_udev_allow_assigned_device(cgroup, device);
udev_device_unref(device);
}
if (cgroup != NULL) {
/* Move ourselves to the device cgroup */
sc_device_cgroup_attach_pid(cgroup, getpid());
debug("associated snap application process %i with device cgroup %s", getpid(), security_tag);
} else {
debug("device cgroup not set up for %s", udev_tag);
}
}