diff options
author | 2015-11-19 10:00:57 -0500 | |
---|---|---|
committer | 2015-11-19 10:00:57 -0500 | |
commit | 4f003daec3ed3acd4dfbe64138d0a627210c8bdf (patch) | |
tree | 09356109fc420c7abc8f2d448fcbe6e1286d061e | |
parent | admin (diff) | |
download | firejail-4f003daec.tar.gz firejail-4f003daec.tar.zst firejail-4f003daec.zip |
prevent leaking user information by modifying /home directory, /etc/passwd and /etc/group
-rw-r--r-- | RELNOTES | 4 | ||||
-rw-r--r-- | src/firejail/firejail.h | 7 | ||||
-rw-r--r-- | src/firejail/fs.c | 59 | ||||
-rw-r--r-- | src/firejail/restrict_users.c | 328 |
4 files changed, 344 insertions, 54 deletions
@@ -1,7 +1,9 @@ | |||
1 | firejail (0.9.34) baseline; urgency=low | 1 | firejail (0.9.35) baseline; urgency=low |
2 | * added unbound and dnscrypt-proxy profiles | 2 | * added unbound and dnscrypt-proxy profiles |
3 | * added --noblacklist option | 3 | * added --noblacklist option |
4 | * whitelist command enhancements | 4 | * whitelist command enhancements |
5 | * prevent leaking user information by modifying /home directory, | ||
6 | /etc/passwd and /etc/group | ||
5 | * bugfixes | 7 | * bugfixes |
6 | -- netblue30 <netblue30@yahoo.com> ongoing development | 8 | -- netblue30 <netblue30@yahoo.com> ongoing development |
7 | 9 | ||
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index b29e11923..25a39d0c4 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h | |||
@@ -52,6 +52,8 @@ | |||
52 | #define RESOLVCONF_FILE "/run/firejail/mnt/resolv.conf" | 52 | #define RESOLVCONF_FILE "/run/firejail/mnt/resolv.conf" |
53 | #define LDPRELOAD_FILE "/run/firejail/mnt/ld.so.preload" | 53 | #define LDPRELOAD_FILE "/run/firejail/mnt/ld.so.preload" |
54 | #define UTMP_FILE "/run/firejail/mnt/utmp" | 54 | #define UTMP_FILE "/run/firejail/mnt/utmp" |
55 | #define PASSWD_FILE "/run/firejail/mnt/passwd" | ||
56 | #define GROUP_FILE "/run/firejail/mnt/group" | ||
55 | 57 | ||
56 | // profiles | 58 | // profiles |
57 | #define DEFAULT_USER_PROFILE "generic" | 59 | #define DEFAULT_USER_PROFILE "generic" |
@@ -468,5 +470,10 @@ void protocol_store(const char *prlist); | |||
468 | void protocol_filter(void); | 470 | void protocol_filter(void); |
469 | void protocol_filter_save(void); | 471 | void protocol_filter_save(void); |
470 | void protocol_filter_load(const char *fname); | 472 | void protocol_filter_load(const char *fname); |
473 | |||
474 | // restrict_users.c | ||
475 | void restrict_users(void); | ||
476 | |||
477 | |||
471 | #endif | 478 | #endif |
472 | 479 | ||
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index aec1698b0..ad84b2222 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -539,49 +539,6 @@ void fs_proc_sys_dev_boot(void) { | |||
539 | } | 539 | } |
540 | } | 540 | } |
541 | 541 | ||
542 | static void sanitize_home(void) { | ||
543 | assert(getuid() != 0); // this code works only for regular users | ||
544 | |||
545 | if (arg_debug) | ||
546 | printf("Cleaning /home directory\n"); | ||
547 | |||
548 | struct stat s; | ||
549 | if (stat(cfg.homedir, &s) == -1) { | ||
550 | // cannot find home directory, just return | ||
551 | fprintf(stderr, "Warning: cannot find home directory\n"); | ||
552 | return; | ||
553 | } | ||
554 | |||
555 | fs_build_mnt_dir(); | ||
556 | if (mkdir(WHITELIST_HOME_DIR, 0755) == -1) | ||
557 | errExit("mkdir"); | ||
558 | |||
559 | // keep a copy of the user home directory | ||
560 | if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
561 | errExit("mount bind"); | ||
562 | |||
563 | // mount tmpfs in the new home | ||
564 | if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
565 | errExit("mount tmpfs"); | ||
566 | |||
567 | // create user home directory | ||
568 | if (mkdir(cfg.homedir, 0755) == -1) | ||
569 | errExit("mkdir"); | ||
570 | |||
571 | // set mode and ownership | ||
572 | if (chown(cfg.homedir, s.st_uid, s.st_gid) == -1) | ||
573 | errExit("chown"); | ||
574 | if (chmod(cfg.homedir, s.st_mode) == -1) | ||
575 | errExit("chmod"); | ||
576 | |||
577 | // mount user home directory | ||
578 | if (mount(WHITELIST_HOME_DIR, cfg.homedir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
579 | errExit("mount bind"); | ||
580 | |||
581 | // mask home dir under /run | ||
582 | if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
583 | errExit("mount tmpfs"); | ||
584 | } | ||
585 | 542 | ||
586 | // build a basic read-only filesystem | 543 | // build a basic read-only filesystem |
587 | void fs_basic_fs(void) { | 544 | void fs_basic_fs(void) { |
@@ -605,9 +562,8 @@ void fs_basic_fs(void) { | |||
605 | fs_var_cache(); | 562 | fs_var_cache(); |
606 | fs_var_utmp(); | 563 | fs_var_utmp(); |
607 | 564 | ||
608 | // only in user mode | 565 | // don't leak user information |
609 | if (getuid()) | 566 | restrict_users(); |
610 | sanitize_home(); | ||
611 | } | 567 | } |
612 | 568 | ||
613 | 569 | ||
@@ -751,9 +707,8 @@ void fs_overlayfs(void) { | |||
751 | fs_var_cache(); | 707 | fs_var_cache(); |
752 | fs_var_utmp(); | 708 | fs_var_utmp(); |
753 | 709 | ||
754 | // only in user mode | 710 | // don't leak user information |
755 | if (getuid()) | 711 | restrict_users(); |
756 | sanitize_home(); | ||
757 | 712 | ||
758 | // cleanup and exit | 713 | // cleanup and exit |
759 | free(option); | 714 | free(option); |
@@ -874,10 +829,8 @@ void fs_chroot(const char *rootdir) { | |||
874 | fs_var_cache(); | 829 | fs_var_cache(); |
875 | fs_var_utmp(); | 830 | fs_var_utmp(); |
876 | 831 | ||
877 | // only in user mode | 832 | // don't leak user information |
878 | if (getuid()) | 833 | restrict_users(); |
879 | sanitize_home(); | ||
880 | |||
881 | } | 834 | } |
882 | #endif | 835 | #endif |
883 | 836 | ||
diff --git a/src/firejail/restrict_users.c b/src/firejail/restrict_users.c new file mode 100644 index 000000000..c0a14ff6f --- /dev/null +++ b/src/firejail/restrict_users.c | |||
@@ -0,0 +1,328 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014, 2015 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 | #include "firejail.h" | ||
21 | #include <sys/mount.h> | ||
22 | #include <sys/stat.h> | ||
23 | #include <linux/limits.h> | ||
24 | #include <fnmatch.h> | ||
25 | #include <glob.h> | ||
26 | #include <dirent.h> | ||
27 | #include <fcntl.h> | ||
28 | #include <errno.h> | ||
29 | |||
30 | #define MAXBUF 1024 | ||
31 | |||
32 | // linked list of users | ||
33 | typedef struct user_list { | ||
34 | struct user_list *next; | ||
35 | const char *user; | ||
36 | } USER_LIST; | ||
37 | USER_LIST *ulist = NULL; | ||
38 | |||
39 | static void ulist_add(const char *user) { | ||
40 | assert(user); | ||
41 | |||
42 | USER_LIST *nlist = malloc(sizeof(USER_LIST)); | ||
43 | memset(nlist, 0, sizeof(USER_LIST)); | ||
44 | nlist->user = user; | ||
45 | nlist->next = ulist; | ||
46 | ulist = nlist; | ||
47 | } | ||
48 | |||
49 | static USER_LIST *ulist_find(const char *user) { | ||
50 | assert(user); | ||
51 | |||
52 | USER_LIST *ptr = ulist; | ||
53 | while (ptr) { | ||
54 | if (strcmp(ptr->user, user) == 0) | ||
55 | return ptr; | ||
56 | ptr = ptr->next; | ||
57 | } | ||
58 | |||
59 | return NULL; | ||
60 | } | ||
61 | |||
62 | static void sanitize_home(void) { | ||
63 | assert(getuid() != 0); // this code works only for regular users | ||
64 | |||
65 | if (arg_debug) | ||
66 | printf("Cleaning /home directory\n"); | ||
67 | |||
68 | struct stat s; | ||
69 | if (stat(cfg.homedir, &s) == -1) { | ||
70 | // cannot find home directory, just return | ||
71 | fprintf(stderr, "Warning: cannot find home directory\n"); | ||
72 | return; | ||
73 | } | ||
74 | |||
75 | fs_build_mnt_dir(); | ||
76 | if (mkdir(WHITELIST_HOME_DIR, 0755) == -1) | ||
77 | errExit("mkdir"); | ||
78 | |||
79 | // keep a copy of the user home directory | ||
80 | if (mount(cfg.homedir, WHITELIST_HOME_DIR, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
81 | errExit("mount bind"); | ||
82 | |||
83 | // mount tmpfs in the new home | ||
84 | if (mount("tmpfs", "/home", "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
85 | errExit("mount tmpfs"); | ||
86 | |||
87 | // create user home directory | ||
88 | if (mkdir(cfg.homedir, 0755) == -1) | ||
89 | errExit("mkdir"); | ||
90 | |||
91 | // set mode and ownership | ||
92 | if (chown(cfg.homedir, s.st_uid, s.st_gid) == -1) | ||
93 | errExit("chown"); | ||
94 | if (chmod(cfg.homedir, s.st_mode) == -1) | ||
95 | errExit("chmod"); | ||
96 | |||
97 | // mount user home directory | ||
98 | if (mount(WHITELIST_HOME_DIR, cfg.homedir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
99 | errExit("mount bind"); | ||
100 | |||
101 | // mask home dir under /run | ||
102 | if (mount("tmpfs", WHITELIST_HOME_DIR, "tmpfs", MS_NOSUID | MS_NODEV | MS_STRICTATIME | MS_REC, "mode=755,gid=0") < 0) | ||
103 | errExit("mount tmpfs"); | ||
104 | } | ||
105 | |||
106 | static void sanitize_passwd(void) { | ||
107 | struct stat s; | ||
108 | if (stat("/etc/passwd", &s) == -1) | ||
109 | return; | ||
110 | if (arg_debug) | ||
111 | printf("Sanitizing /etc/passwd\n"); | ||
112 | |||
113 | FILE *fpin = NULL; | ||
114 | FILE *fpout = NULL; | ||
115 | fs_build_mnt_dir(); | ||
116 | |||
117 | // open files | ||
118 | fpin = fopen("/etc/passwd", "r"); | ||
119 | if (!fpin) | ||
120 | goto errout; | ||
121 | fpout = fopen(PASSWD_FILE, "w"); | ||
122 | if (!fpout) | ||
123 | goto errout; | ||
124 | |||
125 | // read the file line by line | ||
126 | char buf[MAXBUF]; | ||
127 | uid_t myuid = getuid(); | ||
128 | while (fgets(buf, MAXBUF, fpin)) { | ||
129 | // comments and empty lines | ||
130 | if (*buf == '\0' || *buf == '#') | ||
131 | continue; | ||
132 | |||
133 | // sample line: | ||
134 | // www-data:x:33:33:www-data:/var/www:/bin/sh | ||
135 | // drop lines with uid > 1000 and not the current user | ||
136 | char *ptr = buf; | ||
137 | |||
138 | // advance to uid | ||
139 | while (*ptr != ':' && *ptr != '\0') | ||
140 | ptr++; | ||
141 | if (*ptr == '\0') | ||
142 | goto errout; | ||
143 | char *ptr1 = ptr; | ||
144 | ptr++; | ||
145 | while (*ptr != ':' && *ptr != '\0') | ||
146 | ptr++; | ||
147 | if (*ptr == '\0') | ||
148 | goto errout; | ||
149 | ptr++; | ||
150 | if (*ptr == '\0') | ||
151 | goto errout; | ||
152 | |||
153 | // process uid | ||
154 | int uid; | ||
155 | int rv = sscanf(ptr, "%d:", &uid); | ||
156 | if (rv == 0 || uid < 0) | ||
157 | goto errout; | ||
158 | if (uid < 1000) { // todo extract UID_MIN from /etc/login.def | ||
159 | fprintf(fpout, "%s", buf); | ||
160 | continue; | ||
161 | } | ||
162 | if ((uid_t) uid != myuid) { | ||
163 | // store user name - necessary to process /etc/group | ||
164 | *ptr1 = '\0'; | ||
165 | char *user = strdup(buf); | ||
166 | if (!user) | ||
167 | errExit("malloc"); | ||
168 | ulist_add(user); | ||
169 | continue; // skip line | ||
170 | } | ||
171 | fprintf(fpout, "%s", buf); | ||
172 | } | ||
173 | fclose(fpin); | ||
174 | fclose(fpout); | ||
175 | if (chown(PASSWD_FILE, 0, 0) == -1) | ||
176 | errExit("chown"); | ||
177 | if (chmod(PASSWD_FILE, 0644) == -1) | ||
178 | errExit("chmod"); | ||
179 | |||
180 | // mount-bind tne new password file | ||
181 | if (mount(PASSWD_FILE, "/etc/passwd", "none", MS_BIND, "mode=400,gid=0") < 0) | ||
182 | errExit("mount"); | ||
183 | |||
184 | return; | ||
185 | |||
186 | errout: | ||
187 | fprintf(stderr, "Warning: failed to clean up /etc/passwd\n"); | ||
188 | if (fpin) | ||
189 | fclose(fpin); | ||
190 | if (fpout) | ||
191 | fclose(fpout); | ||
192 | } | ||
193 | |||
194 | // returns 1 if fails, 0 if OK | ||
195 | static int copy_line(FILE *fpout, char *buf, char *ptr) { | ||
196 | // fpout: GROUP_FILE | ||
197 | // buf: pulse:x:115:netblue,bingo | ||
198 | // ptr: 115:neblue,bingo | ||
199 | |||
200 | while (*ptr != ':' && *ptr != '\0') | ||
201 | ptr++; | ||
202 | if (*ptr == '\0') | ||
203 | return 1; | ||
204 | |||
205 | ptr++; | ||
206 | if (*ptr == '\n' || *ptr == '\0') { | ||
207 | fprintf(fpout, "%s", buf); | ||
208 | return 0; | ||
209 | } | ||
210 | |||
211 | // print what we have so far | ||
212 | char tmp = *ptr; | ||
213 | *ptr = '\0'; | ||
214 | fprintf(fpout, "%s", buf); | ||
215 | *ptr = tmp; | ||
216 | |||
217 | // tokenize | ||
218 | char *token = strtok(ptr, ",\n"); | ||
219 | int first = 1; | ||
220 | while (token) { | ||
221 | char *newtoken = strtok(NULL, ",\n"); | ||
222 | if (ulist_find(token)) { | ||
223 | //skip | ||
224 | token = newtoken; | ||
225 | continue; | ||
226 | } | ||
227 | if (!first) | ||
228 | fprintf(fpout, ","); | ||
229 | first = 0; | ||
230 | fprintf(fpout, "%s", token); | ||
231 | token = newtoken; | ||
232 | } | ||
233 | fprintf(fpout, "\n"); | ||
234 | return 0; | ||
235 | } | ||
236 | |||
237 | static void sanitize_group(void) { | ||
238 | struct stat s; | ||
239 | if (stat("/etc/group", &s) == -1) | ||
240 | return; | ||
241 | if (arg_debug) | ||
242 | printf("Sanitizing /etc/group\n"); | ||
243 | |||
244 | FILE *fpin = NULL; | ||
245 | FILE *fpout = NULL; | ||
246 | fs_build_mnt_dir(); | ||
247 | |||
248 | // open files | ||
249 | fpin = fopen("/etc/group", "r"); | ||
250 | if (!fpin) | ||
251 | goto errout; | ||
252 | fpout = fopen(GROUP_FILE, "w"); | ||
253 | if (!fpout) | ||
254 | goto errout; | ||
255 | |||
256 | // read the file line by line | ||
257 | char buf[MAXBUF]; | ||
258 | gid_t mygid = getgid(); | ||
259 | while (fgets(buf, MAXBUF, fpin)) { | ||
260 | // comments and empty lines | ||
261 | if (*buf == '\0' || *buf == '#') | ||
262 | continue; | ||
263 | |||
264 | // sample line: | ||
265 | // pulse:x:115:netblue,bingo | ||
266 | // drop lines with uid > 1000 and not the current user group | ||
267 | char *ptr = buf; | ||
268 | |||
269 | // advance to uid | ||
270 | while (*ptr != ':' && *ptr != '\0') | ||
271 | ptr++; | ||
272 | if (*ptr == '\0') | ||
273 | goto errout; | ||
274 | ptr++; | ||
275 | while (*ptr != ':' && *ptr != '\0') | ||
276 | ptr++; | ||
277 | if (*ptr == '\0') | ||
278 | goto errout; | ||
279 | ptr++; | ||
280 | if (*ptr == '\0') | ||
281 | goto errout; | ||
282 | |||
283 | // process uid | ||
284 | int gid; | ||
285 | int rv = sscanf(ptr, "%d:", &gid); | ||
286 | if (rv == 0 || gid < 0) | ||
287 | goto errout; | ||
288 | if (gid < 1000) { // todo extract GID_MIN from /etc/login.def | ||
289 | if (copy_line(fpout, buf, ptr)) | ||
290 | goto errout; | ||
291 | continue; | ||
292 | } | ||
293 | if ((gid_t) gid != mygid) { | ||
294 | continue; // skip line | ||
295 | } | ||
296 | fprintf(fpout, "%s", buf); | ||
297 | if (copy_line(fpout, buf, ptr)) | ||
298 | goto errout; | ||
299 | } | ||
300 | fclose(fpin); | ||
301 | fclose(fpout); | ||
302 | if (chown(GROUP_FILE, 0, 0) == -1) | ||
303 | errExit("chown"); | ||
304 | if (chmod(GROUP_FILE, 0644) == -1) | ||
305 | errExit("chmod"); | ||
306 | |||
307 | // mount-bind tne new group file | ||
308 | if (mount(GROUP_FILE, "/etc/group", "none", MS_BIND, "mode=400,gid=0") < 0) | ||
309 | errExit("mount"); | ||
310 | |||
311 | return; | ||
312 | |||
313 | errout: | ||
314 | fprintf(stderr, "Warning: failed to clean up /etc/group\n"); | ||
315 | if (fpin) | ||
316 | fclose(fpin); | ||
317 | if (fpout) | ||
318 | fclose(fpout); | ||
319 | } | ||
320 | |||
321 | void restrict_users(void) { | ||
322 | // only in user mode | ||
323 | if (getuid()) { | ||
324 | sanitize_home(); | ||
325 | sanitize_passwd(); | ||
326 | sanitize_group(); | ||
327 | } | ||
328 | } | ||