Skip to content

Commit 7ae5db2

Browse files
committed
feature: use globbing in hardcoded numbered /dev paths
Certain devices appear in numbered paths in /dev (such as `/dev/hidraw0` or `/dev/hidraw1`) when they are plugged in. When private-dev is used, it attempts to include a hardcoded list of /dev paths in the sandbox. Since the hardcoded paths only go up to a specific number (mostly from 0 to 9), devices that appear at higher numbered paths (such as `/dev/hidraw10` or `/dev/hidraw20`) do not show up in /dev when private-dev is enabled. This issue also affects options that attempt to disable numbered paths (such as --no3d and --nou2f), independently of whether private-dev is used. Fix this by using glob patterns (such as `/dev/hidraw[0-9]*`) to ensure that all relevant numbered paths are matched regardless of number. The globbing is similar to the one from commit 2993298 ("firecfg: parse config files in /etc/firejail/firecfg.d", 2023-06-29) / PR netblue30#5876. Closes netblue30#2723. Reported-by: @tsankuanglee Reported-by: @WPettersson
1 parent edf623f commit 7ae5db2

File tree

1 file changed

+149
-69
lines changed

1 file changed

+149
-69
lines changed

src/firejail/fs_dev.c

+149-69
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <errno.h>
2626
#include <fcntl.h>
2727
#include <glob.h>
28+
#include <libgen.h>
2829
#include <pwd.h>
2930
#ifndef _BSD_SOURCE
3031
#define _BSD_SOURCE
@@ -67,56 +68,24 @@ static DevTypeStr dev_type_str[] = {
6768
};
6869

6970
typedef struct {
70-
const char *dev_fname;
71-
const char *run_fname;
71+
const char *dev_pattern;
72+
const char *run_pattern;
7273
DEV_TYPE type;
7374
} DevEntry;
7475

