diff options
author | smitsohu <smitsohu@gmail.com> | 2019-09-29 17:58:27 +0200 |
---|---|---|
committer | smitsohu <smitsohu@gmail.com> | 2019-09-29 17:58:27 +0200 |
commit | 94aba62229f083173cb28ffe370eaa9f3ee540b9 (patch) | |
tree | b1a70e5ff502df77bf0ddc45e82f11547677f09a /src | |
parent | move chroot from path based to file descriptor based mounts (diff) | |
download | firejail-94aba62229f083173cb28ffe370eaa9f3ee540b9.tar.gz firejail-94aba62229f083173cb28ffe370eaa9f3ee540b9.tar.zst firejail-94aba62229f083173cb28ffe370eaa9f3ee540b9.zip |
chroot module
Diffstat (limited to 'src')
-rw-r--r-- | src/firejail/chroot.c | 309 | ||||
-rw-r--r-- | src/firejail/firejail.h | 10 | ||||
-rw-r--r-- | src/firejail/fs.c | 284 |
3 files changed, 319 insertions, 284 deletions
diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c new file mode 100644 index 000000000..c99af1d52 --- /dev/null +++ b/src/firejail/chroot.c | |||
@@ -0,0 +1,309 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014-2019 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 | #ifdef HAVE_CHROOT | ||
22 | #include "firejail.h" | ||
23 | #include <sys/mount.h> | ||
24 | #include <sys/stat.h> | ||
25 | #include <sys/sendfile.h> | ||
26 | #include <errno.h> | ||
27 | |||
28 | #include <fcntl.h> | ||
29 | #ifndef O_PATH | ||
30 | # define O_PATH 010000000 | ||
31 | #endif | ||
32 | |||
33 | |||
34 | // exit if error | ||
35 | static void fs_check_chroot_subdir(const char *subdir, int parentfd, int check_writable) { | ||
36 | assert(subdir); | ||
37 | int fd = openat(parentfd, subdir, O_PATH|O_CLOEXEC); | ||
38 | if (fd == -1) { | ||
39 | if (errno == ENOENT) | ||
40 | fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); | ||
41 | else { | ||
42 | perror("open"); | ||
43 | fprintf(stderr, "Error: cannot open /%s in chroot directory\n", subdir); | ||
44 | } | ||
45 | exit(1); | ||
46 | } | ||
47 | struct stat s; | ||
48 | if (fstat(fd, &s) == -1) | ||
49 | errExit("fstat"); | ||
50 | close(fd); | ||
51 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | ||
52 | fprintf(stderr, "Error: chroot /%s should be a directory owned by root\n", subdir); | ||
53 | exit(1); | ||
54 | } | ||
55 | if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
56 | fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); | ||
57 | exit(1); | ||
58 | } | ||
59 | } | ||
60 | |||
61 | // exit if error | ||
62 | void fs_check_chroot_dir(const char *rootdir) { | ||
63 | EUID_ASSERT(); | ||
64 | assert(rootdir); | ||
65 | |||
66 | char *overlay; | ||
67 | if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) | ||
68 | errExit("asprintf"); | ||
69 | if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { | ||
70 | fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); | ||
71 | exit(1); | ||
72 | } | ||
73 | free(overlay); | ||
74 | |||
75 | // fails if there is any symlink or if rootdir is not a directory | ||
76 | int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
77 | if (parentfd == -1) { | ||
78 | fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); | ||
79 | exit(1); | ||
80 | } | ||
81 | // rootdir has to be owned by root and is not allowed to be generally writable, | ||
82 | // this also excludes /tmp, /var/tmp and such | ||
83 | struct stat s; | ||
84 | if (fstat(parentfd, &s) == -1) | ||
85 | errExit("fstat"); | ||
86 | if (s.st_uid != 0) { | ||
87 | fprintf(stderr, "Error: chroot directory should be owned by root\n"); | ||
88 | exit(1); | ||
89 | } | ||
90 | if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
91 | fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); | ||
92 | exit(1); | ||
93 | } | ||
94 | |||
95 | // check subdirectories in rootdir | ||
96 | fs_check_chroot_subdir("dev", parentfd, 0); | ||
97 | fs_check_chroot_subdir("etc", parentfd, 1); | ||
98 | fs_check_chroot_subdir("proc", parentfd, 0); | ||
99 | fs_check_chroot_subdir("tmp", parentfd, 0); | ||
100 | fs_check_chroot_subdir("var/tmp", parentfd, 0); | ||
101 | |||
102 | // there should be no checking on <chrootdir>/etc/resolv.conf | ||
103 | // the file is replaced with the real /etc/resolv.conf anyway | ||
104 | #if 0 | ||
105 | if (asprintf(&name, "%s/etc/resolv.conf", rootdir) == -1) | ||
106 | errExit("asprintf"); | ||
107 | if (stat(name, &s) == 0) { | ||
108 | if (s.st_uid != 0) { | ||
109 | fprintf(stderr, "Error: chroot /etc/resolv.conf should be owned by root\n"); | ||
110 | exit(1); | ||
111 | } | ||
112 | } | ||
113 | else { | ||
114 | fprintf(stderr, "Error: chroot /etc/resolv.conf not found\n"); | ||
115 | exit(1); | ||
116 | } | ||
117 | // on Arch /etc/resolv.conf could be a symlink to /run/systemd/resolve/resolv.conf | ||
118 | // on Ubuntu 17.04 /etc/resolv.conf could be a symlink to /run/resolveconf/resolv.conf | ||
119 | if (is_link(name)) { | ||
120 | // check the link points in chroot | ||
121 | char *rname = realpath(name, NULL); | ||
122 | if (!rname || strncmp(rname, rootdir, strlen(rootdir)) != 0) { | ||
123 | fprintf(stderr, "Error: chroot /etc/resolv.conf is pointing outside chroot\n"); | ||
124 | exit(1); | ||
125 | } | ||
126 | } | ||
127 | free(name); | ||
128 | #endif | ||
129 | |||
130 | // check x11 socket directory | ||
131 | if (getenv("FIREJAIL_X11")) | ||
132 | fs_check_chroot_subdir("tmp/.X11-unix", parentfd, 0); | ||
133 | |||
134 | close(parentfd); | ||
135 | } | ||
136 | |||
137 | // copy /etc/resolv.conf in chroot directory | ||
138 | static void copy_resolvconf(int parentfd) { | ||
139 | int in = open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC); | ||
140 | if (in == -1) { | ||
141 | fwarning("/etc/resolv.conf not initialized\n"); | ||
142 | return; | ||
143 | } | ||
144 | struct stat instat; | ||
145 | if (fstat(in, &instat) == -1) | ||
146 | errExit("fstat"); | ||
147 | // try to detect if resolv.conf has been bind mounted into the chroot | ||
148 | // do nothing in this case in order to not truncate the real file | ||
149 | struct stat outstat; | ||
150 | if (fstatat(parentfd, "etc/resolv.conf", &outstat, 0) == 0) { | ||
151 | if (instat.st_dev == outstat.st_dev && instat.st_ino == outstat.st_ino) { | ||
152 | close(in); | ||
153 | return; | ||
154 | } | ||
155 | } | ||
156 | if (arg_debug) | ||
157 | printf("Updating /etc/resolv.conf in chroot\n"); | ||
158 | int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); | ||
159 | if (out == -1) | ||
160 | errExit("open"); | ||
161 | if (sendfile(out, in, NULL, instat.st_size) == -1) | ||
162 | errExit("sendfile"); | ||
163 | close(in); | ||
164 | close(out); | ||
165 | } | ||
166 | |||
167 | // chroot into an existing directory; mount existing /dev and update /etc/resolv.conf | ||
168 | void fs_chroot(const char *rootdir) { | ||
169 | assert(rootdir); | ||
170 | |||
171 | int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
172 | if (parentfd == -1) | ||
173 | errExit("safe_fd"); | ||
174 | |||
175 | // mount-bind a /dev in rootdir | ||
176 | if (arg_debug) | ||
177 | printf("Mounting /dev on chroot /dev\n"); | ||
178 | int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
179 | if (fd == -1) | ||
180 | errExit("open"); | ||
181 | char *proc; | ||
182 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
183 | errExit("asprintf"); | ||
184 | if (mount("/dev", proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
185 | errExit("mounting /dev"); | ||
186 | free(proc); | ||
187 | close(fd); | ||
188 | |||
189 | // mount a brand new proc filesystem | ||
190 | if (arg_debug) | ||
191 | printf("Mounting /proc filesystem on chroot /proc\n"); | ||
192 | fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
193 | if (fd == -1) | ||
194 | errExit("open"); | ||
195 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
196 | errExit("asprintf"); | ||
197 | if (mount("proc", proc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) | ||
198 | errExit("mounting /proc"); | ||
199 | free(proc); | ||
200 | close(fd); | ||
201 | |||
202 | // x11 | ||
203 | if (getenv("FIREJAIL_X11")) { | ||
204 | if (arg_debug) | ||
205 | printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); | ||
206 | fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
207 | if (fd == -1) | ||
208 | errExit("open"); | ||
209 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
210 | errExit("asprintf"); | ||
211 | if (mount("/tmp/.X11-unix", proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
212 | errExit("mounting /tmp/.X11-unix"); | ||
213 | free(proc); | ||
214 | close(fd); | ||
215 | } | ||
216 | |||
217 | // update chroot resolv.conf | ||
218 | copy_resolvconf(parentfd); | ||
219 | |||
220 | // some older distros don't have a /run directory, create one by default | ||
221 | struct stat s; | ||
222 | if (fstatat(parentfd, "run", &s, AT_SYMLINK_NOFOLLOW) == 0) { | ||
223 | if (S_ISLNK(s.st_mode)) { | ||
224 | fprintf(stderr, "Error: chroot /run is a symbolic link\n"); | ||
225 | exit(1); | ||
226 | } | ||
227 | } | ||
228 | else if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) | ||
229 | errExit("mkdir"); | ||
230 | fs_check_chroot_subdir("run", parentfd, 1); | ||
231 | |||
232 | // create /run/firejail directory in chroot | ||
233 | if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) | ||
234 | errExit("mkdir"); | ||
235 | |||
236 | // create /run/firejail/lib directory in chroot | ||
237 | if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) | ||
238 | errExit("mkdir"); | ||
239 | // mount lib directory into the chroot | ||
240 | fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
241 | if (fd == -1) | ||
242 | errExit("open"); | ||
243 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
244 | errExit("asprintf"); | ||
245 | if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
246 | errExit("mount bind"); | ||
247 | free(proc); | ||
248 | close(fd); | ||
249 | |||
250 | // create /run/firejail/mnt directory in chroot | ||
251 | if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) | ||
252 | errExit("mkdir"); | ||
253 | // mount the current mnt directory into the chroot | ||
254 | fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
255 | if (fd == -1) | ||
256 | errExit("open"); | ||
257 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
258 | errExit("asprintf"); | ||
259 | if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
260 | errExit("mount bind"); | ||
261 | free(proc); | ||
262 | close(fd); | ||
263 | |||
264 | #ifdef HAVE_GCOV | ||
265 | __gcov_flush(); | ||
266 | #endif | ||
267 | // create /run/firejail/mnt/oroot | ||
268 | char *oroot = RUN_OVERLAY_ROOT; | ||
269 | if (mkdir(oroot, 0755) == -1) | ||
270 | errExit("mkdir"); | ||
271 | // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay | ||
272 | if (asprintf(&proc, "/proc/self/fd/%d", parentfd) == -1) | ||
273 | errExit("asprintf"); | ||
274 | if (mount(proc, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
275 | errExit("mounting rootdir oroot"); | ||
276 | free(proc); | ||
277 | close(parentfd); | ||
278 | // chroot into the new directory | ||
279 | if (arg_debug) | ||
280 | printf("Chrooting into %s\n", rootdir); | ||
281 | if (chroot(oroot) < 0) | ||
282 | errExit("chroot"); | ||
283 | |||
284 | // create all other /run/firejail files and directories | ||
285 | preproc_build_firejail_dir(); | ||
286 | |||
287 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
288 | // if (!arg_private_dev) | ||
289 | // fs_dev_shm(); | ||
290 | fs_var_lock(); | ||
291 | if (!arg_keep_var_tmp) | ||
292 | fs_var_tmp(); | ||
293 | if (!arg_writable_var_log) | ||
294 | fs_var_log(); | ||
295 | |||
296 | fs_var_lib(); | ||
297 | fs_var_cache(); | ||
298 | fs_var_utmp(); | ||
299 | fs_machineid(); | ||
300 | |||
301 | // don't leak user information | ||
302 | restrict_users(); | ||
303 | |||
304 | // when starting as root, firejail config is not disabled; | ||
305 | if (getuid() != 0) | ||
306 | disable_config(); | ||
307 | } | ||
308 | |||
309 | #endif // HAVE_CHROOT | ||
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index a6377261f..80cf71caf 100644 --- a/src/firejail/firejail.h +++ b/src/firejail/firejail.h | |||
@@ -386,18 +386,22 @@ void fs_remount(const char *dir, OPERATION op, unsigned check_mnt); | |||
386 | void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt); | 386 | void fs_remount_rec(const char *dir, OPERATION op, unsigned check_mnt); |
387 | // mount /proc and /sys directories | 387 | // mount /proc and /sys directories |
388 | void fs_proc_sys_dev_boot(void); | 388 | void fs_proc_sys_dev_boot(void); |
389 | // blacklist firejail configuration and runtime directories | ||
390 | void disable_config(void); | ||
389 | // build a basic read-only filesystem | 391 | // build a basic read-only filesystem |
390 | void fs_basic_fs(void); | 392 | void fs_basic_fs(void); |
391 | // mount overlayfs on top of / directory | 393 | // mount overlayfs on top of / directory |
392 | char *fs_check_overlay_dir(const char *subdirname, int allow_reuse); | 394 | char *fs_check_overlay_dir(const char *subdirname, int allow_reuse); |
393 | void fs_overlayfs(void); | 395 | void fs_overlayfs(void); |
394 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf | ||
395 | void fs_chroot(const char *rootdir); | ||
396 | void fs_check_chroot_dir(const char *rootdir); | ||
397 | void fs_private_tmp(void); | 396 | void fs_private_tmp(void); |
398 | void fs_private_cache(void); | 397 | void fs_private_cache(void); |
399 | void fs_mnt(const int enforce); | 398 | void fs_mnt(const int enforce); |
400 | 399 | ||
400 | // chroot.c | ||
401 | // chroot into an existing directory; mount existing /dev and update /etc/resolv.conf | ||
402 | void fs_check_chroot_dir(const char *rootdir); | ||
403 | void fs_chroot(const char *rootdir); | ||
404 | |||
401 | // profile.c | 405 | // profile.c |
402 | // find and read the profile specified by name from dir directory | 406 | // find and read the profile specified by name from dir directory |
403 | int profile_find_firejail(const char *name, int add_ext); | 407 | int profile_find_firejail(const char *name, int add_ext); |
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 1c91d96d4..f2639f318 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -27,7 +27,7 @@ | |||
27 | #include <glob.h> | 27 | #include <glob.h> |
28 | #include <dirent.h> | 28 | #include <dirent.h> |
29 | #include <errno.h> | 29 | #include <errno.h> |
30 | #include <sys/sendfile.h> | 30 | |
31 | 31 | ||
32 | #include <fcntl.h> | 32 | #include <fcntl.h> |
33 | #ifndef O_PATH | 33 | #ifndef O_PATH |
@@ -697,8 +697,8 @@ void fs_proc_sys_dev_boot(void) { | |||
697 | } | 697 | } |
698 | } | 698 | } |
699 | 699 | ||
700 | // disable firejail configuration in /etc/firejail and in ~/.config/firejail | 700 | // disable firejail configuration in ~/.config/firejail |
701 | static void disable_config(void) { | 701 | void disable_config(void) { |
702 | struct stat s; | 702 | struct stat s; |
703 | 703 | ||
704 | char *fname; | 704 | char *fname; |
@@ -1124,284 +1124,6 @@ void fs_overlayfs(void) { | |||
1124 | } | 1124 | } |
1125 | #endif | 1125 | #endif |
1126 | 1126 | ||
1127 | |||
1128 | #ifdef HAVE_CHROOT | ||
1129 | // exit if error | ||
1130 | static void fs_check_chroot_subdir(const char *subdir, int parentfd, int check_writable) { | ||
1131 | assert(subdir); | ||
1132 | int fd = openat(parentfd, subdir, O_PATH|O_CLOEXEC); | ||
1133 | if (fd == -1) { | ||
1134 | if (errno == ENOENT) | ||
1135 | fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); | ||
1136 | else { | ||
1137 | perror("open"); | ||
1138 | fprintf(stderr, "Error: cannot open /%s in chroot directory\n", subdir); | ||
1139 | } | ||
1140 | exit(1); | ||
1141 | } | ||
1142 | struct stat s; | ||
1143 | if (fstat(fd, &s) == -1) | ||
1144 | errExit("fstat"); | ||
1145 | close(fd); | ||
1146 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | ||
1147 | fprintf(stderr, "Error: chroot /%s should be a directory owned by root\n", subdir); | ||
1148 | exit(1); | ||
1149 | } | ||
1150 | if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
1151 | fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); | ||
1152 | exit(1); | ||
1153 | } | ||
1154 | } | ||
1155 | |||
1156 | // exit if error | ||
1157 | void fs_check_chroot_dir(const char *rootdir) { | ||
1158 | EUID_ASSERT(); | ||
1159 | assert(rootdir); | ||
1160 | |||
1161 | char *overlay; | ||
1162 | if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) | ||
1163 | errExit("asprintf"); | ||
1164 | if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { | ||
1165 | fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); | ||
1166 | exit(1); | ||
1167 | } | ||
1168 | free(overlay); | ||
1169 | |||
1170 | // fails if there is any symlink or if rootdir is not a directory | ||
1171 | int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
1172 | if (parentfd == -1) { | ||
1173 | fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); | ||
1174 | exit(1); | ||
1175 | } | ||
1176 | // rootdir has to be owned by root and is not allowed to be generally writable, | ||
1177 | // this also excludes /tmp, /var/tmp and such | ||
1178 | struct stat s; | ||
1179 | if (fstat(parentfd, &s) == -1) | ||
1180 | errExit("fstat"); | ||
1181 | if (s.st_uid != 0) { | ||
1182 | fprintf(stderr, "Error: chroot directory should be owned by root\n"); | ||
1183 | exit(1); | ||
1184 | } | ||
1185 | if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
1186 | fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); | ||
1187 | exit(1); | ||
1188 | } | ||
1189 | |||
1190 | // check subdirectories in rootdir | ||
1191 | fs_check_chroot_subdir("dev", parentfd, 0); | ||
1192 | fs_check_chroot_subdir("etc", parentfd, 1); | ||
1193 | fs_check_chroot_subdir("proc", parentfd, 0); | ||
1194 | fs_check_chroot_subdir("tmp", parentfd, 0); | ||
1195 | fs_check_chroot_subdir("var/tmp", parentfd, 0); | ||
1196 | |||
1197 | // there should be no checking on <chrootdir>/etc/resolv.conf | ||
1198 | // the file is replaced with the real /etc/resolv.conf anyway | ||
1199 | #if 0 | ||
1200 | if (asprintf(&name, "%s/etc/resolv.conf", rootdir) == -1) | ||
1201 | errExit("asprintf"); | ||
1202 | if (stat(name, &s) == 0) { | ||
1203 | if (s.st_uid != 0) { | ||
1204 | fprintf(stderr, "Error: chroot /etc/resolv.conf should be owned by root\n"); | ||
1205 | exit(1); | ||
1206 | } | ||
1207 | } | ||
1208 | else { | ||
1209 | fprintf(stderr, "Error: chroot /etc/resolv.conf not found\n"); | ||
1210 | exit(1); | ||
1211 | } | ||
1212 | // on Arch /etc/resolv.conf could be a symlink to /run/systemd/resolve/resolv.conf | ||
1213 | // on Ubuntu 17.04 /etc/resolv.conf could be a symlink to /run/resolveconf/resolv.conf | ||
1214 | if (is_link(name)) { | ||
1215 | // check the link points in chroot | ||
1216 | char *rname = realpath(name, NULL); | ||
1217 | if (!rname || strncmp(rname, rootdir, strlen(rootdir)) != 0) { | ||
1218 | fprintf(stderr, "Error: chroot /etc/resolv.conf is pointing outside chroot\n"); | ||
1219 | exit(1); | ||
1220 | } | ||
1221 | } | ||
1222 | free(name); | ||
1223 | #endif | ||
1224 | |||
1225 | // check x11 socket directory | ||
1226 | if (getenv("FIREJAIL_X11")) | ||
1227 | fs_check_chroot_subdir("tmp/.X11-unix", parentfd, 0); | ||
1228 | |||
1229 | close(parentfd); | ||
1230 | } | ||
1231 | |||
1232 | // copy /etc/resolv.conf in chroot directory | ||
1233 | static void copy_resolvconf(int parentfd) { | ||
1234 | int in = open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC); | ||
1235 | if (in == -1) { | ||
1236 | fwarning("/etc/resolv.conf not initialized\n"); | ||
1237 | return; | ||
1238 | } | ||
1239 | struct stat instat; | ||
1240 | if (fstat(in, &instat) == -1) | ||
1241 | errExit("fstat"); | ||
1242 | // try to detect if resolv.conf has been bind mounted into the chroot | ||
1243 | // do nothing in this case in order to not truncate the real file | ||
1244 | struct stat outstat; | ||
1245 | if (fstatat(parentfd, "etc/resolv.conf", &outstat, 0) == 0) { | ||
1246 | if (instat.st_dev == outstat.st_dev && instat.st_ino == outstat.st_ino) { | ||
1247 | close(in); | ||
1248 | return; | ||
1249 | } | ||
1250 | } | ||
1251 | if (arg_debug) | ||
1252 | printf("Updating /etc/resolv.conf in chroot\n"); | ||
1253 | int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_TRUNC|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); | ||
1254 | if (out == -1) | ||
1255 | errExit("open"); | ||
1256 | if (sendfile(out, in, NULL, instat.st_size) == -1) | ||
1257 | errExit("sendfile"); | ||
1258 | close(in); | ||
1259 | close(out); | ||
1260 | } | ||
1261 | |||
1262 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf | ||
1263 | void fs_chroot(const char *rootdir) { | ||
1264 | assert(rootdir); | ||
1265 | |||
1266 | int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
1267 | if (parentfd == -1) | ||
1268 | errExit("safe_fd"); | ||
1269 | |||
1270 | // mount-bind a /dev in rootdir | ||
1271 | if (arg_debug) | ||
1272 | printf("Mounting /dev on chroot /dev\n"); | ||
1273 | int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
1274 | if (fd == -1) | ||
1275 | errExit("open"); | ||
1276 | char *proc; | ||
1277 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
1278 | errExit("asprintf"); | ||
1279 | if (mount("/dev", proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1280 | errExit("mounting /dev"); | ||
1281 | free(proc); | ||
1282 | close(fd); | ||
1283 | |||
1284 | // mount a brand new proc filesystem | ||
1285 | if (arg_debug) | ||
1286 | printf("Mounting /proc filesystem on chroot /proc\n"); | ||
1287 | fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
1288 | if (fd == -1) | ||
1289 | errExit("open"); | ||
1290 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
1291 | errExit("asprintf"); | ||
1292 | if (mount("proc", proc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) | ||
1293 | errExit("mounting /proc"); | ||
1294 | free(proc); | ||
1295 | close(fd); | ||
1296 | |||
1297 | // x11 | ||
1298 | if (getenv("FIREJAIL_X11")) { | ||
1299 | if (arg_debug) | ||
1300 | printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); | ||
1301 | fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
1302 | if (fd == -1) | ||
1303 | errExit("open"); | ||
1304 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
1305 | errExit("asprintf"); | ||
1306 | if (mount("/tmp/.X11-unix", proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1307 | errExit("mounting /tmp/.X11-unix"); | ||
1308 | free(proc); | ||
1309 | close(fd); | ||
1310 | } | ||
1311 | |||
1312 | // update chroot resolv.conf | ||
1313 | copy_resolvconf(parentfd); | ||
1314 | |||
1315 | // some older distros don't have a /run directory, create one by default | ||
1316 | struct stat s; | ||
1317 | if (fstatat(parentfd, "run", &s, AT_SYMLINK_NOFOLLOW) == 0) { | ||
1318 | if (S_ISLNK(s.st_mode)) { | ||
1319 | fprintf(stderr, "Error: chroot /run is a symbolic link\n"); | ||
1320 | exit(1); | ||
1321 | } | ||
1322 | } | ||
1323 | else if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) | ||
1324 | errExit("mkdir"); | ||
1325 | fs_check_chroot_subdir("run", parentfd, 1); | ||
1326 | |||
1327 | // create /run/firejail directory in chroot | ||
1328 | if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) | ||
1329 | errExit("mkdir"); | ||
1330 | |||
1331 | // create /run/firejail/lib directory in chroot | ||
1332 | if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) | ||
1333 | errExit("mkdir"); | ||
1334 | // mount lib directory into the chroot | ||
1335 | fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
1336 | if (fd == -1) | ||
1337 | errExit("open"); | ||
1338 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
1339 | errExit("asprintf"); | ||
1340 | if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1341 | errExit("mount bind"); | ||
1342 | free(proc); | ||
1343 | close(fd); | ||
1344 | |||
1345 | // create /run/firejail/mnt directory in chroot | ||
1346 | if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) | ||
1347 | errExit("mkdir"); | ||
1348 | // mount the current mnt directory into the chroot | ||
1349 | fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); | ||
1350 | if (fd == -1) | ||
1351 | errExit("open"); | ||
1352 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
1353 | errExit("asprintf"); | ||
1354 | if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1355 | errExit("mount bind"); | ||
1356 | free(proc); | ||
1357 | close(fd); | ||
1358 | |||
1359 | #ifdef HAVE_GCOV | ||
1360 | __gcov_flush(); | ||
1361 | #endif | ||
1362 | // create /run/firejail/mnt/oroot | ||
1363 | char *oroot = RUN_OVERLAY_ROOT; | ||
1364 | if (mkdir(oroot, 0755) == -1) | ||
1365 | errExit("mkdir"); | ||
1366 | // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay | ||
1367 | if (asprintf(&proc, "/proc/self/fd/%d", parentfd) == -1) | ||
1368 | errExit("asprintf"); | ||
1369 | if (mount(proc, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1370 | errExit("mounting rootdir oroot"); | ||
1371 | free(proc); | ||
1372 | close(parentfd); | ||
1373 | // chroot into the new directory | ||
1374 | if (arg_debug) | ||
1375 | printf("Chrooting into %s\n", rootdir); | ||
1376 | if (chroot(oroot) < 0) | ||
1377 | errExit("chroot"); | ||
1378 | |||
1379 | // create all other /run/firejail files and directories | ||
1380 | preproc_build_firejail_dir(); | ||
1381 | |||
1382 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
1383 | // if (!arg_private_dev) | ||
1384 | // fs_dev_shm(); | ||
1385 | fs_var_lock(); | ||
1386 | if (!arg_keep_var_tmp) | ||
1387 | fs_var_tmp(); | ||
1388 | if (!arg_writable_var_log) | ||
1389 | fs_var_log(); | ||
1390 | |||
1391 | fs_var_lib(); | ||
1392 | fs_var_cache(); | ||
1393 | fs_var_utmp(); | ||
1394 | fs_machineid(); | ||
1395 | |||
1396 | // don't leak user information | ||
1397 | restrict_users(); | ||
1398 | |||
1399 | // when starting as root, firejail config is not disabled; | ||
1400 | if (getuid() != 0) | ||
1401 | disable_config(); | ||
1402 | } | ||
1403 | #endif | ||
1404 | |||
1405 | // this function is called from sandbox.c before blacklist/whitelist functions | 1127 | // this function is called from sandbox.c before blacklist/whitelist functions |
1406 | void fs_private_tmp(void) { | 1128 | void fs_private_tmp(void) { |
1407 | // check XAUTHORITY file, KDE keeps it under /tmp | 1129 | // check XAUTHORITY file, KDE keeps it under /tmp |