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/firejail/fs.c | |
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/firejail/fs.c')
-rw-r--r-- | src/firejail/fs.c | 284 |
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 |
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 |