diff options
author | 2023-01-22 12:03:01 -0500 | |
---|---|---|
committer | 2023-01-22 12:03:01 -0500 | |
commit | d1124df32d45e7ca1cc0b32ba961764ad5a84614 (patch) | |
tree | 0dfe176c8fc26ddf92780ad14265292a56826706 /src/firejail/fs_etc.c | |
parent | Merge pull request #5609 from glitsj16/resolv-fixes (diff) | |
download | firejail-d1124df32d45e7ca1cc0b32ba961764ad5a84614.tar.gz firejail-d1124df32d45e7ca1cc0b32ba961764ad5a84614.tar.zst firejail-d1124df32d45e7ca1cc0b32ba961764ad5a84614.zip |
private-etc rework: /etc file groups
Diffstat (limited to 'src/firejail/fs_etc.c')
-rw-r--r-- | src/firejail/fs_etc.c | 320 |
1 files changed, 174 insertions, 146 deletions
diff --git a/src/firejail/fs_etc.c b/src/firejail/fs_etc.c index 5eb3e34e0..bc7cd901c 100644 --- a/src/firejail/fs_etc.c +++ b/src/firejail/fs_etc.c | |||
@@ -24,7 +24,145 @@ | |||
24 | #include <sys/types.h> | 24 | #include <sys/types.h> |
25 | #include <time.h> | 25 | #include <time.h> |
26 | #include <unistd.h> | 26 | #include <unistd.h> |
27 | #include <dirent.h> | 27 | #include <glob.h> |
28 | |||
29 | #define ETC_MAX 256 | ||
30 | static int etc_cnt = 0; | ||
31 | static char *etc_list[ETC_MAX + 1] = { // plus 1 for ending NULL pointer | ||
32 | "alternatives", | ||
33 | "fonts", | ||
34 | "ld.so.cache", | ||
35 | "ld.so.conf", | ||
36 | "ld.so.conf.d", | ||
37 | "ld.so.preload", | ||
38 | "locale", | ||
39 | "locale.alias", | ||
40 | "locale.conf", | ||
41 | "locale.gen", | ||
42 | "localtime", | ||
43 | "nsswitch.conf", | ||
44 | "passwd", | ||
45 | NULL | ||
46 | }; | ||
47 | |||
48 | static char*etc_group_network[] = { | ||
49 | "hostname", | ||
50 | "hosts", | ||
51 | "resolv.conf", | ||
52 | "protocols", | ||
53 | NULL | ||
54 | }; | ||
55 | |||
56 | static char *etc_group_gnome[] = { | ||
57 | "xdg", | ||
58 | "drirc", | ||
59 | "dconf", | ||
60 | "gtk-2.0", | ||
61 | "gtk-3.0", | ||
62 | NULL | ||
63 | }; | ||
64 | |||
65 | static char *etc_group_kde[] = { | ||
66 | "xdg", | ||
67 | "drirc", | ||
68 | "kde4rc", | ||
69 | "kde5rc", | ||
70 | NULL | ||
71 | }; | ||
72 | |||
73 | static char *etc_group_sound[] = { | ||
74 | "alsa", | ||
75 | "asound.conf", | ||
76 | "machine-id", // required by PulseAudio | ||
77 | "pulse", | ||
78 | NULL | ||
79 | }; | ||
80 | |||
81 | static char *etc_group_tls_ca[] = { | ||
82 | "ca-certificates", | ||
83 | "ca-certificates.conf", | ||
84 | "crypto-policies", | ||
85 | "pki", | ||
86 | "ssl", | ||
87 | NULL | ||
88 | }; | ||
89 | |||
90 | static void etc_copy_group(char **pptr) { | ||
91 | assert(pptr); | ||
92 | |||
93 | while (*pptr != NULL) { | ||
94 | etc_list[etc_cnt++] = *pptr; | ||
95 | etc_list[etc_cnt] = NULL; | ||
96 | pptr++; | ||
97 | } | ||
98 | } | ||
99 | |||
100 | static void etc_add(const char *file) { | ||
101 | assert(file); | ||
102 | if (etc_cnt >= ETC_MAX) { | ||
103 | fprintf(stderr, "Error: size of private_etc list exceeded (%d maximum)\n", ETC_MAX); | ||
104 | exit(1); | ||
105 | } | ||
106 | |||
107 | // look for file in the current list | ||
108 | int i; | ||
109 | for (i = 0; i < etc_cnt; i++) { | ||
110 | if (strcmp(file, etc_list[i]) == 0) { | ||
111 | if (arg_debug) | ||
112 | printf("private-etc arguments: skip %s\n", file); | ||
113 | return; | ||
114 | } | ||
115 | } | ||
116 | |||
117 | char *ptr = strdup(file); | ||
118 | if (!ptr) | ||
119 | errExit("strdup"); | ||
120 | etc_list[etc_cnt++] = ptr; | ||
121 | etc_list[etc_cnt] = NULL; | ||
122 | } | ||
123 | |||
124 | // str can be NULL | ||
125 | char *fs_etc_build(char *str) { | ||
126 | while (etc_list[etc_cnt++]); | ||
127 | etc_cnt--; | ||
128 | if (!arg_nonetwork) | ||
129 | etc_copy_group(&etc_group_network[0]); | ||
130 | if (!arg_nosound) | ||
131 | etc_copy_group(&etc_group_sound[0]); | ||
132 | |||
133 | // parsing | ||
134 | if (str) { | ||
135 | char* ptr = strtok(str, ","); | ||
136 | while (ptr) { | ||
137 | // look for standard groups | ||
138 | if (strcmp(ptr, "TLS-CA") == 0) | ||
139 | etc_copy_group(&etc_group_tls_ca[0]); | ||
140 | if (strcmp(ptr, "GNOME") == 0) | ||
141 | etc_copy_group(&etc_group_gnome[0]); | ||
142 | if (strcmp(ptr, "KDE") == 0) | ||
143 | etc_copy_group(&etc_group_kde[0]); | ||
144 | else | ||
145 | etc_add(ptr); | ||
146 | ptr = strtok(NULL, ","); | ||
147 | } | ||
148 | } | ||
149 | |||
150 | // manufacture the new string | ||
151 | int len = 0; | ||
152 | int i; | ||
153 | for (i = 0; i < etc_cnt; i++) | ||
154 | len += strlen(etc_list[i]) + 1; // plus 1 for the trailing ',' | ||
155 | char *rv = malloc(len + 1); | ||
156 | if (!rv) | ||
157 | errExit("malloc"); | ||
158 | char *ptr = rv; | ||
159 | for (i = 0; i < etc_cnt; i++) { | ||
160 | sprintf(ptr, "%s,", etc_list[i]); | ||
161 | ptr += strlen(etc_list[i]) + 1; | ||
162 | } | ||
163 | |||
164 | return rv; | ||
165 | } | ||
28 | 166 | ||
29 | void fs_resolvconf(void) { | 167 | void fs_resolvconf(void) { |
30 | if (arg_debug) | 168 | if (arg_debug) |
@@ -178,19 +316,11 @@ errexit: | |||
178 | } | 316 | } |
179 | 317 | ||
180 | static void duplicate(const char *fname, const char *private_dir, const char *private_run_dir) { | 318 | static void duplicate(const char *fname, const char *private_dir, const char *private_run_dir) { |
181 | assert(fname); | ||
182 | |||
183 | if (*fname == '~' || *fname == '/' || strstr(fname, "..")) { | ||
184 | fprintf(stderr, "Error: \"%s\" is an invalid filename\n", fname); | ||
185 | exit(1); | ||
186 | } | ||
187 | invalid_filename(fname, 0); // no globbing | ||
188 | |||
189 | char *src; | 319 | char *src; |
190 | if (asprintf(&src, "%s/%s", private_dir, fname) == -1) | 320 | if (asprintf(&src, "%s/%s", private_dir, fname) == -1) |
191 | errExit("asprintf"); | 321 | errExit("asprintf"); |
322 | |||
192 | if (check_dir_or_file(src) == 0) { | 323 | if (check_dir_or_file(src) == 0) { |
193 | fwarning("skipping %s for private %s\n", fname, private_dir); | ||
194 | free(src); | 324 | free(src); |
195 | return; | 325 | return; |
196 | } | 326 | } |
@@ -204,8 +334,9 @@ static void duplicate(const char *fname, const char *private_dir, const char *pr | |||
204 | 334 | ||
205 | build_dirs(src, dst, strlen(private_dir), strlen(private_run_dir)); | 335 | build_dirs(src, dst, strlen(private_dir), strlen(private_run_dir)); |
206 | 336 | ||
207 | // follow links! this will make a copy of the file or directory pointed by the symlink | 337 | // follow links by default, thus making a copy of the file or directory pointed by the symlink |
208 | // this will solve problems such as NixOS #4887 | 338 | // this will solve problems such as NixOS #4887 |
339 | // | ||
209 | // don't follow links to dynamic directories such as /proc | 340 | // don't follow links to dynamic directories such as /proc |
210 | if (strcmp(src, "/etc/mtab") == 0) | 341 | if (strcmp(src, "/etc/mtab") == 0) |
211 | sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FCOPY, src, dst); | 342 | sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FCOPY, src, dst); |
@@ -214,9 +345,38 @@ static void duplicate(const char *fname, const char *private_dir, const char *pr | |||
214 | 345 | ||
215 | free(dst); | 346 | free(dst); |
216 | fs_logger2("clone", src); | 347 | fs_logger2("clone", src); |
217 | free(src); | ||
218 | } | 348 | } |
219 | 349 | ||
350 | static void duplicate_globbing(const char *fname, const char *private_dir, const char *private_run_dir) { | ||
351 | assert(fname); | ||
352 | |||
353 | if (*fname == '~' || *fname == '/' || strstr(fname, "..")) { | ||
354 | fprintf(stderr, "Error: \"%s\" is an invalid filename\n", fname); | ||
355 | exit(1); | ||
356 | } | ||
357 | invalid_filename(fname, 1); // no globbing | ||
358 | |||
359 | char *pattern; | ||
360 | if (asprintf(&pattern, "%s/%s", private_dir, fname) == -1) | ||
361 | errExit("asprintf"); | ||
362 | |||
363 | glob_t globbuf; | ||
364 | int globerr = glob(pattern, GLOB_NOCHECK | GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf); | ||
365 | if (globerr) { | ||
366 | fprintf(stderr, "Error: failed to glob pattern %s\n", pattern); | ||
367 | exit(1); | ||
368 | } | ||
369 | |||
370 | size_t i; | ||
371 | int len = strlen(private_dir); | ||
372 | for (i = 0; i < globbuf.gl_pathc; i++) { | ||
373 | char *path = globbuf.gl_pathv[i]; | ||
374 | duplicate(path + len + 1, private_dir, private_run_dir); | ||
375 | } | ||
376 | |||
377 | globfree(&globbuf); | ||
378 | free(pattern); | ||
379 | } | ||
220 | 380 | ||
221 | void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list) { | 381 | void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list) { |
222 | assert(private_dir); | 382 | assert(private_dir); |
@@ -256,10 +416,10 @@ void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, c | |||
256 | fprintf(stderr, "Error: invalid private %s argument\n", private_dir); | 416 | fprintf(stderr, "Error: invalid private %s argument\n", private_dir); |
257 | exit(1); | 417 | exit(1); |
258 | } | 418 | } |
259 | duplicate(ptr, private_dir, private_run_dir); | 419 | duplicate_globbing(ptr, private_dir, private_run_dir); |
260 | 420 | ||
261 | while ((ptr = strtok(NULL, ",")) != NULL) | 421 | while ((ptr = strtok(NULL, ",")) != NULL) |
262 | duplicate(ptr, private_dir, private_run_dir); | 422 | duplicate_globbing(ptr, private_dir, private_run_dir); |
263 | free(dlist); | 423 | free(dlist); |
264 | fs_logger_print(); | 424 | fs_logger_print(); |
265 | } | 425 | } |
@@ -297,135 +457,3 @@ void fs_private_dir_list(const char *private_dir, const char *private_run_dir, c | |||
297 | fmessage("Private %s installed in %0.2f ms\n", private_dir, timetrace_end()); | 457 | fmessage("Private %s installed in %0.2f ms\n", private_dir, timetrace_end()); |
298 | } | 458 | } |
299 | 459 | ||
300 | #if 0 | ||
301 | void fs_rebuild_etc(void) { | ||
302 | int have_dhcp = 1; | ||
303 | if (cfg.dns1 == NULL && !any_dhcp()) { | ||
304 | // Disabling this option ensures that updates to files using | ||
305 | // rename(2) propagate into the sandbox, in order to avoid | ||
306 | // breaking /etc/resolv.conf (issue #5010). | ||
307 | if (!checkcfg(CFG_ETC_HIDE_BLACKLISTED)) | ||
308 | return; | ||
309 | have_dhcp = 0; | ||
310 | } | ||
311 | |||
312 | if (arg_debug) | ||
313 | printf("rebuilding /etc directory\n"); | ||
314 | if (mkdir(RUN_DNS_ETC, 0755)) | ||
315 | errExit("mkdir"); | ||
316 | selinux_relabel_path(RUN_DNS_ETC, "/etc"); | ||
317 | fs_logger("tmpfs /etc"); | ||
318 | |||
319 | DIR *dir = opendir("/etc"); | ||
320 | if (!dir) | ||
321 | errExit("opendir"); | ||
322 | |||
323 | struct stat s; | ||
324 | struct dirent *entry; | ||
325 | while ((entry = readdir(dir))) { | ||
326 | if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) | ||
327 | continue; | ||
328 | |||
329 | // skip files in cfg.profile_rebuild_etc list | ||
330 | // these files are already blacklisted | ||
331 | { | ||
332 | ProfileEntry *prf = cfg.profile_rebuild_etc; | ||
333 | int found = 0; | ||
334 | while (prf) { | ||
335 | if (strcmp(entry->d_name, prf->data + 5) == 0) { // 5 is strlen("/etc/") | ||
336 | found = 1; | ||
337 | break; | ||
338 | } | ||
339 | prf = prf->next; | ||
340 | } | ||
341 | if (found) | ||
342 | continue; | ||
343 | } | ||
344 | |||
345 | // for resolv.conf we might have to create a brand new file later | ||
346 | if (have_dhcp && | ||
347 | (strcmp(entry->d_name, "resolv.conf") == 0 || | ||
348 | strcmp(entry->d_name, "resolv.conf.dhclient-new") == 0)) | ||
349 | continue; | ||
350 | // printf("linking %s\n", entry->d_name); | ||
351 | |||
352 | char *src; | ||
353 | if (asprintf(&src, "/etc/%s", entry->d_name) == -1) | ||
354 | errExit("asprintf"); | ||
355 | if (stat(src, &s) != 0) { | ||
356 | free(src); | ||
357 | continue; | ||
358 | } | ||
359 | |||
360 | char *dest; | ||
361 | if (asprintf(&dest, "%s/%s", RUN_DNS_ETC, entry->d_name) == -1) | ||
362 | errExit("asprintf"); | ||
363 | |||
364 | int symlink_done = 0; | ||
365 | if (is_link(src)) { | ||
366 | char *rp =realpath(src, NULL); | ||
367 | if (rp == NULL) { | ||
368 | free(src); | ||
369 | free(dest); | ||
370 | continue; | ||
371 | } | ||
372 | if (symlink(rp, dest)) | ||
373 | errExit("symlink"); | ||
374 | else | ||
375 | symlink_done = 1; | ||
376 | } | ||
377 | else if (S_ISDIR(s.st_mode)) | ||
378 | create_empty_dir_as_root(dest, S_IRWXU); | ||
379 | else | ||
380 | create_empty_file_as_root(dest, S_IRUSR | S_IWUSR); | ||
381 | |||
382 | // bind-mount src on top of dest | ||
383 | if (!symlink_done) { | ||
384 | if (mount(src, dest, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
385 | errExit("mount bind mirroring /etc"); | ||
386 | } | ||
387 | fs_logger2("clone", src); | ||
388 | |||
389 | free(src); | ||
390 | free(dest); | ||
391 | } | ||
392 | closedir(dir); | ||
393 | |||
394 | // mount bind our private etc directory on top of /etc | ||
395 | if (arg_debug) | ||
396 | printf("Mount-bind %s on top of /etc\n", RUN_DNS_ETC); | ||
397 | if (mount(RUN_DNS_ETC, "/etc", NULL, MS_BIND|MS_REC, NULL) < 0) | ||
398 | errExit("mount bind mirroring /etc"); | ||
399 | fs_logger("mount /etc"); | ||
400 | |||
401 | if (have_dhcp == 0) | ||
402 | return; | ||
403 | |||
404 | if (arg_debug) | ||
405 | printf("Creating a new /etc/resolv.conf file\n"); | ||
406 | FILE *fp = fopen("/etc/resolv.conf", "wxe"); | ||
407 | if (!fp) { | ||
408 | fprintf(stderr, "Error: cannot create /etc/resolv.conf file\n"); | ||
409 | exit(1); | ||
410 | } | ||
411 | |||
412 | if (cfg.dns1) { | ||
413 | if (any_dhcp()) | ||
414 | fwarning("network setup uses DHCP, nameservers will likely be overwritten\n"); | ||
415 | fprintf(fp, "nameserver %s\n", cfg.dns1); | ||
416 | } | ||
417 | if (cfg.dns2) | ||
418 | fprintf(fp, "nameserver %s\n", cfg.dns2); | ||
419 | if (cfg.dns3) | ||
420 | fprintf(fp, "nameserver %s\n", cfg.dns3); | ||
421 | if (cfg.dns4) | ||
422 | fprintf(fp, "nameserver %s\n", cfg.dns4); | ||
423 | |||
424 | // mode and owner | ||
425 | SET_PERMS_STREAM(fp, 0, 0, 0644); | ||
426 | |||
427 | fclose(fp); | ||
428 | |||
429 | fs_logger("create /etc/resolv.conf"); | ||
430 | } | ||
431 | #endif \ No newline at end of file | ||