diff options
author | netblue30 <netblue30@yahoo.com> | 2019-10-04 16:11:06 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-04 16:11:06 -0500 |
commit | 6cf61b6ab53d44bc35e2bd7eb1a1987f72c25329 (patch) | |
tree | c459376a3d0d4516b170394388940f2038d32608 | |
parent | fix the fix (diff) | |
parent | improve enforce_filters warning (diff) | |
download | firejail-6cf61b6ab53d44bc35e2bd7eb1a1987f72c25329.tar.gz firejail-6cf61b6ab53d44bc35e2bd7eb1a1987f72c25329.tar.zst firejail-6cf61b6ab53d44bc35e2bd7eb1a1987f72c25329.zip |
Merge pull request #2982 from smitsohu/chroot
Move chroot entirely from path based to file descriptor based mounts
-rw-r--r-- | src/firejail/chroot.c | 280 | ||||
-rw-r--r-- | src/firejail/firejail.h | 10 | ||||
-rw-r--r-- | src/firejail/fs.c | 255 | ||||
-rw-r--r-- | src/firejail/main.c | 28 | ||||
-rw-r--r-- | src/firejail/sandbox.c | 2 |
5 files changed, 299 insertions, 276 deletions
diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c new file mode 100644 index 000000000..f5bb11a76 --- /dev/null +++ b/src/firejail/chroot.c | |||
@@ -0,0 +1,280 @@ | |||
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/sendfile.h> | ||
25 | #include <errno.h> | ||
26 | |||
27 | #include <fcntl.h> | ||
28 | #ifndef O_PATH | ||
29 | # define O_PATH 010000000 | ||
30 | #endif | ||
31 | |||
32 | |||
33 | // exit if error | ||
34 | void fs_check_chroot_dir(void) { | ||
35 | EUID_ASSERT(); | ||
36 | assert(cfg.chrootdir); | ||
37 | if (strstr(cfg.chrootdir, "..") || | ||
38 | is_link(cfg.chrootdir) || | ||
39 | !is_dir(cfg.chrootdir)) | ||
40 | goto errout; | ||
41 | |||
42 | // check chroot dirname exists, chrooting into the root directory is not allowed | ||
43 | char *rpath = realpath(cfg.chrootdir, NULL); | ||
44 | if (rpath == NULL || strcmp(rpath, "/") == 0) | ||
45 | goto errout; | ||
46 | |||
47 | char *overlay; | ||
48 | if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) | ||
49 | errExit("asprintf"); | ||
50 | if (strncmp(rpath, overlay, strlen(overlay)) == 0) { | ||
51 | fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); | ||
52 | exit(1); | ||
53 | } | ||
54 | free(overlay); | ||
55 | cfg.chrootdir = rpath; | ||
56 | return; | ||
57 | |||
58 | errout: | ||
59 | fprintf(stderr, "Error: invalid chroot directory %s\n", cfg.chrootdir); | ||
60 | exit(1); | ||
61 | } | ||
62 | |||
63 | // copy /etc/resolv.conf in chroot directory | ||
64 | static void copy_resolvconf(int parentfd) { | ||
65 | int in = open("/etc/resolv.conf", O_RDONLY|O_CLOEXEC); | ||
66 | if (in == -1) { | ||
67 | fwarning("/etc/resolv.conf not initialized\n"); | ||
68 | return; | ||
69 | } | ||
70 | struct stat src; | ||
71 | if (fstat(in, &src) == -1) | ||
72 | errExit("fstat"); | ||
73 | // try to detect if resolv.conf has been bind mounted into the chroot | ||
74 | // do nothing in this case in order to not unlink the real file | ||
75 | struct stat dst; | ||
76 | if (fstatat(parentfd, "etc/resolv.conf", &dst, 0) == 0) { | ||
77 | if (src.st_dev == dst.st_dev && src.st_ino == dst.st_ino) { | ||
78 | close(in); | ||
79 | return; | ||
80 | } | ||
81 | } | ||
82 | if (arg_debug) | ||
83 | printf("Updating /etc/resolv.conf in chroot\n"); | ||
84 | unlinkat(parentfd, "etc/resolv.conf", 0); | ||
85 | int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH); | ||
86 | if (out == -1) | ||
87 | errExit("open"); | ||
88 | if (sendfile(out, in, NULL, src.st_size) == -1) | ||
89 | errExit("sendfile"); | ||
90 | close(in); | ||
91 | close(out); | ||
92 | } | ||
93 | |||
94 | // exit if error | ||
95 | static void check_subdir(int parentfd, const char *subdir, int check_writable) { | ||
96 | assert(subdir); | ||
97 | struct stat s; | ||
98 | if (fstatat(parentfd, subdir, &s, AT_SYMLINK_NOFOLLOW) != 0) { | ||
99 | fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); | ||
100 | exit(1); | ||
101 | } | ||
102 | if (!S_ISDIR(s.st_mode)) { | ||
103 | if (S_ISLNK(s.st_mode)) | ||
104 | fprintf(stderr, "Error: chroot /%s is a symbolic link\n", subdir); | ||
105 | else | ||
106 | fprintf(stderr, "Error: chroot /%s is not a directory\n", subdir); | ||
107 | exit(1); | ||
108 | } | ||
109 | if (s.st_uid != 0) { | ||
110 | fprintf(stderr, "Error: chroot /%s should owned by root\n", subdir); | ||
111 | exit(1); | ||
112 | } | ||
113 | if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
114 | fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); | ||
115 | exit(1); | ||
116 | } | ||
117 | } | ||
118 | |||
119 | // chroot into an existing directory; mount existing /dev and update /etc/resolv.conf | ||
120 | void fs_chroot(const char *rootdir) { | ||
121 | assert(rootdir); | ||
122 | |||
123 | // fails if there is any symlink or if rootdir is not a directory | ||
124 | int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
125 | if (parentfd == -1) | ||
126 | errExit("safe_fd"); | ||
127 | // rootdir has to be owned by root and is not allowed to be generally writable, | ||
128 | // this also excludes /tmp and friends | ||
129 | struct stat s; | ||
130 | if (fstat(parentfd, &s) == -1) | ||
131 | errExit("fstat"); | ||
132 | if (s.st_uid != 0) { | ||
133 | fprintf(stderr, "Error: chroot directory should be owned by root\n"); | ||
134 | exit(1); | ||
135 | } | ||
136 | if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
137 | fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); | ||
138 | exit(1); | ||
139 | } | ||
140 | // check chroot subdirectories; /tmp/.X11-unix and /run are treated separately | ||
141 | check_subdir(parentfd, "dev", 0); | ||
142 | check_subdir(parentfd, "etc", 1); | ||
143 | check_subdir(parentfd, "proc", 0); | ||
144 | check_subdir(parentfd, "tmp", 0); | ||
145 | check_subdir(parentfd, "var/tmp", 0); | ||
146 | |||
147 | // mount-bind a /dev in rootdir | ||
148 | if (arg_debug) | ||
149 | printf("Mounting /dev on chroot /dev\n"); | ||
150 | // open chroot /dev to get a file descriptor, | ||
151 | // then use this descriptor as a mount target | ||
152 | int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
153 | if (fd == -1) | ||
154 | errExit("open"); | ||
155 | char *proc; | ||
156 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
157 | errExit("asprintf"); | ||
158 | if (mount("/dev", proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
159 | errExit("mounting /dev"); | ||
160 | free(proc); | ||
161 | close(fd); | ||
162 | |||
163 | // mount a brand new proc filesystem | ||
164 | if (arg_debug) | ||
165 | printf("Mounting /proc filesystem on chroot /proc\n"); | ||
166 | fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
167 | if (fd == -1) | ||
168 | errExit("open"); | ||
169 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
170 | errExit("asprintf"); | ||
171 | if (mount("proc", proc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) | ||
172 | errExit("mounting /proc"); | ||
173 | free(proc); | ||
174 | close(fd); | ||
175 | |||
176 | // x11 | ||
177 | if (getenv("FIREJAIL_X11")) { | ||
178 | if (arg_debug) | ||
179 | printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); | ||
180 | check_subdir(parentfd, "tmp/.X11-unix", 0); | ||
181 | fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
182 | if (fd == -1) | ||
183 | errExit("open"); | ||
184 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
185 | errExit("asprintf"); | ||
186 | if (mount("/tmp/.X11-unix", proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
187 | errExit("mounting /tmp/.X11-unix"); | ||
188 | free(proc); | ||
189 | close(fd); | ||
190 | } | ||
191 | |||
192 | // some older distros don't have a /run directory, create one by default | ||
193 | if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) | ||
194 | errExit("mkdir"); | ||
195 | check_subdir(parentfd, "run", 1); | ||
196 | |||
197 | // create /run/firejail directory in chroot | ||
198 | if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) | ||
199 | errExit("mkdir"); | ||
200 | check_subdir(parentfd, RUN_FIREJAIL_DIR+1, 1); | ||
201 | |||
202 | // create /run/firejail/lib directory in chroot | ||
203 | if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) | ||
204 | errExit("mkdir"); | ||
205 | check_subdir(parentfd, RUN_FIREJAIL_LIB_DIR+1, 1); | ||
206 | // mount lib directory into the chroot | ||
207 | fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
208 | if (fd == -1) | ||
209 | errExit("open"); | ||
210 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
211 | errExit("asprintf"); | ||
212 | if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
213 | errExit("mount bind"); | ||
214 | free(proc); | ||
215 | close(fd); | ||
216 | |||
217 | // create /run/firejail/mnt directory in chroot | ||
218 | if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) | ||
219 | errExit("mkdir"); | ||
220 | check_subdir(parentfd, RUN_MNT_DIR+1, 1); | ||
221 | // mount the current mnt directory into the chroot | ||
222 | fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
223 | if (fd == -1) | ||
224 | errExit("open"); | ||
225 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | ||
226 | errExit("asprintf"); | ||
227 | if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
228 | errExit("mount bind"); | ||
229 | free(proc); | ||
230 | close(fd); | ||
231 | |||
232 | // update chroot resolv.conf | ||
233 | copy_resolvconf(parentfd); | ||
234 | |||
235 | #ifdef HAVE_GCOV | ||
236 | __gcov_flush(); | ||
237 | #endif | ||
238 | // create /run/firejail/mnt/oroot | ||
239 | char *oroot = RUN_OVERLAY_ROOT; | ||
240 | if (mkdir(oroot, 0755) == -1) | ||
241 | errExit("mkdir"); | ||
242 | // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay | ||
243 | if (asprintf(&proc, "/proc/self/fd/%d", parentfd) == -1) | ||
244 | errExit("asprintf"); | ||
245 | if (mount(proc, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
246 | errExit("mounting rootdir oroot"); | ||
247 | free(proc); | ||
248 | close(parentfd); | ||
249 | // chroot into the new directory | ||
250 | if (arg_debug) | ||
251 | printf("Chrooting into %s\n", rootdir); | ||
252 | if (chroot(oroot) < 0) | ||
253 | errExit("chroot"); | ||
254 | |||
255 | // create all other /run/firejail files and directories | ||
256 | preproc_build_firejail_dir(); | ||
257 | |||
258 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
259 | // if (!arg_private_dev) | ||
260 | // fs_dev_shm(); | ||
261 | fs_var_lock(); | ||
262 | if (!arg_keep_var_tmp) | ||
263 | fs_var_tmp(); | ||
264 | if (!arg_writable_var_log) | ||
265 | fs_var_log(); | ||
266 | |||
267 | fs_var_lib(); | ||
268 | fs_var_cache(); | ||
269 | fs_var_utmp(); | ||
270 | fs_machineid(); | ||
271 | |||
272 | // don't leak user information | ||
273 | restrict_users(); | ||
274 | |||
275 | // when starting as root, firejail config is not disabled; | ||
276 | if (getuid() != 0) | ||
277 | disable_config(); | ||
278 | } | ||
279 | |||
280 | #endif // HAVE_CHROOT | ||
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h index a6377261f..fdbeb4691 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(void); | ||
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 ce2ca5e2a..f2639f318 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -28,6 +28,7 @@ | |||
28 | #include <dirent.h> | 28 | #include <dirent.h> |
29 | #include <errno.h> | 29 | #include <errno.h> |
30 | 30 | ||
31 | |||
31 | #include <fcntl.h> | 32 | #include <fcntl.h> |
32 | #ifndef O_PATH | 33 | #ifndef O_PATH |
33 | # define O_PATH 010000000 | 34 | # define O_PATH 010000000 |
@@ -696,8 +697,8 @@ void fs_proc_sys_dev_boot(void) { | |||
696 | } | 697 | } |
697 | } | 698 | } |
698 | 699 | ||
699 | // disable firejail configuration in /etc/firejail and in ~/.config/firejail | 700 | // disable firejail configuration in ~/.config/firejail |
700 | static void disable_config(void) { | 701 | void disable_config(void) { |
701 | struct stat s; | 702 | struct stat s; |
702 | 703 | ||
703 | char *fname; | 704 | char *fname; |
@@ -1123,256 +1124,6 @@ void fs_overlayfs(void) { | |||
1123 | } | 1124 | } |
1124 | #endif | 1125 | #endif |
1125 | 1126 | ||
1126 | |||
1127 | #ifdef HAVE_CHROOT | ||
1128 | // exit if error | ||
1129 | static void fs_check_chroot_subdir(const char *subdir, int parentfd, int check_writable) { | ||
1130 | assert(subdir); | ||
1131 | int fd = openat(parentfd, subdir, O_PATH|O_CLOEXEC); | ||
1132 | if (fd == -1) { | ||
1133 | if (errno == ENOENT) | ||
1134 | fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir); | ||
1135 | else { | ||
1136 | perror("open"); | ||
1137 | fprintf(stderr, "Error: cannot open /%s in chroot directory\n", subdir); | ||
1138 | } | ||
1139 | exit(1); | ||
1140 | } | ||
1141 | struct stat s; | ||
1142 | if (fstat(fd, &s) == -1) | ||
1143 | errExit("fstat"); | ||
1144 | close(fd); | ||
1145 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | ||
1146 | fprintf(stderr, "Error: chroot /%s should be a directory owned by root\n", subdir); | ||
1147 | exit(1); | ||
1148 | } | ||
1149 | if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
1150 | fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir); | ||
1151 | exit(1); | ||
1152 | } | ||
1153 | } | ||
1154 | |||
1155 | // exit if error | ||
1156 | void fs_check_chroot_dir(const char *rootdir) { | ||
1157 | EUID_ASSERT(); | ||
1158 | assert(rootdir); | ||
1159 | |||
1160 | char *overlay; | ||
1161 | if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) | ||
1162 | errExit("asprintf"); | ||
1163 | if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { | ||
1164 | fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); | ||
1165 | exit(1); | ||
1166 | } | ||
1167 | free(overlay); | ||
1168 | |||
1169 | // fails if there is any symlink or if rootdir is not a directory | ||
1170 | int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
1171 | if (parentfd == -1) { | ||
1172 | fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); | ||
1173 | exit(1); | ||
1174 | } | ||
1175 | // rootdir has to be owned by root and is not allowed to be generally writable, | ||
1176 | // this also excludes /tmp, /var/tmp and such | ||
1177 | struct stat s; | ||
1178 | if (fstat(parentfd, &s) == -1) | ||
1179 | errExit("fstat"); | ||
1180 | if (s.st_uid != 0) { | ||
1181 | fprintf(stderr, "Error: chroot directory should be owned by root\n"); | ||
1182 | exit(1); | ||
1183 | } | ||
1184 | if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
1185 | fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n"); | ||
1186 | exit(1); | ||
1187 | } | ||
1188 | |||
1189 | // check subdirectories in rootdir | ||
1190 | fs_check_chroot_subdir("dev", parentfd, 0); | ||
1191 | fs_check_chroot_subdir("etc", parentfd, 1); | ||
1192 | fs_check_chroot_subdir("proc", parentfd, 0); | ||
1193 | fs_check_chroot_subdir("tmp", parentfd, 0); | ||
1194 | fs_check_chroot_subdir("var/tmp", parentfd, 0); | ||
1195 | |||
1196 | // there should be no checking on <chrootdir>/etc/resolv.conf | ||
1197 | // the file is replaced with the real /etc/resolv.conf anyway | ||
1198 | #if 0 | ||
1199 | if (asprintf(&name, "%s/etc/resolv.conf", rootdir) == -1) | ||
1200 | errExit("asprintf"); | ||
1201 | if (stat(name, &s) == 0) { | ||
1202 | if (s.st_uid != 0) { | ||
1203 | fprintf(stderr, "Error: chroot /etc/resolv.conf should be owned by root\n"); | ||
1204 | exit(1); | ||
1205 | } | ||
1206 | } | ||
1207 | else { | ||
1208 | fprintf(stderr, "Error: chroot /etc/resolv.conf not found\n"); | ||
1209 | exit(1); | ||
1210 | } | ||
1211 | // on Arch /etc/resolv.conf could be a symlink to /run/systemd/resolve/resolv.conf | ||
1212 | // on Ubuntu 17.04 /etc/resolv.conf could be a symlink to /run/resolveconf/resolv.conf | ||
1213 | if (is_link(name)) { | ||
1214 | // check the link points in chroot | ||
1215 | char *rname = realpath(name, NULL); | ||
1216 | if (!rname || strncmp(rname, rootdir, strlen(rootdir)) != 0) { | ||
1217 | fprintf(stderr, "Error: chroot /etc/resolv.conf is pointing outside chroot\n"); | ||
1218 | exit(1); | ||
1219 | } | ||
1220 | } | ||
1221 | free(name); | ||
1222 | #endif | ||
1223 | |||
1224 | // check x11 socket directory | ||
1225 | if (getenv("FIREJAIL_X11")) | ||
1226 | fs_check_chroot_subdir("tmp/.X11-unix", parentfd, 0); | ||
1227 | |||
1228 | close(parentfd); | ||
1229 | } | ||
1230 | |||
1231 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf | ||
1232 | void fs_chroot(const char *rootdir) { | ||
1233 | assert(rootdir); | ||
1234 | |||
1235 | // mount-bind a /dev in rootdir | ||
1236 | char *newdev; | ||
1237 | if (asprintf(&newdev, "%s/dev", rootdir) == -1) | ||
1238 | errExit("asprintf"); | ||
1239 | if (arg_debug) | ||
1240 | printf("Mounting /dev on %s\n", newdev); | ||
1241 | if (mount("/dev", newdev, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1242 | errExit("mounting /dev"); | ||
1243 | free(newdev); | ||
1244 | |||
1245 | // mount a new proc filesystem | ||
1246 | char *newproc; | ||
1247 | if (asprintf(&newproc, "%s/proc", rootdir) == -1) | ||
1248 | errExit("asprintf"); | ||
1249 | if (arg_debug) | ||
1250 | printf("Mounting /proc filesystem on %s\n", newproc); | ||
1251 | if (mount("proc", newproc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) | ||
1252 | errExit("mounting /proc"); | ||
1253 | free(newproc); | ||
1254 | |||
1255 | // x11 | ||
1256 | if (getenv("FIREJAIL_X11")) { | ||
1257 | char *newx11; | ||
1258 | if (asprintf(&newx11, "%s/tmp/.X11-unix", rootdir) == -1) | ||
1259 | errExit("asprintf"); | ||
1260 | if (arg_debug) | ||
1261 | printf("Mounting /tmp/.X11-unix on %s\n", newx11); | ||
1262 | if (mount("/tmp/.X11-unix", newx11, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1263 | errExit("mounting /tmp/.X11-unix"); | ||
1264 | free(newx11); | ||
1265 | } | ||
1266 | |||
1267 | // some older distros don't have a /run directory | ||
1268 | // create one by default | ||
1269 | char *rundir; | ||
1270 | if (asprintf(&rundir, "%s/run", rootdir) == -1) | ||
1271 | errExit("asprintf"); | ||
1272 | struct stat s; | ||
1273 | if (lstat(rundir, &s) == 0) { | ||
1274 | if (S_ISLNK(s.st_mode)) { | ||
1275 | fprintf(stderr, "Error: chroot /run is a symbolic link\n"); | ||
1276 | exit(1); | ||
1277 | } | ||
1278 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | ||
1279 | fprintf(stderr, "Error: chroot /run should be a directory owned by root\n"); | ||
1280 | exit(1); | ||
1281 | } | ||
1282 | if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
1283 | fprintf(stderr, "Error: only root user should be given write permission on chroot /run\n"); | ||
1284 | exit(1); | ||
1285 | } | ||
1286 | } | ||
1287 | else { | ||
1288 | // several sandboxes could race to create /run | ||
1289 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | ||
1290 | errExit("mkdir"); | ||
1291 | ASSERT_PERMS(rundir, 0, 0, 0755); | ||
1292 | } | ||
1293 | free(rundir); | ||
1294 | |||
1295 | // create /run/firejail directory in chroot | ||
1296 | if (asprintf(&rundir, "%s/run/firejail", rootdir) == -1) | ||
1297 | errExit("asprintf"); | ||
1298 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | ||
1299 | errExit("mkdir"); | ||
1300 | ASSERT_PERMS(rundir, 0, 0, 0755); | ||
1301 | free(rundir); | ||
1302 | |||
1303 | // create /run/firejail/lib directory in chroot and mount it | ||
1304 | if (asprintf(&rundir, "%s%s", rootdir, RUN_FIREJAIL_LIB_DIR) == -1) | ||
1305 | errExit("asprintf"); | ||
1306 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | ||
1307 | errExit("mkdir"); | ||
1308 | ASSERT_PERMS(rundir, 0, 0, 0755); | ||
1309 | if (mount(RUN_FIREJAIL_LIB_DIR, rundir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1310 | errExit("mount bind"); | ||
1311 | free(rundir); | ||
1312 | |||
1313 | // create /run/firejail/mnt directory in chroot and mount the current one | ||
1314 | if (asprintf(&rundir, "%s%s", rootdir, RUN_MNT_DIR) == -1) | ||
1315 | errExit("asprintf"); | ||
1316 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | ||
1317 | errExit("mkdir"); | ||
1318 | ASSERT_PERMS(rundir, 0, 0, 0755); | ||
1319 | if (mount(RUN_MNT_DIR, rundir, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1320 | errExit("mount bind"); | ||
1321 | free(rundir); | ||
1322 | |||
1323 | // copy /etc/resolv.conf in chroot directory | ||
1324 | char *fname; | ||
1325 | if (asprintf(&fname, "%s/etc/resolv.conf", rootdir) == -1) | ||
1326 | errExit("asprintf"); | ||
1327 | if (arg_debug) | ||
1328 | printf("Updating /etc/resolv.conf in %s\n", fname); | ||
1329 | unlink(fname); | ||
1330 | if (copy_file("/etc/resolv.conf", fname, 0, 0, 0644) == -1) // root needed | ||
1331 | fwarning("/etc/resolv.conf not initialized\n"); | ||
1332 | free(fname); | ||
1333 | |||
1334 | // chroot into the new directory | ||
1335 | #ifdef HAVE_GCOV | ||
1336 | __gcov_flush(); | ||
1337 | #endif | ||
1338 | // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay | ||
1339 | // and chroot into this new directory | ||
1340 | if (arg_debug) | ||
1341 | printf("Chrooting into %s\n", rootdir); | ||
1342 | char *oroot = RUN_OVERLAY_ROOT; | ||
1343 | if (mkdir(oroot, 0755) == -1) | ||
1344 | errExit("mkdir"); | ||
1345 | if (mount(rootdir, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) | ||
1346 | errExit("mounting rootdir oroot"); | ||
1347 | if (chroot(oroot) < 0) | ||
1348 | errExit("chroot"); | ||
1349 | |||
1350 | // create all other /run/firejail files and directories | ||
1351 | preproc_build_firejail_dir(); | ||
1352 | |||
1353 | // update /var directory in order to support multiple sandboxes running on the same root directory | ||
1354 | // if (!arg_private_dev) | ||
1355 | // fs_dev_shm(); | ||
1356 | fs_var_lock(); | ||
1357 | if (!arg_keep_var_tmp) | ||
1358 | fs_var_tmp(); | ||
1359 | if (!arg_writable_var_log) | ||
1360 | fs_var_log(); | ||
1361 | |||
1362 | fs_var_lib(); | ||
1363 | fs_var_cache(); | ||
1364 | fs_var_utmp(); | ||
1365 | fs_machineid(); | ||
1366 | |||
1367 | // don't leak user information | ||
1368 | restrict_users(); | ||
1369 | |||
1370 | // when starting as root, firejail config is not disabled; | ||
1371 | if (getuid() != 0) | ||
1372 | disable_config(); | ||
1373 | } | ||
1374 | #endif | ||
1375 | |||
1376 | // this function is called from sandbox.c before blacklist/whitelist functions | 1127 | // this function is called from sandbox.c before blacklist/whitelist functions |
1377 | void fs_private_tmp(void) { | 1128 | void fs_private_tmp(void) { |
1378 | // check XAUTHORITY file, KDE keeps it under /tmp | 1129 | // check XAUTHORITY file, KDE keeps it under /tmp |
diff --git a/src/firejail/main.c b/src/firejail/main.c index e8664e914..cbe3292ba 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c | |||
@@ -1659,12 +1659,14 @@ int main(int argc, char **argv) { | |||
1659 | fprintf(stderr, "Error: --chroot option is not available on Grsecurity systems\n"); | 1659 | fprintf(stderr, "Error: --chroot option is not available on Grsecurity systems\n"); |
1660 | exit(1); | 1660 | exit(1); |
1661 | } | 1661 | } |
1662 | |||
1663 | |||
1664 | invalid_filename(argv[i] + 9, 0); // no globbing | ||
1665 | |||
1666 | // extract chroot dirname | 1662 | // extract chroot dirname |
1667 | cfg.chrootdir = argv[i] + 9; | 1663 | cfg.chrootdir = argv[i] + 9; |
1664 | if (*cfg.chrootdir == '\0') { | ||
1665 | fprintf(stderr, "Error: invalid chroot option\n"); | ||
1666 | exit(1); | ||
1667 | } | ||
1668 | invalid_filename(cfg.chrootdir, 0); // no globbing | ||
1669 | |||
1668 | // if the directory starts with ~, expand the home directory | 1670 | // if the directory starts with ~, expand the home directory |
1669 | if (*cfg.chrootdir == '~') { | 1671 | if (*cfg.chrootdir == '~') { |
1670 | char *tmp; | 1672 | char *tmp; |
@@ -1672,22 +1674,8 @@ int main(int argc, char **argv) { | |||
1672 | errExit("asprintf"); | 1674 | errExit("asprintf"); |
1673 | cfg.chrootdir = tmp; | 1675 | cfg.chrootdir = tmp; |
1674 | } | 1676 | } |
1675 | 1677 | // check chroot directory | |
1676 | if (strstr(cfg.chrootdir, "..") || is_link(cfg.chrootdir)) { | 1678 | fs_check_chroot_dir(); |
1677 | fprintf(stderr, "Error: invalid chroot directory %s\n", cfg.chrootdir); | ||
1678 | return 1; | ||
1679 | } | ||
1680 | |||
1681 | // check chroot dirname exists, don't allow "--chroot=/" | ||
1682 | char *rpath = realpath(cfg.chrootdir, NULL); | ||
1683 | if (rpath == NULL || strcmp(rpath, "/") == 0) { | ||
1684 | fprintf(stderr, "Error: invalid chroot directory\n"); | ||
1685 | exit(1); | ||
1686 | } | ||
1687 | cfg.chrootdir = rpath; | ||
1688 | |||
1689 | // check chroot directory structure | ||
1690 | fs_check_chroot_dir(cfg.chrootdir); | ||
1691 | } | 1679 | } |
1692 | else | 1680 | else |
1693 | exit_err_feature("chroot"); | 1681 | exit_err_feature("chroot"); |
diff --git a/src/firejail/sandbox.c b/src/firejail/sandbox.c index 288726d22..80b595a9f 100644 --- a/src/firejail/sandbox.c +++ b/src/firejail/sandbox.c | |||
@@ -578,7 +578,7 @@ static void enforce_filters(void) { | |||
578 | force_nonewprivs = 1; | 578 | force_nonewprivs = 1; |
579 | 579 | ||
580 | // disable all capabilities | 580 | // disable all capabilities |
581 | fmessage("\n** Warning: dropping all Linux capabilities **\n"); | 581 | fmessage("\n** Warning: dropping all Linux capabilities **\n\n"); |
582 | arg_caps_drop_all = 1; | 582 | arg_caps_drop_all = 1; |
583 | 583 | ||
584 | // drop all supplementary groups; /etc/group file inside chroot | 584 | // drop all supplementary groups; /etc/group file inside chroot |