7576
static DevEntry dev[] = {
7677
{"/dev/snd", RUN_DEV_DIR "/snd", DEV_SOUND}, // sound device
7778
{"/dev/dri", RUN_DEV_DIR "/dri", DEV_3D}, // 3d devices
7879
{"/dev/kfd", RUN_DEV_DIR "/kfd", DEV_3D},
79-
{"/dev/nvidia0", RUN_DEV_DIR "/nvidia0", DEV_3D},
80-
{"/dev/nvidia1", RUN_DEV_DIR "/nvidia1", DEV_3D},
81-
{"/dev/nvidia2", RUN_DEV_DIR "/nvidia2", DEV_3D},
82-
{"/dev/nvidia3", RUN_DEV_DIR "/nvidia3", DEV_3D},
83-
{"/dev/nvidia4", RUN_DEV_DIR "/nvidia4", DEV_3D},
84-
{"/dev/nvidia5", RUN_DEV_DIR "/nvidia5", DEV_3D},
85-
{"/dev/nvidia6", RUN_DEV_DIR "/nvidia6", DEV_3D},
86-
{"/dev/nvidia7", RUN_DEV_DIR "/nvidia7", DEV_3D},
87-
{"/dev/nvidia8", RUN_DEV_DIR "/nvidia8", DEV_3D},
88-
{"/dev/nvidia9", RUN_DEV_DIR "/nvidia9", DEV_3D},
80+
{"/dev/nvidia[0-9]*", RUN_DEV_DIR "/nvidia[0-9]*", DEV_3D},
8981
{"/dev/nvidiactl", RUN_DEV_DIR "/nvidiactl", DEV_3D},
9082
{"/dev/nvidia-modeset", RUN_DEV_DIR "/nvidia-modeset", DEV_3D},
9183
{"/dev/nvidia-uvm", RUN_DEV_DIR "/nvidia-uvm", DEV_3D},
92-
{"/dev/video0", RUN_DEV_DIR "/video0", DEV_VIDEO}, // video camera devices
93-
{"/dev/video1", RUN_DEV_DIR "/video1", DEV_VIDEO},
94-
{"/dev/video2", RUN_DEV_DIR "/video2", DEV_VIDEO},
95-
{"/dev/video3", RUN_DEV_DIR "/video3", DEV_VIDEO},
96-
{"/dev/video4", RUN_DEV_DIR "/video4", DEV_VIDEO},
97-
{"/dev/video5", RUN_DEV_DIR "/video5", DEV_VIDEO},
98-
{"/dev/video6", RUN_DEV_DIR "/video6", DEV_VIDEO},
99-
{"/dev/video7", RUN_DEV_DIR "/video7", DEV_VIDEO},
100-
{"/dev/video8", RUN_DEV_DIR "/video8", DEV_VIDEO},
101-
{"/dev/video9", RUN_DEV_DIR "/video9", DEV_VIDEO},
84+
{"/dev/video[0-9]*", RUN_DEV_DIR "/video[0-9]*", DEV_VIDEO}, // video camera devices
10285
{"/dev/dvb", RUN_DEV_DIR "/dvb", DEV_TV}, // DVB (Digital Video Broadcasting) - TV device
103-
{"/dev/sr0", RUN_DEV_DIR "/sr0", DEV_DVD}, // for DVD and audio CD players
104-
{"/dev/tpm0", RUN_DEV_DIR "/tpm0", DEV_TPM}, // TPM (Trusted Platform Module) devices
105-
{"/dev/tpm1", RUN_DEV_DIR "/tpm1", DEV_TPM},
106-
{"/dev/tpm2", RUN_DEV_DIR "/tpm2", DEV_TPM},
107-
{"/dev/tpm3", RUN_DEV_DIR "/tpm3", DEV_TPM},
108-
{"/dev/tpm4", RUN_DEV_DIR "/tpm4", DEV_TPM},
109-
{"/dev/tpm5", RUN_DEV_DIR "/tpm5", DEV_TPM},
110-
{"/dev/hidraw0", RUN_DEV_DIR "/hidraw0", DEV_U2F},
111-
{"/dev/hidraw1", RUN_DEV_DIR "/hidraw1", DEV_U2F},
112-
{"/dev/hidraw2", RUN_DEV_DIR "/hidraw2", DEV_U2F},
113-
{"/dev/hidraw3", RUN_DEV_DIR "/hidraw3", DEV_U2F},
114-
{"/dev/hidraw4", RUN_DEV_DIR "/hidraw4", DEV_U2F},
115-
{"/dev/hidraw5", RUN_DEV_DIR "/hidraw5", DEV_U2F},
116-
{"/dev/hidraw6", RUN_DEV_DIR "/hidraw6", DEV_U2F},
117-
{"/dev/hidraw7", RUN_DEV_DIR "/hidraw7", DEV_U2F},
118-
{"/dev/hidraw8", RUN_DEV_DIR "/hidraw8", DEV_U2F},
119-
{"/dev/hidraw9", RUN_DEV_DIR "/hidraw9", DEV_U2F},
86+
{"/dev/sr[0-9]*", RUN_DEV_DIR "/sr[0-9]*", DEV_DVD}, // for DVD and audio CD players
87+
{"/dev/tpm[0-9]*", RUN_DEV_DIR "/tpm[0-9]*", DEV_TPM}, // TPM (Trusted Platform Module) devices
88+
{"/dev/hidraw[0-9]*", RUN_DEV_DIR "/hidraw[0-9]*", DEV_U2F},
12089
{"/dev/usb", RUN_DEV_DIR "/usb", DEV_U2F}, // USB devices such as Yubikey, U2F
12190
{"/dev/input", RUN_DEV_DIR "/input", DEV_INPUT},
12291
{"/dev/ntsync", RUN_DEV_DIR "/ntsync", DEV_NTSYNC},
@@ -196,11 +165,97 @@ static void deventry_mount(const char *source,
196165
fs_logger2("whitelist", target);
197166
}
198167

168+
// For every path in source_pattern, mount it on the dirname of target_pattern.
169+
//
170+
// Example:
171+
//
172+
// deventry_mount_glob("/run/foo*", "/dev/foo*") ->
173+
// mount("/run/foo1", "/dev/foo1")
174+
// mount("/run/foo2", "/dev/foo2")
175+
// ...
176+
static void deventry_mount_glob(const char *source_pattern,
177+
const char *target_pattern, DEV_TYPE type) {
178+
assert(source_pattern);
179+
assert(target_pattern);
180+
assert(type >= DEV_NONE && type < DEV_MAX);
181+
182+
const char *typestr = dev_type_str[type].str;
183+
if (arg_debug) {
184+
printf("Globbing %s on %s (type=%s)\n", source_pattern,
185+
target_pattern, typestr);
186+
}
187+
188+
// sanity check to avoid arguments like ("/run/foo*", "/dev/bar*")
189+
const char *source_pattern_base = gnu_basename(source_pattern);
190+
const char *target_pattern_base = gnu_basename(target_pattern);
191+
if (strcmp(source_pattern_base, target_pattern_base) != 0) {
192+
fprintf(stderr, "Error: patterns do not match: %s / %s (type=%s)\n",
193+
source_pattern_base, target_pattern_base, typestr);
194+
exit(1);
195+
}
196+
197+
EUID_USER();
198+
glob_t globbuf;
199+
int globerr = glob(source_pattern, 0, NULL, &globbuf);
200+
EUID_ROOT();
201+
if (globerr == GLOB_NOMATCH) {
202+
if (arg_debug) {
203+
printf("No match %s (type=%s)\n", source_pattern,
204+
typestr);
205+
}
206+
return;
207+
} else if (globerr) {
208+
fwarning("failed to glob pattern %s (type=%s): %s\n",
209+
source_pattern, typestr, strerror(errno));
210+
return;
211+
}
212+
213+
// strdup for dirname
214+
char *tmp = strdup(target_pattern);
215+
if (!tmp)
216+
errExit("strdup");
217+
const char *target_dir = dirname(tmp);
218+
if (strcmp(target_dir, ".") == 0 ||
219+
strcmp(target_dir, "..") == 0) {
220+
fprintf(stderr, "Error: invalid target_dir: %s -> %s (type=%s)\n",
221+
target_pattern, target_dir, typestr);
222+
exit(1);
223+
}
224+
225+
size_t i;
226+
for (i = 0; i < globbuf.gl_pathc; i++) {
227+
const char *source = globbuf.gl_pathv[i];
228+
assert(source);
229+
230+
const char *source_base = gnu_basename(source);
231+
if (strcmp(source_base, ".") == 0 ||
232+
strcmp(source_base, "..") == 0) {
233+
fprintf(stderr, "Error: invalid source_base: %s -> %s -> %s (type=%s)\n",
234+
source_pattern, source, source_base, typestr);
235+
exit(1);
236+
}
237+
238+
char *target = NULL;
239+
if (asprintf(&target, "%s/%s", target_dir, source_base) == -1)
240+
errExit("asprintf");
241+
242+
deventry_mount(source, target, type);
243+
free(target);
244+
}
245+
246+
free(tmp);
247+
globfree(&globbuf);
248+
}
249+
250+
// Note: By the time that this function is called for private-dev, RUN_DEV_DIR
251+
// points to the real /dev directory and tmpfs is mounted on top of /dev, so
252+
// run_pattern is the source path and dev_pattern is the target path when
253+
// bind-mounting.
199254
static void deventry_mount_all(void) {
200255
int i = 0;
201-
while (dev[i].dev_fname != NULL) {
202-
deventry_mount(dev[i].run_fname, dev[i].dev_fname,
203-
dev[i].type);
256+
while (dev[i].dev_pattern != NULL) {
257+
deventry_mount_glob(dev[i].run_pattern, dev[i].dev_pattern,
258+
dev[i].type);
204259
i++;
205260
}
206261
}
@@ -382,102 +437,127 @@ void fs_private_dev(void) {
382437
}
383438
}
384439

385-
void fs_dev_disable_sound(void) {
386-
unsigned i = 0;
387-
while (dev[i].dev_fname != NULL) {
388-
if (dev[i].type == DEV_SOUND)
389-
disable_file_or_dir(dev[i].dev_fname);
390-
i++;
440+
void fs_dev_disable_glob(const char *pattern, DEV_TYPE type,
441+
int skip_symlinks) {
442+
assert(pattern);
443+
assert(type >= DEV_NONE && type < DEV_MAX);
444+
445+
const char *typestr = dev_type_str[type].str;
446+
if (arg_debug) {
447+
printf("Globbing %s (type=%s skip_symlinks=%d)\n", pattern,
448+
typestr, skip_symlinks);
391449
}
392450

393-
// disable all jack sockets in /dev/shm
394451
EUID_USER();
395452
glob_t globbuf;
396-
int globerr = glob("/dev/shm/jack*", GLOB_NOSORT, NULL, &globbuf);
453+
int globerr = glob(pattern, 0, NULL, &globbuf);
397454
EUID_ROOT();
398-
if (globerr)
455+
if (globerr == GLOB_NOMATCH) {
456+
if (arg_debug)
457+
printf("No match %s (type=%s)\n", pattern, typestr);
458+
return;
459+
} else if (globerr) {
460+
fwarning("failed to glob pattern %s (type=%s): %s\n", pattern,
461+
typestr, strerror(errno));
399462
return;
463+
}
400464

465+
size_t i;
401466
for (i = 0; i < globbuf.gl_pathc; i++) {
402467
char *path = globbuf.gl_pathv[i];
403468
assert(path);
404-
if (is_link(path)) {
405-
fwarning("skipping nosound for %s because it is a symbolic link\n", path);
469+
470+
if (skip_symlinks && is_link(path)) {
471+
fwarning("skipping %s because it is a symbolic link (type=%s)\n",
472+
pattern, path);
406473
continue;
407474
}
408475
disable_file_or_dir(path);
409476
}
477+
410478
globfree(&globbuf);
411479
}
412480

481+
void fs_dev_disable_sound(void) {
482+
int i = 0;
483+
while (dev[i].dev_pattern != NULL) {
484+
if (dev[i].type == DEV_SOUND)
485+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_SOUND, 0);
486+
i++;
487+
}
488+
489+
// disable all jack sockets in /dev/shm
490+
fs_dev_disable_glob("/dev/shm/jack*", DEV_SOUND, 1);
491+
}
492+
413493
void fs_dev_disable_video(void) {
414494
int i = 0;
415-
while (dev[i].dev_fname != NULL) {
495+
while (dev[i].dev_pattern != NULL) {
416496
if (dev[i].type == DEV_VIDEO)
417-
disable_file_or_dir(dev[i].dev_fname);
497+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_VIDEO, 0);
418498
i++;
419499
}
420500
}
421501

422502
void fs_dev_disable_3d(void) {
423503
int i = 0;
424-
while (dev[i].dev_fname != NULL) {
504+
while (dev[i].dev_pattern != NULL) {
425505
if (dev[i].type == DEV_3D)
426-
disable_file_or_dir(dev[i].dev_fname);
506+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_3D, 0);
427507
i++;
428508
}
429509
}
430510

431511
void fs_dev_disable_tv(void) {
432512
int i = 0;
433-
while (dev[i].dev_fname != NULL) {
513+
while (dev[i].dev_pattern != NULL) {
434514
if (dev[i].type == DEV_TV)
435-
disable_file_or_dir(dev[i].dev_fname);
515+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_TV, 0);
436516
i++;
437517
}
438518
}
439519

440520
void fs_dev_disable_dvd(void) {
441521
int i = 0;
442-
while (dev[i].dev_fname != NULL) {
522+
while (dev[i].dev_pattern != NULL) {
443523
if (dev[i].type == DEV_DVD)
444-
disable_file_or_dir(dev[i].dev_fname);
524+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_DVD, 0);
445525
i++;
446526
}
447527
}
448528

449529
void fs_dev_disable_tpm(void) {
450530
int i = 0;
451-
while (dev[i].dev_fname != NULL) {
531+
while (dev[i].dev_pattern != NULL) {
452532
if (dev[i].type == DEV_TPM)
453-
disable_file_or_dir(dev[i].dev_fname);
533+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_TPM, 0);
454534
i++;
455535
}
456536
}
457537

458538
void fs_dev_disable_u2f(void) {
459539
int i = 0;
460-
while (dev[i].dev_fname != NULL) {
540+
while (dev[i].dev_pattern != NULL) {
461541
if (dev[i].type == DEV_U2F)
462-
disable_file_or_dir(dev[i].dev_fname);
542+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_U2F, 0);
463543
i++;
464544
}
465545
}
466546

467547
void fs_dev_disable_input(void) {
468548
int i = 0;
469-
while (dev[i].dev_fname != NULL) {
549+
while (dev[i].dev_pattern != NULL) {
470550
if (dev[i].type == DEV_INPUT)
471-
disable_file_or_dir(dev[i].dev_fname);
551+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_INPUT, 0);
472552
i++;
473553
}
474554
}
475555

476556
void fs_dev_disable_ntsync(void) {
477557
int i = 0;
478-
while (dev[i].dev_fname != NULL) {
558+
while (dev[i].dev_pattern != NULL) {
479559
if (dev[i].type == DEV_NTSYNC)
480-
disable_file_or_dir(dev[i].dev_fname);
560+
fs_dev_disable_glob(dev[i].dev_pattern, DEV_NTSYNC, 0);
481561
i++;
482562
}
483563
}

0 commit comments

Comments
 (0)