aboutsummaryrefslogtreecommitdiffstats
path: root/src/firejail/fs_etc.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/firejail/fs_etc.c')
-rw-r--r--src/firejail/fs_etc.c320
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
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