aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorLibravatar smitsohu <smitsohu@gmail.com>2019-10-01 15:46:12 +0200
committerLibravatar smitsohu <smitsohu@gmail.com>2019-10-01 15:46:12 +0200
commit70c8924ebaf51387f74eba3c443ef7e870d2afc9 (patch)
treeda4a8e53bc0d6311739e0ee6357a55a0d954f063 /src
parentimprove variable names (diff)
downloadfirejail-70c8924ebaf51387f74eba3c443ef7e870d2afc9.tar.gz
firejail-70c8924ebaf51387f74eba3c443ef7e870d2afc9.tar.zst
firejail-70c8924ebaf51387f74eba3c443ef7e870d2afc9.zip
base checks and mounts on same file descriptor
Diffstat (limited to 'src')
-rw-r--r--src/firejail/chroot.c190
-rw-r--r--src/firejail/firejail.h3
-rw-r--r--src/firejail/main.c32
3 files changed, 91 insertions, 134 deletions
diff --git a/src/firejail/chroot.c b/src/firejail/chroot.c
index 4eff84494..8a57dee35 100644
--- a/src/firejail/chroot.c
+++ b/src/firejail/chroot.c
@@ -21,7 +21,6 @@
21#ifdef HAVE_CHROOT 21#ifdef HAVE_CHROOT
22#include "firejail.h" 22#include "firejail.h"
23#include <sys/mount.h> 23#include <sys/mount.h>
24#include <sys/stat.h>
25#include <sys/sendfile.h> 24#include <sys/sendfile.h>
26#include <errno.h> 25#include <errno.h>
27 26
@@ -31,107 +30,33 @@
31#endif 30#endif
32 31
33 32
34// exit if error 33// exit if error, return resolved chroot path
35static void fs_check_chroot_subdir(const char *subdir, int parentfd, int check_writable) { 34char *fs_check_chroot_dir(const char *rootdir) {
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
62void fs_check_chroot_dir(const char *rootdir) {
63 EUID_ASSERT(); 35 EUID_ASSERT();
64 assert(rootdir); 36 assert(rootdir);
37 if (strstr(rootdir, "..") ||
38 is_link(rootdir) ||
39 !is_dir(rootdir))
40 goto errout;
41
42 // check chroot dirname exists, chrooting into the root directory is not allowed
43 char *rpath = realpath(rootdir, NULL);
44 if (rpath == NULL || strcmp(rpath, "/") == 0)
45 goto errout;
65 46
66 char *overlay; 47 char *overlay;
67 if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) 48 if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1)
68 errExit("asprintf"); 49 errExit("asprintf");
69 if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { 50 if (strncmp(rpath, overlay, strlen(overlay)) == 0) {
70 fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay); 51 fprintf(stderr, "Error: invalid chroot directory: no directories in %s are allowed\n", overlay);
71 exit(1); 52 exit(1);
72 } 53 }
73 free(overlay); 54 free(overlay);
55 return rpath;
74 56
75 // fails if there is any symlink or if rootdir is not a directory 57errout:
76 int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 58 fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir);
77 if (parentfd == -1) { 59 exit(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} 60}
136 61
137// copy /etc/resolv.conf in chroot directory 62// copy /etc/resolv.conf in chroot directory
@@ -145,7 +70,7 @@ static void copy_resolvconf(int parentfd) {
145 if (fstat(in, &src) == -1) 70 if (fstat(in, &src) == -1)
146 errExit("fstat"); 71 errExit("fstat");
147 // try to detect if resolv.conf has been bind mounted into the chroot 72 // 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 73 // do nothing in this case in order to not unlink the real file
149 struct stat dst; 74 struct stat dst;
150 if (fstatat(parentfd, "etc/resolv.conf", &dst, 0) == 0) { 75 if (fstatat(parentfd, "etc/resolv.conf", &dst, 0) == 0) {
151 if (src.st_dev == dst.st_dev && src.st_ino == dst.st_ino) { 76 if (src.st_dev == dst.st_dev && src.st_ino == dst.st_ino) {
@@ -155,7 +80,8 @@ static void copy_resolvconf(int parentfd) {
155 } 80 }
156 if (arg_debug) 81 if (arg_debug)
157 printf("Updating /etc/resolv.conf in chroot\n"); 82 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); 83 unlinkat(parentfd, "etc/resolv.conf", 0);
84 int out = openat(parentfd, "etc/resolv.conf", O_CREAT|O_WRONLY|O_CLOEXEC, S_IRUSR | S_IWRITE | S_IRGRP | S_IROTH);
159 if (out == -1) 85 if (out == -1)
160 errExit("open"); 86 errExit("open");
161 if (sendfile(out, in, NULL, src.st_size) == -1) 87 if (sendfile(out, in, NULL, src.st_size) == -1)
@@ -164,18 +90,65 @@ static void copy_resolvconf(int parentfd) {
164 close(out); 90 close(out);
165} 91}
166 92
93// exit if error
94static void check_subdir(int parentfd, const char *subdir, int check_writable) {
95 assert(subdir);
96 struct stat s;
97 if (fstatat(parentfd, subdir, &s, AT_SYMLINK_NOFOLLOW) != 0) {
98 fprintf(stderr, "Error: cannot find /%s in chroot directory\n", subdir);
99 exit(1);
100 }
101 if (!S_ISDIR(s.st_mode)) {
102 if (S_ISLNK(s.st_mode))
103 fprintf(stderr, "Error: chroot /%s is a symbolic link\n", subdir);
104 else
105 fprintf(stderr, "Error: chroot /%s is not a directory\n", subdir);
106 exit(1);
107 }
108 if (s.st_uid != 0) {
109 fprintf(stderr, "Error: chroot /%s should owned by root\n", subdir);
110 exit(1);
111 }
112 if (check_writable && ((S_IWGRP|S_IWOTH) & s.st_mode) != 0) {
113 fprintf(stderr, "Error: only root user should be given write permission on chroot /%s\n", subdir);
114 exit(1);
115 }
116}
117
167// chroot into an existing directory; mount existing /dev and update /etc/resolv.conf 118// chroot into an existing directory; mount existing /dev and update /etc/resolv.conf
168void fs_chroot(const char *rootdir) { 119void fs_chroot(const char *rootdir) {
169 assert(rootdir); 120 assert(rootdir);
170 121
122 // fails if there is any symlink or if rootdir is not a directory
171 int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); 123 int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
172 if (parentfd == -1) 124 if (parentfd == -1)
173 errExit("safe_fd"); 125 errExit("safe_fd");
126 // rootdir has to be owned by root and is not allowed to be generally writable,
127 // this also excludes /tmp and friends
128 struct stat s;
129 if (fstat(parentfd, &s) == -1)
130 errExit("fstat");
131 if (s.st_uid != 0) {
132 fprintf(stderr, "Error: chroot directory should be owned by root\n");
133 exit(1);
134 }
135 if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) {
136 fprintf(stderr, "Error: only root user should be given write permission on chroot directory\n");
137 exit(1);
138 }
139 // check chroot subdirectories; /tmp/.X11-unix and /run are treated separately
140 check_subdir(parentfd, "dev", 0);
141 check_subdir(parentfd, "etc", 1);
142 check_subdir(parentfd, "proc", 0);
143 check_subdir(parentfd, "tmp", 0);
144 check_subdir(parentfd, "var/tmp", 0);
174 145
175 // mount-bind a /dev in rootdir 146 // mount-bind a /dev in rootdir
176 if (arg_debug) 147 if (arg_debug)
177 printf("Mounting /dev on chroot /dev\n"); 148 printf("Mounting /dev on chroot /dev\n");
178 int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_CLOEXEC); 149 // open chroot /dev to get a file descriptor,
150 // then use this descriptor as a mount target
151 int fd = openat(parentfd, "dev", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
179 if (fd == -1) 152 if (fd == -1)
180 errExit("open"); 153 errExit("open");
181 char *proc; 154 char *proc;
@@ -189,7 +162,7 @@ void fs_chroot(const char *rootdir) {
189 // mount a brand new proc filesystem 162 // mount a brand new proc filesystem
190 if (arg_debug) 163 if (arg_debug)
191 printf("Mounting /proc filesystem on chroot /proc\n"); 164 printf("Mounting /proc filesystem on chroot /proc\n");
192 fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_CLOEXEC); 165 fd = openat(parentfd, "proc", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
193 if (fd == -1) 166 if (fd == -1)
194 errExit("open"); 167 errExit("open");
195 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 168 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
@@ -203,7 +176,8 @@ void fs_chroot(const char *rootdir) {
203 if (getenv("FIREJAIL_X11")) { 176 if (getenv("FIREJAIL_X11")) {
204 if (arg_debug) 177 if (arg_debug)
205 printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); 178 printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n");
206 fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_CLOEXEC); 179 check_subdir(parentfd, "tmp/.X11-unix", 0);
180 fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
207 if (fd == -1) 181 if (fd == -1)
208 errExit("open"); 182 errExit("open");
209 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 183 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
@@ -214,30 +188,22 @@ void fs_chroot(const char *rootdir) {
214 close(fd); 188 close(fd);
215 } 189 }
216 190
217 // update chroot resolv.conf
218 copy_resolvconf(parentfd);
219
220 // some older distros don't have a /run directory, create one by default 191 // some older distros don't have a /run directory, create one by default
221 struct stat s; 192 if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST)
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"); 193 errExit("mkdir");
230 fs_check_chroot_subdir("run", parentfd, 1); 194 check_subdir(parentfd, "run", 1);
231 195
232 // create /run/firejail directory in chroot 196 // create /run/firejail directory in chroot
233 if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) 197 if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST)
234 errExit("mkdir"); 198 errExit("mkdir");
199 check_subdir(parentfd, RUN_FIREJAIL_DIR+1, 1);
235 200
236 // create /run/firejail/lib directory in chroot 201 // create /run/firejail/lib directory in chroot
237 if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) 202 if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST)
238 errExit("mkdir"); 203 errExit("mkdir");
204 check_subdir(parentfd, RUN_FIREJAIL_LIB_DIR+1, 1);
239 // mount lib directory into the chroot 205 // mount lib directory into the chroot
240 fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); 206 fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
241 if (fd == -1) 207 if (fd == -1)
242 errExit("open"); 208 errExit("open");
243 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 209 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
@@ -250,8 +216,9 @@ void fs_chroot(const char *rootdir) {
250 // create /run/firejail/mnt directory in chroot 216 // create /run/firejail/mnt directory in chroot
251 if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) 217 if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST)
252 errExit("mkdir"); 218 errExit("mkdir");
219 check_subdir(parentfd, RUN_MNT_DIR+1, 1);
253 // mount the current mnt directory into the chroot 220 // mount the current mnt directory into the chroot
254 fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); 221 fd = openat(parentfd, RUN_MNT_DIR+1, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC);
255 if (fd == -1) 222 if (fd == -1)
256 errExit("open"); 223 errExit("open");
257 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) 224 if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1)
@@ -261,6 +228,9 @@ void fs_chroot(const char *rootdir) {
261 free(proc); 228 free(proc);
262 close(fd); 229 close(fd);
263 230
231 // update chroot resolv.conf
232 copy_resolvconf(parentfd);
233
264#ifdef HAVE_GCOV 234#ifdef HAVE_GCOV
265 __gcov_flush(); 235 __gcov_flush();
266#endif 236#endif
diff --git a/src/firejail/firejail.h b/src/firejail/firejail.h
index 80cf71caf..487803770 100644
--- a/src/firejail/firejail.h
+++ b/src/firejail/firejail.h
@@ -398,8 +398,9 @@ void fs_private_cache(void);
398void fs_mnt(const int enforce); 398void fs_mnt(const int enforce);
399 399
400// chroot.c 400// chroot.c
401// returns resolved chroot directory path
402char *fs_check_chroot_dir(const char *rootdir);
401// chroot into an existing directory; mount existing /dev and update /etc/resolv.conf 403// chroot into an existing directory; mount existing /dev and update /etc/resolv.conf
402void fs_check_chroot_dir(const char *rootdir);
403void fs_chroot(const char *rootdir); 404void fs_chroot(const char *rootdir);
404 405
405// profile.c 406// profile.c
diff --git a/src/firejail/main.c b/src/firejail/main.c
index e8664e914..5c83239ef 100644
--- a/src/firejail/main.c
+++ b/src/firejail/main.c
@@ -1659,35 +1659,21 @@ 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 1662 if (*(argv[i] + 9) == '\0') {
1663 1663 fprintf(stderr, "Error: invalid chroot option\n");
1664 exit(1);
1665 }
1664 invalid_filename(argv[i] + 9, 0); // no globbing 1666 invalid_filename(argv[i] + 9, 0); // no globbing
1665 1667
1666 // extract chroot dirname 1668 // extract chroot dirname
1667 cfg.chrootdir = argv[i] + 9; 1669 char *tmp = argv[i] + 9;
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 (*(argv[i] + 9) == '~') {
1670 char *tmp; 1672 if (asprintf(&tmp, "%s%s", cfg.homedir, argv[i] + 10) == -1)
1671 if (asprintf(&tmp, "%s%s", cfg.homedir, cfg.chrootdir + 1) == -1)
1672 errExit("asprintf"); 1673 errExit("asprintf");
1673 cfg.chrootdir = tmp;
1674 } 1674 }
1675 1675 // check chroot directory
1676 if (strstr(cfg.chrootdir, "..") || is_link(cfg.chrootdir)) { 1676 cfg.chrootdir = fs_check_chroot_dir(tmp);
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 } 1677 }
1692 else 1678 else
1693 exit_err_feature("chroot"); 1679 exit_err_feature("chroot");