aboutsummaryrefslogtreecommitdiffstats
path: root/src/firejail/fs.c
diff options
context:
space:
mode:
authorLibravatar smitsohu <smitsohu@gmail.com>2019-09-29 17:58:27 +0200
committerLibravatar smitsohu <smitsohu@gmail.com>2019-09-29 17:58:27 +0200
commit94aba62229f083173cb28ffe370eaa9f3ee540b9 (patch)
treeb1a70e5ff502df77bf0ddc45e82f11547677f09a /src/firejail/fs.c
parentmove chroot from path based to file descriptor based mounts (diff)
downloadfirejail-94aba62229f083173cb28ffe370eaa9f3ee540b9.tar.gz
firejail-94aba62229f083173cb28ffe370eaa9f3ee540b9.tar.zst
firejail-94aba62229f083173cb28ffe370eaa9f3ee540b9.zip
chroot module
Diffstat (limited to 'src/firejail/fs.c')
-rw-r--r--src/firejail/fs.c284
1 files changed, 3 insertions, 281 deletions
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
701static void disable_config(void) { 701void 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
1130static 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
1157void 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
1233static 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
1263void 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
1406void fs_private_tmp(void) { 1128void fs_private_tmp(void) {
1407 // check XAUTHORITY file, KDE keeps it under /tmp 1129 // check XAUTHORITY file, KDE keeps it under /tmp