diff options
author | smitsohu <smitsohu@gmail.com> | 2019-09-29 17:26:32 +0200 |
---|---|---|
committer | smitsohu <smitsohu@gmail.com> | 2019-09-29 17:26:32 +0200 |
commit | 9dfecbe49fbbd85714d64b18933600b0d7f88ff3 (patch) | |
tree | e511ec10f77ad78137d911a56887335e014d9992 | |
parent | Update evince (diff) | |
download | firejail-9dfecbe49fbbd85714d64b18933600b0d7f88ff3.tar.gz firejail-9dfecbe49fbbd85714d64b18933600b0d7f88ff3.tar.zst firejail-9dfecbe49fbbd85714d64b18933600b0d7f88ff3.zip |
move chroot from path based to file descriptor based mounts
-rw-r--r-- | src/firejail/fs.c | 181 |
1 files changed, 105 insertions, 76 deletions
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index ce2ca5e2a..1c91d96d4 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -27,6 +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 | #include <fcntl.h> | 32 | #include <fcntl.h> |
32 | #ifndef O_PATH | 33 | #ifndef O_PATH |
@@ -1228,122 +1229,150 @@ void fs_check_chroot_dir(const char *rootdir) { | |||
1228 | close(parentfd); | 1229 | close(parentfd); |
1229 | } | 1230 | } |
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 | |||
1231 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf | 1262 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf |
1232 | void fs_chroot(const char *rootdir) { | 1263 | void fs_chroot(const char *rootdir) { |
1233 | assert(rootdir); | 1264 | assert(rootdir); |
1234 | 1265 | ||
1266 | int parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); | ||
1267 | if (parentfd == -1) | ||
1268 | errExit("safe_fd"); | ||
1269 | |||
1235 | // mount-bind a /dev in rootdir | 1270 | // mount-bind a /dev in rootdir |
1236 | char *newdev; | ||
1237 | if (asprintf(&newdev, "%s/dev", rootdir) == -1) | ||
1238 | errExit("asprintf"); | ||
1239 | if (arg_debug) | 1271 | if (arg_debug) |
1240 | printf("Mounting /dev on %s\n", newdev); | 1272 | printf("Mounting /dev on chroot /dev\n"); |
1241 | if (mount("/dev", newdev, NULL, MS_BIND|MS_REC, NULL) < 0) | 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) | ||
1242 | errExit("mounting /dev"); | 1280 | errExit("mounting /dev"); |
1243 | free(newdev); | 1281 | free(proc); |
1282 | close(fd); | ||
1244 | 1283 | ||
1245 | // mount a new proc filesystem | 1284 | // mount a brand new proc filesystem |
1246 | char *newproc; | ||
1247 | if (asprintf(&newproc, "%s/proc", rootdir) == -1) | ||
1248 | errExit("asprintf"); | ||
1249 | if (arg_debug) | 1285 | if (arg_debug) |
1250 | printf("Mounting /proc filesystem on %s\n", newproc); | 1286 | printf("Mounting /proc filesystem on chroot /proc\n"); |
1251 | if (mount("proc", newproc, "proc", MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_REC, NULL) < 0) | 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) | ||
1252 | errExit("mounting /proc"); | 1293 | errExit("mounting /proc"); |
1253 | free(newproc); | 1294 | free(proc); |
1295 | close(fd); | ||
1254 | 1296 | ||
1255 | // x11 | 1297 | // x11 |
1256 | if (getenv("FIREJAIL_X11")) { | 1298 | if (getenv("FIREJAIL_X11")) { |
1257 | char *newx11; | ||
1258 | if (asprintf(&newx11, "%s/tmp/.X11-unix", rootdir) == -1) | ||
1259 | errExit("asprintf"); | ||
1260 | if (arg_debug) | 1299 | if (arg_debug) |
1261 | printf("Mounting /tmp/.X11-unix on %s\n", newx11); | 1300 | printf("Mounting /tmp/.X11-unix on chroot /tmp/.X11-unix\n"); |
1262 | if (mount("/tmp/.X11-unix", newx11, NULL, MS_BIND|MS_REC, NULL) < 0) | 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) | ||
1263 | errExit("mounting /tmp/.X11-unix"); | 1307 | errExit("mounting /tmp/.X11-unix"); |
1264 | free(newx11); | 1308 | free(proc); |
1309 | close(fd); | ||
1265 | } | 1310 | } |
1266 | 1311 | ||
1267 | // some older distros don't have a /run directory | 1312 | // update chroot resolv.conf |
1268 | // create one by default | 1313 | copy_resolvconf(parentfd); |
1269 | char *rundir; | 1314 | |
1270 | if (asprintf(&rundir, "%s/run", rootdir) == -1) | 1315 | // some older distros don't have a /run directory, create one by default |
1271 | errExit("asprintf"); | ||
1272 | struct stat s; | 1316 | struct stat s; |
1273 | if (lstat(rundir, &s) == 0) { | 1317 | if (fstatat(parentfd, "run", &s, AT_SYMLINK_NOFOLLOW) == 0) { |
1274 | if (S_ISLNK(s.st_mode)) { | 1318 | if (S_ISLNK(s.st_mode)) { |
1275 | fprintf(stderr, "Error: chroot /run is a symbolic link\n"); | 1319 | fprintf(stderr, "Error: chroot /run is a symbolic link\n"); |
1276 | exit(1); | 1320 | exit(1); |
1277 | } | 1321 | } |
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 | } | 1322 | } |
1287 | else { | 1323 | else if (mkdirat(parentfd, "run", 0755) == -1 && errno != EEXIST) |
1288 | // several sandboxes could race to create /run | 1324 | errExit("mkdir"); |
1289 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | 1325 | fs_check_chroot_subdir("run", parentfd, 1); |
1290 | errExit("mkdir"); | ||
1291 | ASSERT_PERMS(rundir, 0, 0, 0755); | ||
1292 | } | ||
1293 | free(rundir); | ||
1294 | 1326 | ||
1295 | // create /run/firejail directory in chroot | 1327 | // create /run/firejail directory in chroot |
1296 | if (asprintf(&rundir, "%s/run/firejail", rootdir) == -1) | 1328 | if (mkdirat(parentfd, RUN_FIREJAIL_DIR+1, 0755) == -1 && errno != EEXIST) |
1297 | errExit("asprintf"); | ||
1298 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | ||
1299 | errExit("mkdir"); | 1329 | errExit("mkdir"); |
1300 | ASSERT_PERMS(rundir, 0, 0, 0755); | ||
1301 | free(rundir); | ||
1302 | 1330 | ||
1303 | // create /run/firejail/lib directory in chroot and mount it | 1331 | // create /run/firejail/lib directory in chroot |
1304 | if (asprintf(&rundir, "%s%s", rootdir, RUN_FIREJAIL_LIB_DIR) == -1) | 1332 | if (mkdirat(parentfd, RUN_FIREJAIL_LIB_DIR+1, 0755) == -1 && errno != EEXIST) |
1305 | errExit("asprintf"); | ||
1306 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | ||
1307 | errExit("mkdir"); | 1333 | errExit("mkdir"); |
1308 | ASSERT_PERMS(rundir, 0, 0, 0755); | 1334 | // mount lib directory into the chroot |
1309 | if (mount(RUN_FIREJAIL_LIB_DIR, rundir, NULL, MS_BIND|MS_REC, NULL) < 0) | 1335 | fd = openat(parentfd, RUN_FIREJAIL_LIB_DIR+1, O_PATH|O_DIRECTORY|O_CLOEXEC); |
1310 | errExit("mount bind"); | 1336 | if (fd == -1) |
1311 | free(rundir); | 1337 | errExit("open"); |
1312 | 1338 | if (asprintf(&proc, "/proc/self/fd/%d", fd) == -1) | |
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"); | 1339 | errExit("asprintf"); |
1316 | if (mkdir(rundir, 0755) == -1 && errno != EEXIST) | 1340 | if (mount(RUN_FIREJAIL_LIB_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) |
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"); | 1341 | errExit("mount bind"); |
1321 | free(rundir); | 1342 | free(proc); |
1343 | close(fd); | ||
1322 | 1344 | ||
1323 | // copy /etc/resolv.conf in chroot directory | 1345 | // create /run/firejail/mnt directory in chroot |
1324 | char *fname; | 1346 | if (mkdirat(parentfd, RUN_MNT_DIR+1, 0755) == -1 && errno != EEXIST) |
1325 | if (asprintf(&fname, "%s/etc/resolv.conf", rootdir) == -1) | 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) | ||
1326 | errExit("asprintf"); | 1353 | errExit("asprintf"); |
1327 | if (arg_debug) | 1354 | if (mount(RUN_MNT_DIR, proc, NULL, MS_BIND|MS_REC, NULL) < 0) |
1328 | printf("Updating /etc/resolv.conf in %s\n", fname); | 1355 | errExit("mount bind"); |
1329 | unlink(fname); | 1356 | free(proc); |
1330 | if (copy_file("/etc/resolv.conf", fname, 0, 0, 0644) == -1) // root needed | 1357 | close(fd); |
1331 | fwarning("/etc/resolv.conf not initialized\n"); | ||
1332 | free(fname); | ||
1333 | 1358 | ||
1334 | // chroot into the new directory | ||
1335 | #ifdef HAVE_GCOV | 1359 | #ifdef HAVE_GCOV |
1336 | __gcov_flush(); | 1360 | __gcov_flush(); |
1337 | #endif | 1361 | #endif |
1338 | // mount the chroot dir on top of /run/firejail/mnt/oroot in order to reuse the apparmor rules for overlay | 1362 | // create /run/firejail/mnt/oroot |
1339 | // and chroot into this new directory | ||
1340 | if (arg_debug) | ||
1341 | printf("Chrooting into %s\n", rootdir); | ||
1342 | char *oroot = RUN_OVERLAY_ROOT; | 1363 | char *oroot = RUN_OVERLAY_ROOT; |
1343 | if (mkdir(oroot, 0755) == -1) | 1364 | if (mkdir(oroot, 0755) == -1) |
1344 | errExit("mkdir"); | 1365 | errExit("mkdir"); |
1345 | if (mount(rootdir, oroot, NULL, MS_BIND|MS_REC, NULL) < 0) | 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) | ||
1346 | errExit("mounting rootdir oroot"); | 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); | ||
1347 | if (chroot(oroot) < 0) | 1376 | if (chroot(oroot) < 0) |
1348 | errExit("chroot"); | 1377 | errExit("chroot"); |
1349 | 1378 | ||