aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar netblue30 <netblue30@protonmail.com>2023-01-22 12:03:01 -0500
committerLibravatar netblue30 <netblue30@protonmail.com>2023-01-22 12:03:01 -0500
commitd1124df32d45e7ca1cc0b32ba961764ad5a84614 (patch)
tree0dfe176c8fc26ddf92780ad14265292a56826706 /src
parentMerge pull request #5609 from glitsj16/resolv-fixes (diff)
downloadfirejail-d1124df32d45e7ca1cc0b32ba961764ad5a84614.tar.gz
firejail-d1124df32d45e7ca1cc0b32ba961764ad5a84614.tar.zst
firejail-d1124df32d45e7ca1cc0b32ba961764ad5a84614.zip
private-etc rework: /etc file groups
Diffstat (limited to 'src')
-rw-r--r--src/firejail/firejail.h1
-rw-r--r--src/firejail/fs_etc.c320
-rw-r--r--src/firejail/main.c11
-rw-r--r--src/firejail/profile.c14
-rw-r--r--src/firejail/sandbox.c4
5 files changed, 203 insertions, 147 deletions
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index b6403bb41..66d2d8b83 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -693,6 +693,7 @@ void bandwidth_pid(pid_t pid, const char *command, const char *dev, int down, in
693void network_set_run_file(pid_t pid); 693void network_set_run_file(pid_t pid);
694 694
695// fs_etc.c 695// fs_etc.c
696char *fs_etc_build(char *str);
696void fs_resolvconf(void); 697void fs_resolvconf(void);
697void fs_machineid(void); 698void fs_machineid(void);
698void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list); 699void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list);
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
30static int etc_cnt = 0;
31static 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
48static char*etc_group_network[] = {
49 "hostname",
50 "hosts",
51 "resolv.conf",
52 "protocols",
53 NULL
54};
55
56static char *etc_group_gnome[] = {
57 "xdg",
58 "drirc",
59 "dconf",
60 "gtk-2.0",
61 "gtk-3.0",
62 NULL
63};
64
65static char *etc_group_kde[] = {
66 "xdg",
67 "drirc",
68 "kde4rc",
69 "kde5rc",
70 NULL
71};
72
73static char *etc_group_sound[] = {
74 "alsa",
75 "asound.conf",
76 "machine-id", // required by PulseAudio
77 "pulse",
78 NULL
79};
80
81static char *etc_group_tls_ca[] = {
82 "ca-certificates",
83 "ca-certificates.conf",
84 "crypto-policies",
85 "pki",
86 "ssl",
87 NULL
88};
89
90static 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
100static 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
125char *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
29void fs_resolvconf(void) { 167void fs_resolvconf(void) {
30 if (arg_debug) 168 if (arg_debug)
@@ -178,19 +316,11 @@ errexit:
178} 316}
179 317
180static void duplicate(const char *fname, const char *private_dir, const char *private_run_dir) { 318static 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
350static 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
221void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list) { 381void 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
301void 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
diff --git a/src/firejail/main.c b/src/firejail/main.c
index 18e9ae651..57fe4fb22 100644
--- a/src/firejail/main.c
+++ b/src/firejail/main.c
@@ -2044,6 +2044,17 @@ int main(int argc, char **argv, char **envp) {
2044 else if (strcmp(argv[i], "--keep-dev-shm") == 0) { 2044 else if (strcmp(argv[i], "--keep-dev-shm") == 0) {
2045 arg_keep_dev_shm = 1; 2045 arg_keep_dev_shm = 1;
2046 } 2046 }
2047 else if (strcmp(argv[i], "--private-etc") == 0) {
2048 if (checkcfg(CFG_PRIVATE_ETC)) {
2049 if (arg_writable_etc) {
2050 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
2051 exit(1);
2052 }
2053 arg_private_etc = 1;
2054 }
2055 else
2056 exit_err_feature("private-etc");
2057 }
2047 else if (strncmp(argv[i], "--private-etc=", 14) == 0) { 2058 else if (strncmp(argv[i], "--private-etc=", 14) == 0) {
2048 if (checkcfg(CFG_PRIVATE_ETC)) { 2059 if (checkcfg(CFG_PRIVATE_ETC)) {
2049 if (arg_writable_etc) { 2060 if (arg_writable_etc) {
diff --git a/src/firejail/profile.c b/src/firejail/profile.c
index acf206da6..a64198e68 100644
--- a/src/firejail/profile.c
+++ b/src/firejail/profile.c
@@ -1366,6 +1366,20 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
1366 return 0; 1366 return 0;
1367 } 1367 }
1368 1368
1369 // private /etc without a list of files and directories
1370 if (strcmp(ptr, "private-etc") == 0) {
1371 if (checkcfg(CFG_PRIVATE_ETC)) {
1372 if (arg_writable_etc) {
1373 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
1374 exit(1);
1375 }
1376 arg_private_etc = 1;
1377 }
1378 else
1379 warning_feature_disabled("private-etc");
1380 return 0;
1381 }
1382
1369 // private /opt list of files and directories 1383 // private /opt list of files and directories
1370 if (strncmp(ptr, "private-opt ", 12) == 0) { 1384 if (strncmp(ptr, "private-opt ", 12) == 0) {
1371 if (checkcfg(CFG_PRIVATE_OPT)) { 1385 if (checkcfg(CFG_PRIVATE_OPT)) {
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c
index 3d0d43965..ec3bc250e 100644
--- a/src/firejail/sandbox.c
+++ b/src/firejail/sandbox.c
@@ -1030,6 +1030,7 @@ int sandbox(void* sandbox_arg) {
1030 * 3. mount RUN_ETC_DIR at /etc 1030 * 3. mount RUN_ETC_DIR at /etc
1031 */ 1031 */
1032 timetrace_start(); 1032 timetrace_start();
1033 cfg.etc_private_keep = fs_etc_build(cfg.etc_private_keep);
1033 fs_private_dir_copy("/etc", RUN_ETC_DIR, cfg.etc_private_keep); 1034 fs_private_dir_copy("/etc", RUN_ETC_DIR, cfg.etc_private_keep);
1034 1035
1035 if (umount2("/etc/group", MNT_DETACH) == -1) 1036 if (umount2("/etc/group", MNT_DETACH) == -1)
@@ -1046,7 +1047,8 @@ int sandbox(void* sandbox_arg) {
1046 1047
1047 // openSUSE configuration is split between /etc and /usr/etc 1048 // openSUSE configuration is split between /etc and /usr/etc
1048 // process private-etc a second time 1049 // process private-etc a second time
1049 fs_private_dir_list("/usr/etc", RUN_USR_ETC_DIR, cfg.etc_private_keep); 1050 if (access("/usr/etc", F_OK) == 0)
1051 fs_private_dir_list("/usr/etc", RUN_USR_ETC_DIR, cfg.etc_private_keep);
1050 } 1052 }
1051 } 1053 }
1052 1054