aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/etc-cleanup/Makefile9
-rw-r--r--src/etc-cleanup/main.c255
-rw-r--r--src/firecfg/firecfg.config9
-rw-r--r--src/firejail/Makefile4
-rw-r--r--src/firejail/firejail.h4
-rw-r--r--src/firejail/fs.c5
-rw-r--r--src/firejail/fs_etc.c293
-rw-r--r--src/firejail/fs_home.c9
-rw-r--r--src/firejail/main.c28
-rw-r--r--src/firejail/profile.c32
-rw-r--r--src/firejail/sandbox.c12
-rw-r--r--src/firejail/usage.c3
-rw-r--r--src/firejail/util.c1
-rw-r--r--src/include/etc_groups.h103
-rw-r--r--src/include/rundefs.h2
-rw-r--r--src/man/firejail-profile.txt3
-rw-r--r--src/man/firejail.txt49
-rw-r--r--src/zsh_completion/_firejail.in1
18 files changed, 653 insertions, 169 deletions
diff --git a/src/etc-cleanup/Makefile b/src/etc-cleanup/Makefile
new file mode 100644
index 000000000..349da8821
--- /dev/null
+++ b/src/etc-cleanup/Makefile
@@ -0,0 +1,9 @@
1ROOT = ../..
2-include $(ROOT)/config.mk
3
4PROG = etc-cleanup
5TARGET = $(PROG)
6
7MOD_HDRS = ../include/etc-groups.h
8
9include $(ROOT)/src/prog.mk
diff --git a/src/etc-cleanup/main.c b/src/etc-cleanup/main.c
new file mode 100644
index 000000000..47fe1556b
--- /dev/null
+++ b/src/etc-cleanup/main.c
@@ -0,0 +1,255 @@
1/*
2 * Copyright (C) 2014-2022 Firejail Authors
3 *
4 * This file is part of firejail project
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/
20
21#include "../include/etc_groups.h"
22#include "../include/common.h"
23#include <stdarg.h>
24
25#define MAX_BUF 4098
26#define MAX_ARR 1024
27char *arr[MAX_ARR] = {NULL};
28int arr_cnt = 0;
29
30static int arr_tls_ca = 0;
31static int arr_x11 = 0;
32static int arr_games = 0;
33static char outbuf[256 * 1024];
34static char *outptr;
35static int arg_replace = 0;
36static int arg_debug = 0;
37
38void outprintf(char* fmt, ...) {
39 va_list args;
40 va_start(args,fmt);
41 outptr += vsprintf(outptr, fmt, args);
42 va_end(args);
43}
44
45
46
47static int arr_check(const char *fname, char **pptr) {
48 assert(fname);
49 assert(pptr);
50
51 while (*pptr != NULL) {
52 if (strcmp(fname, *pptr) == 0)
53 return 1;
54 pptr++;
55 }
56
57 return 0;
58}
59
60
61
62static void arr_add(const char *fname) {
63 assert(fname);
64 assert(arr_cnt < MAX_ARR);
65
66 int i;
67 for (i = 0; i < arr_cnt; i++)
68 if (strcmp(arr[i], fname) == 0)
69 return;
70
71 arr[arr_cnt] = strdup(fname);
72 if (!arr[arr_cnt])
73 errExit("strdup");
74 arr_cnt++;
75}
76
77int arr_cmp(const void *p1, const void *p2) {
78 char **ptr1 = (char **) p1;
79 char **ptr2 = (char **) p2;
80
81 return strcmp(*ptr1, *ptr2);
82}
83
84static void arr_sort(void) {
85 qsort(&arr[0], arr_cnt, sizeof(char *), arr_cmp);
86}
87
88static void arr_clean(void) {
89 int i;
90 for (i = 0; i < arr_cnt; i++) {
91 free(arr[i]);
92 arr[i] = NULL;
93 }
94
95 arr_cnt = 0;
96 arr_games = 0;
97 arr_tls_ca = 0;
98 arr_x11 = 0;
99}
100
101static char *arr_print(void) {
102 char *last_line = outptr;
103 outprintf("private-etc ");
104
105 if (arr_games)
106 outprintf("@games,");
107 if (arr_tls_ca)
108 outprintf("@tls-ca,");
109 if (arr_x11)
110 outprintf("@x11,");
111
112 int i;
113 for (i = 0; i < arr_cnt; i++)
114 outprintf("%s,", arr[i]);
115 if (*(outptr - 1) == ' ' || *(outptr - 1) == ',') {
116 outptr--;
117 *outptr = '\0';
118 }
119 outprintf("\n");
120
121 return last_line;
122}
123
124static void process_file(const char *fname) {
125 assert(fname);
126
127 FILE *fp = fopen(fname, "r");
128 if (!fp) {
129 fprintf(stderr, "Error: cannot open %s file\n", fname);
130 exit(1);
131 }
132
133 outptr = outbuf;
134 *outptr = '\0';
135 arr_clean();
136
137 char line[MAX_BUF];
138 char orig_line[MAX_BUF];
139 int cnt = 0;
140 int print = 0;
141 while (fgets(line, MAX_BUF, fp)) {
142 cnt++;
143 if (strncmp(line, "private-etc", 11) != 0) {
144 outprintf("%s", line);
145 continue;
146 }
147
148 strcpy(orig_line,line);
149 char *ptr = strchr(line, '\n');
150 if (ptr)
151 *ptr = '\0';
152
153 ptr = line + 12;
154 while (*ptr == ' ' || *ptr == '\t')
155 ptr++;
156
157 // check for blanks and tabs
158 char *ptr2 = ptr;
159 while (*ptr2 != '\0') {
160 if (*ptr2 == ' ' || *ptr2 == '\t') {
161 fprintf(stderr, "Error: invalid private-etc line %s:%d\n", fname, cnt);
162 exit(1);
163 }
164 ptr2++;
165 }
166
167 ptr = strtok(ptr, ",");
168 while (ptr) {
169 if (arg_debug)
170 printf("%s\n", ptr);
171 if (arr_check(ptr, &etc_list[0]));
172 else if (arr_check(ptr, &etc_group_sound[0]));
173 else if (arr_check(ptr, &etc_group_network[0]));
174 else if (strcmp(ptr, "@games") == 0)
175 arr_games = 1;
176 else if (strcmp(ptr, "@tls-ca") == 0)
177 arr_tls_ca = 1;
178 else if (strcmp(ptr, "@x11") == 0)
179 arr_x11 = 1;
180 else if (arr_check(ptr, &etc_group_games[0]))
181 arr_games = 1;
182 else if (arr_check(ptr, &etc_group_tls_ca[0]))
183 arr_tls_ca = 1;
184 else if (arr_check(ptr, &etc_group_x11[0]))
185 arr_x11 = 1;
186 else
187 arr_add(ptr);
188
189 ptr = strtok(NULL, ",");
190 }
191
192 arr_sort();
193 char *last_line = arr_print();
194 if (strcmp(last_line, orig_line) == 0) {
195 fclose(fp);
196 return;
197 }
198 printf("\n********************\nfile: %s\n\nold: %s\nnew: %s\n", fname, orig_line, last_line);
199 print = 1;
200 }
201
202 fclose(fp);
203
204 if (print && arg_replace) {
205 fp = fopen(fname, "w");
206 if (!fp) {
207 fprintf(stderr, "Error: cannot open profile file\n");
208 exit(1);
209 }
210 fprintf(fp, "%s", outbuf);
211 fclose(fp);
212 }
213}
214
215static void usage(void) {
216 printf("usage: cleanup-etc [options] file.profile [file.profile]\n");
217 printf("Group and clean private-etc entries in one or more profile files.\n");
218 printf("Options:\n");
219 printf(" --debug - print debug messages\n");
220 printf(" -h, -?, --help - this help screen\n");
221 printf(" --replace - replace profile file\n");
222}
223
224int main(int argc, char **argv) {
225 if (argc < 2) {
226 fprintf(stderr, "Error: invalid number of parameters\n");
227 usage();
228 return 1;
229 }
230
231 int i;
232 for (i = 1; i < argc; i++) {
233 if (strcmp(argv[i], "-h") == 0 ||
234 strcmp(argv[i], "-?") == 0 ||
235 strcmp(argv[i], "--help") == 0) {
236 usage();
237 return 0;
238 }
239 else if (strcmp(argv[i], "--debug") == 0)
240 arg_debug = 1;
241 else if (strcmp(argv[i], "--replace") == 0)
242 arg_replace = 1;
243 else if (*argv[i] == '-') {
244 fprintf(stderr, "Error: invalid program option %s\n", argv[i]);
245 return 1;
246 }
247 else
248 break;
249 }
250
251 for (; i < argc; i++)
252 process_file(argv[i]);
253
254 return 0;
255} \ No newline at end of file
diff --git a/src/firecfg/firecfg.config b/src/firecfg/firecfg.config
index 15169f983..db73dd1f6 100644
--- a/src/firecfg/firecfg.config
+++ b/src/firecfg/firecfg.config
@@ -479,6 +479,7 @@ lincity-ng
479links 479links
480links2 480links2
481linphone 481linphone
482linuxqq
482lmms 483lmms
483lobase 484lobase
484localc 485localc
@@ -518,6 +519,7 @@ matrix-mirage
518mattermost-desktop 519mattermost-desktop
519mcabber 520mcabber
520mcomix 521mcomix
522md5sum
521mediainfo 523mediainfo
522mediathekview 524mediathekview
523megaglest 525megaglest
@@ -693,6 +695,7 @@ qlipper
693qmmp 695qmmp
694qnapi 696qnapi
695qpdfview 697qpdfview
698qq
696qt-faststart 699qt-faststart
697qtox 700qtox
698quadrapassel 701quadrapassel
@@ -734,6 +737,11 @@ seahorse-tool
734seamonkey 737seamonkey
735seamonkey-bin 738seamonkey-bin
736secret-tool 739secret-tool
740sha1sum
741sha224sum
742sha256sum
743sha348sum
744sha512sum
737shellcheck 745shellcheck
738shortwave 746shortwave
739shotcut 747shotcut
@@ -773,6 +781,7 @@ straw-viewer
773strings 781strings
774studio.sh 782studio.sh
775subdownloader 783subdownloader
784sum
776supertux2 785supertux2
777supertuxkart 786supertuxkart
778surf 787surf
diff --git a/src/firejail/Makefile b/src/firejail/Makefile
index 4e241af7e..47edc5ac6 100644
--- a/src/firejail/Makefile
+++ b/src/firejail/Makefile
@@ -13,7 +13,9 @@ MOD_HDRS = \
13../include/seccomp.h \ 13../include/seccomp.h \
14../include/syscall_i386.h \ 14../include/syscall_i386.h \
15../include/syscall_x86_64.h \ 15../include/syscall_x86_64.h \
16../include/firejail_user.h 16../include/firejail_user.h \
17../include/etc_groups.h
18
17 19
18MOD_OBJS = \ 20MOD_OBJS = \
19../lib/common.o \ 21../lib/common.o \
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index 4fe3a5974..a09158e9e 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -332,6 +332,7 @@ extern int arg_nice; // nice value configured
332extern int arg_ipc; // enable ipc namespace 332extern int arg_ipc; // enable ipc namespace
333extern int arg_writable_etc; // writable etc 333extern int arg_writable_etc; // writable etc
334extern int arg_keep_config_pulse; // disable automatic ~/.config/pulse init 334extern int arg_keep_config_pulse; // disable automatic ~/.config/pulse init
335extern int arg_keep_shell_rc; // do not copy shell configuration from /etc/skel
335extern int arg_writable_var; // writable var 336extern int arg_writable_var; // writable var
336extern int arg_keep_var_tmp; // don't overwrite /var/tmp 337extern int arg_keep_var_tmp; // don't overwrite /var/tmp
337extern int arg_writable_run_user; // writable /run/user 338extern int arg_writable_run_user; // writable /run/user
@@ -693,11 +694,12 @@ void bandwidth_pid(pid_t pid, const char *command, const char *dev, int down, in
693void network_set_run_file(pid_t pid); 694void network_set_run_file(pid_t pid);
694 695
695// fs_etc.c 696// fs_etc.c
697char *fs_etc_build(char *str);
698void fs_resolvconf(void);
696void fs_machineid(void); 699void fs_machineid(void);
697void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list); 700void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list);
698void fs_private_dir_mount(const char *private_dir, const char *private_run_dir); 701void fs_private_dir_mount(const char *private_dir, const char *private_run_dir);
699void fs_private_dir_list(const char *private_dir, const char *private_run_dir, const char *private_list); 702void fs_private_dir_list(const char *private_dir, const char *private_run_dir, const char *private_list);
700void fs_rebuild_etc(void);
701 703
702// no_sandbox.c 704// no_sandbox.c
703int check_namespace_virt(void); 705int check_namespace_virt(void);
diff --git a/src/firejail/fs.c b/src/firejail/fs.c
index b44eb65ee..89a67f686 100644
--- a/src/firejail/fs.c
+++ b/src/firejail/fs.c
@@ -67,11 +67,8 @@ static void disable_file(OPERATION op, const char *filename) {
67 // they don't seem to like a uid of 0 67 // they don't seem to like a uid of 0
68 // force mounting 68 // force mounting
69 int fd = open(filename, O_PATH|O_CLOEXEC); 69 int fd = open(filename, O_PATH|O_CLOEXEC);
70 if (fd < 0) { 70 if (fd < 0)
71 if (arg_debug)
72 printf("Warning (blacklisting): cannot open %s: %s\n", filename, strerror(errno));
73 return; 71 return;
74 }
75 72
76 EUID_ROOT(); 73 EUID_ROOT();
77 int err = bind_mount_path_to_fd(RUN_RO_DIR, fd); 74 int err = bind_mount_path_to_fd(RUN_RO_DIR, fd);
diff --git a/src/firejail/fs_etc.c b/src/firejail/fs_etc.c
index 77fa00d6b..83f140d80 100644
--- a/src/firejail/fs_etc.c
+++ b/src/firejail/fs_etc.c
@@ -24,7 +24,126 @@
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#include "../include/etc_groups.h"
29
30static int etc_cnt = 0;
31
32static void etc_copy_group(char **pptr) {
33 assert(pptr);
34
35 while (*pptr != NULL) {
36 etc_list[etc_cnt++] = *pptr;
37 etc_list[etc_cnt] = NULL;
38 pptr++;
39 }
40}
41
42static void etc_add(const char *file) {
43 assert(file);
44 if (etc_cnt >= ETC_MAX) {
45 fprintf(stderr, "Error: size of private_etc list exceeded (%d maximum)\n", ETC_MAX);
46 exit(1);
47 }
48
49 // look for file in the current list
50 int i;
51 for (i = 0; i < etc_cnt; i++) {
52 if (strcmp(file, etc_list[i]) == 0) {
53 if (arg_debug)
54 printf("private-etc arguments: skip %s\n", file);
55 return;
56 }
57 }
58
59 char *ptr = strdup(file);
60 if (!ptr)
61 errExit("strdup");
62 etc_list[etc_cnt++] = ptr;
63 etc_list[etc_cnt] = NULL;
64}
65
66// str can be NULL
67char *fs_etc_build(char *str) {
68 while (etc_list[etc_cnt++]);
69 etc_cnt--;
70 if (!arg_nonetwork)
71 etc_copy_group(&etc_group_network[0]);
72 if (!arg_nosound)
73 etc_copy_group(&etc_group_sound[0]);
74
75 // parsing
76 if (str) {
77 char* ptr = strtok(str, ",");
78 while (ptr) {
79 // look for standard groups
80 if (strcmp(ptr, "@tls-ca") == 0)
81 etc_copy_group(&etc_group_tls_ca[0]);
82 if (strcmp(ptr, "@x11") == 0)
83 etc_copy_group(&etc_group_x11[0]);
84 if (strcmp(ptr, "@sound") == 0)
85 etc_copy_group(&etc_group_sound[0]);
86 if (strcmp(ptr, "@network") == 0)
87 etc_copy_group(&etc_group_network[0]);
88 if (strcmp(ptr, "@games") == 0)
89 etc_copy_group(&etc_group_games[0]);
90 else
91 etc_add(ptr);
92 ptr = strtok(NULL, ",");
93 }
94 }
95
96 // manufacture the new string
97 int len = 0;
98 int i;
99 for (i = 0; i < etc_cnt; i++)
100 len += strlen(etc_list[i]) + 1; // plus 1 for the trailing ','
101 char *rv = malloc(len + 1);
102 if (!rv)
103 errExit("malloc");
104 char *ptr = rv;
105 for (i = 0; i < etc_cnt; i++) {
106 sprintf(ptr, "%s,", etc_list[i]);
107 ptr += strlen(etc_list[i]) + 1;
108 }
109
110 return rv;
111}
112
113void fs_resolvconf(void) {
114 if (arg_debug)
115 printf("Creating a new /etc/resolv.conf file\n");
116 FILE *fp = fopen(RUN_RESOLVCONF_FILE, "wxe");
117 if (!fp) {
118 fprintf(stderr, "Error: cannot create /etc/resolv.conf file\n");
119 exit(1);
120 }
121
122 if (cfg.dns1) {
123 if (any_dhcp())
124 fwarning("network setup uses DHCP, nameservers will likely be overwritten\n");
125 fprintf(fp, "nameserver %s\n", cfg.dns1);
126 }
127 if (cfg.dns2)
128 fprintf(fp, "nameserver %s\n", cfg.dns2);
129 if (cfg.dns3)
130 fprintf(fp, "nameserver %s\n", cfg.dns3);
131 if (cfg.dns4)
132 fprintf(fp, "nameserver %s\n", cfg.dns4);
133
134 // mode and owner
135 SET_PERMS_STREAM(fp, 0, 0, 0644);
136
137 fclose(fp);
138 selinux_relabel_path(RUN_RESOLVCONF_FILE, "/etc/resolv.conf");
139
140
141 if (mount(RUN_RESOLVCONF_FILE, "/etc/resolv.conf", "none", MS_BIND, "mode=644,gid=0") < 0)
142 errExit("mount");
143
144 fs_logger("create /etc/resolv.conf");
145}
146
28 147
29// spoof /etc/machine_id 148// spoof /etc/machine_id
30void fs_machineid(void) { 149void fs_machineid(void) {
@@ -143,19 +262,11 @@ errexit:
143} 262}
144 263
145static void duplicate(const char *fname, const char *private_dir, const char *private_run_dir) { 264static void duplicate(const char *fname, const char *private_dir, const char *private_run_dir) {
146 assert(fname);
147
148 if (*fname == '~' || *fname == '/' || strstr(fname, "..")) {
149 fprintf(stderr, "Error: \"%s\" is an invalid filename\n", fname);
150 exit(1);
151 }
152 invalid_filename(fname, 0); // no globbing
153
154 char *src; 265 char *src;
155 if (asprintf(&src, "%s/%s", private_dir, fname) == -1) 266 if (asprintf(&src, "%s/%s", private_dir, fname) == -1)
156 errExit("asprintf"); 267 errExit("asprintf");
268
157 if (check_dir_or_file(src) == 0) { 269 if (check_dir_or_file(src) == 0) {
158 fwarning("skipping %s for private %s\n", fname, private_dir);
159 free(src); 270 free(src);
160 return; 271 return;
161 } 272 }
@@ -169,8 +280,9 @@ static void duplicate(const char *fname, const char *private_dir, const char *pr
169 280
170 build_dirs(src, dst, strlen(private_dir), strlen(private_run_dir)); 281 build_dirs(src, dst, strlen(private_dir), strlen(private_run_dir));
171 282
172 // follow links! this will make a copy of the file or directory pointed by the symlink 283 // follow links by default, thus making a copy of the file or directory pointed by the symlink
173 // this will solve problems such as NixOS #4887 284 // this will solve problems such as NixOS #4887
285 //
174 // don't follow links to dynamic directories such as /proc 286 // don't follow links to dynamic directories such as /proc
175 if (strcmp(src, "/etc/mtab") == 0) 287 if (strcmp(src, "/etc/mtab") == 0)
176 sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FCOPY, src, dst); 288 sbox_run(SBOX_ROOT | SBOX_SECCOMP, 3, PATH_FCOPY, src, dst);
@@ -179,9 +291,38 @@ static void duplicate(const char *fname, const char *private_dir, const char *pr
179 291
180 free(dst); 292 free(dst);
181 fs_logger2("clone", src); 293 fs_logger2("clone", src);
182 free(src);
183} 294}
184 295
296static void duplicate_globbing(const char *fname, const char *private_dir, const char *private_run_dir) {
297 assert(fname);
298
299 if (*fname == '~' || *fname == '/' || strstr(fname, "..")) {
300 fprintf(stderr, "Error: \"%s\" is an invalid filename\n", fname);
301 exit(1);
302 }
303 invalid_filename(fname, 1); // no globbing
304
305 char *pattern;
306 if (asprintf(&pattern, "%s/%s", private_dir, fname) == -1)
307 errExit("asprintf");
308
309 glob_t globbuf;
310 int globerr = glob(pattern, GLOB_NOCHECK | GLOB_NOSORT | GLOB_PERIOD, NULL, &globbuf);
311 if (globerr) {
312 fprintf(stderr, "Error: failed to glob pattern %s\n", pattern);
313 exit(1);
314 }
315
316 size_t i;
317 int len = strlen(private_dir);
318 for (i = 0; i < globbuf.gl_pathc; i++) {
319 char *path = globbuf.gl_pathv[i];
320 duplicate(path + len + 1, private_dir, private_run_dir);
321 }
322
323 globfree(&globbuf);
324 free(pattern);
325}
185 326
186void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list) { 327void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, const char *private_list) {
187 assert(private_dir); 328 assert(private_dir);
@@ -221,10 +362,10 @@ void fs_private_dir_copy(const char *private_dir, const char *private_run_dir, c
221 fprintf(stderr, "Error: invalid private %s argument\n", private_dir); 362 fprintf(stderr, "Error: invalid private %s argument\n", private_dir);
222 exit(1); 363 exit(1);
223 } 364 }
224 duplicate(ptr, private_dir, private_run_dir); 365 duplicate_globbing(ptr, private_dir, private_run_dir);
225 366
226 while ((ptr = strtok(NULL, ",")) != NULL) 367 while ((ptr = strtok(NULL, ",")) != NULL)
227 duplicate(ptr, private_dir, private_run_dir); 368 duplicate_globbing(ptr, private_dir, private_run_dir);
228 free(dlist); 369 free(dlist);
229 fs_logger_print(); 370 fs_logger_print();
230 } 371 }
@@ -262,127 +403,3 @@ void fs_private_dir_list(const char *private_dir, const char *private_run_dir, c
262 fmessage("Private %s installed in %0.2f ms\n", private_dir, timetrace_end()); 403 fmessage("Private %s installed in %0.2f ms\n", private_dir, timetrace_end());
263} 404}
264 405
265void fs_rebuild_etc(void) {
266 int have_dhcp = 1;
267 if (cfg.dns1 == NULL && !any_dhcp())
268 have_dhcp = 0;
269
270 if (arg_debug)
271 printf("rebuilding /etc directory\n");
272 if (mkdir(RUN_DNS_ETC, 0755))
273 errExit("mkdir");
274 selinux_relabel_path(RUN_DNS_ETC, "/etc");
275 fs_logger("tmpfs /etc");
276
277 DIR *dir = opendir("/etc");
278 if (!dir)
279 errExit("opendir");
280
281 struct stat s;
282 struct dirent *entry;
283 while ((entry = readdir(dir))) {
284 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
285 continue;
286
287 // skip files in cfg.profile_rebuild_etc list
288 // these files are already blacklisted
289 {
290 ProfileEntry *prf = cfg.profile_rebuild_etc;
291 int found = 0;
292 while (prf) {
293 if (strcmp(entry->d_name, prf->data + 5) == 0) { // 5 is strlen("/etc/")
294 found = 1;
295 break;
296 }
297 prf = prf->next;
298 }
299 if (found)
300 continue;
301 }
302
303 // for resolv.conf we might have to create a brand new file later
304 if (have_dhcp &&
305 (strcmp(entry->d_name, "resolv.conf") == 0 ||
306 strcmp(entry->d_name, "resolv.conf.dhclient-new") == 0))
307 continue;
308// printf("linking %s\n", entry->d_name);
309
310 char *src;
311 if (asprintf(&src, "/etc/%s", entry->d_name) == -1)
312 errExit("asprintf");
313 if (stat(src, &s) != 0) {
314 free(src);
315 continue;
316 }
317
318 char *dest;
319 if (asprintf(&dest, "%s/%s", RUN_DNS_ETC, entry->d_name) == -1)
320 errExit("asprintf");
321
322 int symlink_done = 0;
323 if (is_link(src)) {
324 char *rp =realpath(src, NULL);
325 if (rp == NULL) {
326 free(src);
327 free(dest);
328 continue;
329 }
330 if (symlink(rp, dest))
331 errExit("symlink");
332 else
333 symlink_done = 1;
334 }
335 else if (S_ISDIR(s.st_mode))
336 create_empty_dir_as_root(dest, S_IRWXU);
337 else
338 create_empty_file_as_root(dest, S_IRUSR | S_IWUSR);
339
340 // bind-mount src on top of dest
341 if (!symlink_done) {
342 if (mount(src, dest, NULL, MS_BIND|MS_REC, NULL) < 0)
343 errExit("mount bind mirroring /etc");
344 }
345 fs_logger2("clone", src);
346
347 free(src);
348 free(dest);
349 }
350 closedir(dir);
351
352 // mount bind our private etc directory on top of /etc
353 if (arg_debug)
354 printf("Mount-bind %s on top of /etc\n", RUN_DNS_ETC);
355 if (mount(RUN_DNS_ETC, "/etc", NULL, MS_BIND|MS_REC, NULL) < 0)
356 errExit("mount bind mirroring /etc");
357 fs_logger("mount /etc");
358
359 if (have_dhcp == 0)
360 return;
361
362 if (arg_debug)
363 printf("Creating a new /etc/resolv.conf file\n");
364 FILE *fp = fopen("/etc/resolv.conf", "wxe");
365 if (!fp) {
366 fprintf(stderr, "Error: cannot create /etc/resolv.conf file\n");
367 exit(1);
368 }
369
370 if (cfg.dns1) {
371 if (any_dhcp())
372 fwarning("network setup uses DHCP, nameservers will likely be overwritten\n");
373 fprintf(fp, "nameserver %s\n", cfg.dns1);
374 }
375 if (cfg.dns2)
376 fprintf(fp, "nameserver %s\n", cfg.dns2);
377 if (cfg.dns3)
378 fprintf(fp, "nameserver %s\n", cfg.dns3);
379 if (cfg.dns4)
380 fprintf(fp, "nameserver %s\n", cfg.dns4);
381
382 // mode and owner
383 SET_PERMS_STREAM(fp, 0, 0, 0644);
384
385 fclose(fp);
386
387 fs_logger("create /etc/resolv.conf");
388}
diff --git a/src/firejail/fs_home.c b/src/firejail/fs_home.c
index 8c4cb3d4f..8e72f8687 100644
--- a/src/firejail/fs_home.c
+++ b/src/firejail/fs_home.c
@@ -361,7 +361,8 @@ void fs_private_homedir(void) {
361 } 361 }
362 EUID_USER(); 362 EUID_USER();
363 363
364 skel(homedir); 364 if (!arg_keep_shell_rc)
365 skel(homedir);
365 if (xflag) 366 if (xflag)
366 copy_xauthority(); 367 copy_xauthority();
367 if (aflag) 368 if (aflag)
@@ -430,7 +431,8 @@ void fs_private(void) {
430 selinux_relabel_path(homedir, homedir); 431 selinux_relabel_path(homedir, homedir);
431 } 432 }
432 433
433 skel(homedir); 434 if (!arg_keep_shell_rc)
435 skel(homedir);
434 if (xflag) 436 if (xflag)
435 copy_xauthority(); 437 copy_xauthority();
436 if (aflag) 438 if (aflag)
@@ -682,7 +684,8 @@ void fs_private_home_list(void) {
682 errExit("mounting tmpfs"); 684 errExit("mounting tmpfs");
683 EUID_USER(); 685 EUID_USER();
684 686
685 skel(homedir); 687 if (!arg_keep_shell_rc)
688 skel(homedir);
686 if (xflag) 689 if (xflag)
687 copy_xauthority(); 690 copy_xauthority();
688 if (aflag) 691 if (aflag)
diff --git a/src/firejail/main.c b/src/firejail/main.c
index 18e9ae651..8df6926ee 100644
--- a/src/firejail/main.c
+++ b/src/firejail/main.c
@@ -127,6 +127,7 @@ int arg_nice = 0; // nice value configured
127int arg_ipc = 0; // enable ipc namespace 127int arg_ipc = 0; // enable ipc namespace
128int arg_writable_etc = 0; // writable etc 128int arg_writable_etc = 0; // writable etc
129int arg_keep_config_pulse = 0; // disable automatic ~/.config/pulse init 129int arg_keep_config_pulse = 0; // disable automatic ~/.config/pulse init
130int arg_keep_shell_rc = 0; // do not copy shell configuration from /etc/skel
130int arg_writable_var = 0; // writable var 131int arg_writable_var = 0; // writable var
131int arg_keep_var_tmp = 0; // don't overwrite /var/tmp 132int arg_keep_var_tmp = 0; // don't overwrite /var/tmp
132int arg_writable_run_user = 0; // writable /run/user 133int arg_writable_run_user = 0; // writable /run/user
@@ -1975,6 +1976,9 @@ int main(int argc, char **argv, char **envp) {
1975 else if (strcmp(argv[i], "--keep-config-pulse") == 0) { 1976 else if (strcmp(argv[i], "--keep-config-pulse") == 0) {
1976 arg_keep_config_pulse = 1; 1977 arg_keep_config_pulse = 1;
1977 } 1978 }
1979 else if (strcmp(argv[i], "--keep-shell-rc") == 0) {
1980 arg_keep_shell_rc = 1;
1981 }
1978 else if (strcmp(argv[i], "--writable-var") == 0) { 1982 else if (strcmp(argv[i], "--writable-var") == 0) {
1979 arg_writable_var = 1; 1983 arg_writable_var = 1;
1980 } 1984 }
@@ -2044,6 +2048,17 @@ int main(int argc, char **argv, char **envp) {
2044 else if (strcmp(argv[i], "--keep-dev-shm") == 0) { 2048 else if (strcmp(argv[i], "--keep-dev-shm") == 0) {
2045 arg_keep_dev_shm = 1; 2049 arg_keep_dev_shm = 1;
2046 } 2050 }
2051 else if (strcmp(argv[i], "--private-etc") == 0) {
2052 if (checkcfg(CFG_PRIVATE_ETC)) {
2053 if (arg_writable_etc) {
2054 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
2055 exit(1);
2056 }
2057 arg_private_etc = 1;
2058 }
2059 else
2060 exit_err_feature("private-etc");
2061 }
2047 else if (strncmp(argv[i], "--private-etc=", 14) == 0) { 2062 else if (strncmp(argv[i], "--private-etc=", 14) == 0) {
2048 if (checkcfg(CFG_PRIVATE_ETC)) { 2063 if (checkcfg(CFG_PRIVATE_ETC)) {
2049 if (arg_writable_etc) { 2064 if (arg_writable_etc) {
@@ -2161,11 +2176,24 @@ int main(int argc, char **argv, char **envp) {
2161 // hostname, etc 2176 // hostname, etc
2162 //************************************* 2177 //*************************************
2163 else if (strncmp(argv[i], "--name=", 7) == 0) { 2178 else if (strncmp(argv[i], "--name=", 7) == 0) {
2179 int only_numbers = 1;
2164 cfg.name = argv[i] + 7; 2180 cfg.name = argv[i] + 7;
2165 if (strlen(cfg.name) == 0) { 2181 if (strlen(cfg.name) == 0) {
2166 fprintf(stderr, "Error: please provide a name for sandbox\n"); 2182 fprintf(stderr, "Error: please provide a name for sandbox\n");
2167 return 1; 2183 return 1;
2168 } 2184 }
2185 const char *c = cfg.name;
2186 while (*c) {
2187 if (!isdigit(*c)) {
2188 only_numbers = 0;
2189 break;
2190 }
2191 ++c;
2192 }
2193 if (only_numbers) {
2194 fprintf(stderr, "Error: invalid sandbox name: it only contains digits\n");
2195 return 1;
2196 }
2169 } 2197 }
2170 else if (strncmp(argv[i], "--hostname=", 11) == 0) { 2198 else if (strncmp(argv[i], "--hostname=", 11) == 0) {
2171 cfg.hostname = argv[i] + 11; 2199 cfg.hostname = argv[i] + 11;
diff --git a/src/firejail/profile.c b/src/firejail/profile.c
index acf206da6..3924465e4 100644
--- a/src/firejail/profile.c
+++ b/src/firejail/profile.c
@@ -326,11 +326,24 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
326 } 326 }
327 // sandbox name 327 // sandbox name
328 else if (strncmp(ptr, "name ", 5) == 0) { 328 else if (strncmp(ptr, "name ", 5) == 0) {
329 int only_numbers = 1;
329 cfg.name = ptr + 5; 330 cfg.name = ptr + 5;
330 if (strlen(cfg.name) == 0) { 331 if (strlen(cfg.name) == 0) {
331 fprintf(stderr, "Error: invalid sandbox name\n"); 332 fprintf(stderr, "Error: invalid sandbox name\n");
332 exit(1); 333 exit(1);
333 } 334 }
335 const char *c = cfg.name;
336 while (*c) {
337 if (!isdigit(*c)) {
338 only_numbers = 0;
339 break;
340 }
341 ++c;
342 }
343 if (only_numbers) {
344 fprintf(stderr, "Error: invalid sandbox name: it only contains digits\n");
345 exit(1);
346 }
334 return 0; 347 return 0;
335 } 348 }
336 else if (strcmp(ptr, "ipc-namespace") == 0) { 349 else if (strcmp(ptr, "ipc-namespace") == 0) {
@@ -1222,6 +1235,11 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
1222 return 0; 1235 return 0;
1223 } 1236 }
1224 1237
1238 if (strcmp(ptr, "keep-shell-rc") == 0) {
1239 arg_keep_shell_rc = 1;
1240 return 0;
1241 }
1242
1225 // writable-var 1243 // writable-var
1226 if (strcmp(ptr, "writable-var") == 0) { 1244 if (strcmp(ptr, "writable-var") == 0) {
1227 arg_writable_var = 1; 1245 arg_writable_var = 1;
@@ -1366,6 +1384,20 @@ int profile_check_line(char *ptr, int lineno, const char *fname) {
1366 return 0; 1384 return 0;
1367 } 1385 }
1368 1386
1387 // private /etc without a list of files and directories
1388 if (strcmp(ptr, "private-etc") == 0) {
1389 if (checkcfg(CFG_PRIVATE_ETC)) {
1390 if (arg_writable_etc) {
1391 fprintf(stderr, "Error: --private-etc and --writable-etc are mutually exclusive\n");
1392 exit(1);
1393 }
1394 arg_private_etc = 1;
1395 }
1396 else
1397 warning_feature_disabled("private-etc");
1398 return 0;
1399 }
1400
1369 // private /opt list of files and directories 1401 // private /opt list of files and directories
1370 if (strncmp(ptr, "private-opt ", 12) == 0) { 1402 if (strncmp(ptr, "private-opt ", 12) == 0) {
1371 if (checkcfg(CFG_PRIVATE_OPT)) { 1403 if (checkcfg(CFG_PRIVATE_OPT)) {
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c
index 77fe73174..ec3bc250e 100644
--- a/src/firejail/sandbox.c
+++ b/src/firejail/sandbox.c
@@ -580,8 +580,6 @@ void start_application(int no_sandbox, int fd, char *set_sandbox_status) {
580 if (arg_debug) 580 if (arg_debug)
581 printf("Running %s command through %s\n", cfg.command_line, cfg.usershell); 581 printf("Running %s command through %s\n", cfg.command_line, cfg.usershell);
582 arg[index++] = "-c"; 582 arg[index++] = "-c";
583 if (arg_doubledash)
584 arg[index++] = "--";
585 arg[index++] = cfg.command_line; 583 arg[index++] = cfg.command_line;
586 } 584 }
587 else if (login_shell) { 585 else if (login_shell) {
@@ -1032,6 +1030,7 @@ int sandbox(void* sandbox_arg) {
1032 * 3. mount RUN_ETC_DIR at /etc 1030 * 3. mount RUN_ETC_DIR at /etc
1033 */ 1031 */
1034 timetrace_start(); 1032 timetrace_start();
1033 cfg.etc_private_keep = fs_etc_build(cfg.etc_private_keep);
1035 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);
1036 1035
1037 if (umount2("/etc/group", MNT_DETACH) == -1) 1036 if (umount2("/etc/group", MNT_DETACH) == -1)
@@ -1048,7 +1047,8 @@ int sandbox(void* sandbox_arg) {
1048 1047
1049 // openSUSE configuration is split between /etc and /usr/etc 1048 // openSUSE configuration is split between /etc and /usr/etc
1050 // process private-etc a second time 1049 // process private-etc a second time
1051 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);
1052 } 1052 }
1053 } 1053 }
1054 1054
@@ -1098,10 +1098,10 @@ int sandbox(void* sandbox_arg) {
1098 fs_dev_disable_input(); 1098 fs_dev_disable_input();
1099 1099
1100 //**************************** 1100 //****************************
1101 // rebuild etc directory, set dns 1101 // set DNS
1102 //**************************** 1102 //****************************
1103 if (!arg_writable_etc) 1103 if (cfg.dns1 != NULL || any_dhcp())
1104 fs_rebuild_etc(); 1104 fs_resolvconf();
1105 1105
1106 //**************************** 1106 //****************************
1107 // start dhcp client 1107 // start dhcp client
diff --git a/src/firejail/usage.c b/src/firejail/usage.c
index 0a4c8a483..e31293c66 100644
--- a/src/firejail/usage.c
+++ b/src/firejail/usage.c
@@ -30,10 +30,12 @@ static char *usage_str =
30 " -- - signal the end of options and disables further option processing.\n" 30 " -- - signal the end of options and disables further option processing.\n"
31 " --allow-debuggers - allow tools such as strace and gdb inside the sandbox.\n" 31 " --allow-debuggers - allow tools such as strace and gdb inside the sandbox.\n"
32 " --allusers - all user home directories are visible inside the sandbox.\n" 32 " --allusers - all user home directories are visible inside the sandbox.\n"
33#ifdef HAVE_APPARMOR
33 " --apparmor - enable AppArmor confinement with the default profile.\n" 34 " --apparmor - enable AppArmor confinement with the default profile.\n"
34 " --apparmor=profile_name - enable AppArmor confinement with a\n" 35 " --apparmor=profile_name - enable AppArmor confinement with a\n"
35 "\tcustom profile.\n" 36 "\tcustom profile.\n"
36 " --apparmor.print=name|pid - print apparmor status.\n" 37 " --apparmor.print=name|pid - print apparmor status.\n"
38#endif
37 " --appimage - sandbox an AppImage application.\n" 39 " --appimage - sandbox an AppImage application.\n"
38#ifdef HAVE_NETWORK 40#ifdef HAVE_NETWORK
39 " --bandwidth=name|pid - set bandwidth limits.\n" 41 " --bandwidth=name|pid - set bandwidth limits.\n"
@@ -127,6 +129,7 @@ static char *usage_str =
127 " --keep-config-pulse - disable automatic ~/.config/pulse init.\n" 129 " --keep-config-pulse - disable automatic ~/.config/pulse init.\n"
128 " --keep-dev-shm - /dev/shm directory is untouched (even with --private-dev).\n" 130 " --keep-dev-shm - /dev/shm directory is untouched (even with --private-dev).\n"
129 " --keep-fd - inherit open file descriptors to sandbox.\n" 131 " --keep-fd - inherit open file descriptors to sandbox.\n"
132 " --keep-shell-rc - do not copy shell rc files from /etc/skel\n"
130 " --keep-var-tmp - /var/tmp directory is untouched.\n" 133 " --keep-var-tmp - /var/tmp directory is untouched.\n"
131 " --list - list all sandboxes.\n" 134 " --list - list all sandboxes.\n"
132#ifdef HAVE_FILE_TRANSFER 135#ifdef HAVE_FILE_TRANSFER
diff --git a/src/firejail/util.c b/src/firejail/util.c
index a01290cf2..b35225620 100644
--- a/src/firejail/util.c
+++ b/src/firejail/util.c
@@ -255,7 +255,6 @@ static void clean_supplementary_groups(gid_t gid) {
255 return; 255 return;
256 256
257clean_all: 257clean_all:
258 fwarning("cleaning all supplementary groups\n");
259 if (setgroups(0, NULL) < 0) 258 if (setgroups(0, NULL) < 0)
260 errExit("setgroups"); 259 errExit("setgroups");
261} 260}
diff --git a/src/include/etc_groups.h b/src/include/etc_groups.h
new file mode 100644
index 000000000..dca767934
--- /dev/null
+++ b/src/include/etc_groups.h
@@ -0,0 +1,103 @@
1/*
2 * Copyright (C) 2014-2022 Firejail Authors
3 *
4 * This file is part of firejail project
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19*/
20
21#ifndef ETC_GROUPS_H
22#define ETC_GROUPS_H
23#include <stddef.h>
24
25#define ETC_MAX 256
26
27// @default
28static char *etc_list[ETC_MAX + 1] = { // plus 1 for ending NULL pointer
29 "alternatives",
30 "fonts",
31 "group",
32 "ld.so.cache",
33 "ld.so.conf",
34 "ld.so.conf.d",
35 "ld.so.preload",
36 "locale",
37 "locale.alias",
38 "locale.conf",
39 "localtime",
40 "login.defs", // firejail reading UID/GID MIN and MAX at startup
41 "nsswitch.conf",
42 "passwd",
43 "selinux",
44 NULL
45};
46
47// @games
48static char *etc_group_games[] = {
49 "openal", // 3D sound
50 "timidity", // MIDI
51 "timidity.cfg",
52 "vulkan", // next generation OpenGL stack
53 NULL
54};
55
56// @network
57static char*etc_group_network[] = {
58 "hostname",
59 "hosts",
60 "protocols",
61 "resolv.conf",
62 NULL
63};
64
65// @sound
66static char *etc_group_sound[] = {
67 "alsa",
68 "asound.conf",
69 "machine-id", // required by PulseAudio
70 "pulse",
71 NULL
72};
73
74// @tls-ca
75static char *etc_group_tls_ca[] = {
76 "ca-certificates",
77 "crypto-policies",
78 "gcrypt", // GNU crypto library - contains hardware config for various encryption schemes
79 // and random number generators. The file is not installed by Debian.
80 "pki",
81 "ssl",
82 NULL
83};
84
85// @x11
86static char *etc_group_x11[] = {
87 "ati", // 3D
88 "dconf",
89 "drirc",
90 "gtk-2.0",
91 "gtk-3.0",
92 "kde4rc",
93 "kde5rc",
94 "machine-id", // QT dbus lib is crashing without it!
95 "nvidia", // 3D
96 "pango", // text rendering/internationalization
97 "Trolltech.conf", // old QT config file
98 "X11",
99 "xdg",
100 NULL
101};
102
103#endif
diff --git a/src/include/rundefs.h b/src/include/rundefs.h
index 079670f10..b3ad564ac 100644
--- a/src/include/rundefs.h
+++ b/src/include/rundefs.h
@@ -99,5 +99,7 @@
99#define RUN_UMASK_FILE RUN_MNT_DIR "/umask" 99#define RUN_UMASK_FILE RUN_MNT_DIR "/umask"
100#define RUN_JOIN_FILE RUN_MNT_DIR "/join" 100#define RUN_JOIN_FILE RUN_MNT_DIR "/join"
101#define RUN_OVERLAY_ROOT RUN_MNT_DIR "/oroot" 101#define RUN_OVERLAY_ROOT RUN_MNT_DIR "/oroot"
102#define RUN_RESOLVCONF_FILE RUN_MNT_DIR "/resolv.conf"
103
102 104
103#endif 105#endif
diff --git a/src/man/firejail-profile.txt b/src/man/firejail-profile.txt
index 5b16179ac..3fa07d1ee 100644
--- a/src/man/firejail-profile.txt
+++ b/src/man/firejail-profile.txt
@@ -288,6 +288,9 @@ pulse servers or non-standard socket paths.
288\fBkeep-dev-shm 288\fBkeep-dev-shm
289/dev/shm directory is untouched (even with private-dev). 289/dev/shm directory is untouched (even with private-dev).
290.TP 290.TP
291\fBkeep-shell-rc
292Do not copy shell rc files (such as ~/.bashrc and ~/.zshrc) from /etc/skel.
293.TP
291\fBkeep-var-tmp 294\fBkeep-var-tmp
292/var/tmp directory is untouched. 295/var/tmp directory is untouched.
293.TP 296.TP
diff --git a/src/man/firejail.txt b/src/man/firejail.txt
index e5020e37e..6068c9ff4 100644
--- a/src/man/firejail.txt
+++ b/src/man/firejail.txt
@@ -1224,6 +1224,14 @@ Example:
1224$ firejail --keep-fd=3,4,5 1224$ firejail --keep-fd=3,4,5
1225 1225
1226.TP 1226.TP
1227\fB\-\-keep-shell-rc
1228By default, when using a private home directory, firejail copies files from the
1229system's user home template (/etc/skel) into it, which overrides attempts to
1230whitelist the original files (such as ~/.bashrc and ~/.zshrc).
1231This option disables this feature, and enables the user to whitelist the
1232original files.
1233
1234.TP
1227\fB\-\-keep-var-tmp 1235\fB\-\-keep-var-tmp
1228/var/tmp directory is untouched. 1236/var/tmp directory is untouched.
1229.br 1237.br
@@ -1330,6 +1338,7 @@ $ firejail \-\-net=eth0 \-\-mtu=1492
1330\fB\-\-name=name 1338\fB\-\-name=name
1331Set sandbox name. Several options, such as \-\-join and \-\-shutdown, can use 1339Set sandbox name. Several options, such as \-\-join and \-\-shutdown, can use
1332this name to identify a sandbox. 1340this name to identify a sandbox.
1341The name cannot contain only digits, as that is treated as a PID in the other options, such as in \-\-join.
1333 1342
1334In case the name supplied by the user is already in use by another sandbox, Firejail will assign a 1343In case the name supplied by the user is already in use by another sandbox, Firejail will assign a
1335new name as "name-PID", where PID is the process ID of the sandbox. This functionality 1344new name as "name-PID", where PID is the process ID of the sandbox. This functionality
@@ -2127,22 +2136,32 @@ cdrom cdrw dri dvd dvdrw full log null ptmx pts random shm snd sr0
2127.br 2136.br
2128$ 2137$
2129.TP 2138.TP
2130\fB\-\-private-etc=file,directory 2139\fB\-\-private-etc, \-\-private-etc=file,directory,@group
2131Build a new /etc in a temporary 2140The files installed by \-\-private-etc are copies of the original system files from /etc directory.
2132filesystem, and copy the files and directories in the list. 2141By default, the command brings in a skeleton of files and directories used by most console tools:
2133The files and directories in the list must be expressed as relative to
2134the /etc directory (e.g., /etc/foo must be expressed as foo).
2135If no listed file is found, /etc directory will be empty.
2136All modifications are discarded when the sandbox is closed.
2137Multiple private-etc commands are allowed and they accumulate.
2138.br
2139 2142
2140.br 2143$ firejail --private-etc dig debian.org
2141Example: 2144
2142.br 2145For X11/GTK/QT/Gnome/KDE programs add @x11 group as a parameter. Example:
2143$ firejail --private-etc=group,hostname,localtime, \\ 2146
2144.br 2147$ firejail --private-etc=@x11,gcrypt,python* gimp
2145nsswitch.conf,passwd,resolv.conf 2148
2149gcrypt and /etc/python* directories are not part of the generic @x11 group.
2150File globbing is supported.
2151
2152For games, add @games group:
2153
2154$ firejail --private-etc=@games,@x11 warzone2100
2155
2156Sound and networking files are included automatically, unless \-\-nosound or \-\-net=none are specified.
2157Files for encrypted TLS/SSL protocol are in @tls-ca group.
2158
2159$ firejail --private-etc=@tls-ca,wgetrc wget https://debian.org
2160
2161
2162Note: The easiest way to extract the list of /etc files accessed by your program is using strace utility:
2163
2164$ strace /usr/bin/transmission-qt 2>&1 | grep open | grep etc
2146#ifdef HAVE_PRIVATE_HOME 2165#ifdef HAVE_PRIVATE_HOME
2147.TP 2166.TP
2148\fB\-\-private-home=file,directory 2167\fB\-\-private-home=file,directory
diff --git a/src/zsh_completion/_firejail.in b/src/zsh_completion/_firejail.in
index 2b67c2a00..37ce7055b 100644
--- a/src/zsh_completion/_firejail.in
+++ b/src/zsh_completion/_firejail.in
@@ -104,6 +104,7 @@ _firejail_args=(
104 '--keep-config-pulse[disable automatic ~/.config/pulse init]' 104 '--keep-config-pulse[disable automatic ~/.config/pulse init]'
105 '--keep-dev-shm[/dev/shm directory is untouched (even with --private-dev)]' 105 '--keep-dev-shm[/dev/shm directory is untouched (even with --private-dev)]'
106 '--keep-fd[inherit open file descriptors to sandbox]: :' 106 '--keep-fd[inherit open file descriptors to sandbox]: :'
107 '--keep-shell-rc[do not copy shell rc files from /etc/skel]'
107 '--keep-var-tmp[/var/tmp directory is untouched]' 108 '--keep-var-tmp[/var/tmp directory is untouched]'
108 '--machine-id[spoof /etc/machine-id with a random id]' 109 '--machine-id[spoof /etc/machine-id with a random id]'
109 '--memory-deny-write-execute[seccomp filter to block attempts to create memory mappings that are both writable and executable]' 110 '--memory-deny-write-execute[seccomp filter to block attempts to create memory mappings that are both writable and executable]'