diff options
-rw-r--r-- | src/firejail/fs.c | 82 | ||||
-rw-r--r-- | src/firejail/main.c | 2 |
2 files changed, 50 insertions, 34 deletions
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index bd71a6912..0080f3de2 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -1148,24 +1148,33 @@ void fs_overlayfs(void) { | |||
1148 | 1148 | ||
1149 | 1149 | ||
1150 | #ifdef HAVE_CHROOT | 1150 | #ifdef HAVE_CHROOT |
1151 | // return 1 if error | 1151 | // exit if error |
1152 | void fs_check_chroot_dir(const char *rootdir) { | 1152 | void fs_check_chroot_dir(const char *rootdir) { |
1153 | EUID_ASSERT(); | 1153 | EUID_ASSERT(); |
1154 | assert(rootdir); | 1154 | assert(rootdir); |
1155 | struct stat s; | 1155 | struct stat s; |
1156 | char *name; | 1156 | int fd = -1; |
1157 | int parentfd = -1; | ||
1157 | 1158 | ||
1158 | if (strcmp(rootdir, "/tmp") == 0 || strcmp(rootdir, "/var/tmp") == 0) { | 1159 | char *overlay; |
1159 | fprintf(stderr, "Error: invalid chroot directory\n"); | 1160 | if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) |
1161 | errExit("asprintf"); | ||
1162 | if (strncmp(rootdir, overlay, strlen(overlay)) == 0) { | ||
1163 | fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); | ||
1160 | exit(1); | 1164 | exit(1); |
1161 | } | 1165 | } |
1166 | free(overlay); | ||
1162 | 1167 | ||
1163 | // rootdir has to be owned by root and is not allowed to be world-writable | 1168 | // fails if there is any symlink or if rootdir is not a directory |
1164 | // we checked already if it is a directory | 1169 | parentfd = safe_fd(rootdir, O_PATH|O_DIRECTORY|O_NOFOLLOW|O_CLOEXEC); |
1165 | if (stat(rootdir, &s) != 0) { | 1170 | if (parentfd == -1) { |
1166 | fprintf(stderr, "Error: cannot find chroot directory\n"); | 1171 | fprintf(stderr, "Error: invalid chroot directory %s\n", rootdir); |
1167 | exit(1); | 1172 | exit(1); |
1168 | } | 1173 | } |
1174 | // rootdir has to be owned by root and is not allowed to be world-writable; | ||
1175 | // this also excludes /tmp, /var/tmp and such | ||
1176 | if (fstat(parentfd, &s) == -1) | ||
1177 | errExit("fstat"); | ||
1169 | if (s.st_uid != 0) { | 1178 | if (s.st_uid != 0) { |
1170 | fprintf(stderr, "Error: chroot directory should be owned by root\n"); | 1179 | fprintf(stderr, "Error: chroot directory should be owned by root\n"); |
1171 | exit(1); | 1180 | exit(1); |
@@ -1176,64 +1185,69 @@ void fs_check_chroot_dir(const char *rootdir) { | |||
1176 | } | 1185 | } |
1177 | 1186 | ||
1178 | // check /dev | 1187 | // check /dev |
1179 | if (asprintf(&name, "%s/dev", rootdir) == -1) | 1188 | fd = openat(parentfd, "dev", O_PATH|O_CLOEXEC); |
1180 | errExit("asprintf"); | 1189 | if (fd == -1) { |
1181 | if (stat(name, &s) == -1) { | ||
1182 | fprintf(stderr, "Error: cannot find /dev in chroot directory\n"); | 1190 | fprintf(stderr, "Error: cannot find /dev in chroot directory\n"); |
1183 | exit(1); | 1191 | exit(1); |
1184 | } | 1192 | } |
1193 | if (fstat(fd, &s) == -1) | ||
1194 | errExit("fstat"); | ||
1185 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | 1195 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { |
1186 | fprintf(stderr, "Error: chroot /dev should be a directory owned by root\n"); | 1196 | fprintf(stderr, "Error: chroot /dev should be a directory owned by root\n"); |
1187 | exit(1); | 1197 | exit(1); |
1188 | } | 1198 | } |
1189 | free(name); | 1199 | close(fd); |
1190 | 1200 | ||
1191 | // check /var/tmp | 1201 | // check /var/tmp |
1192 | if (asprintf(&name, "%s/var/tmp", rootdir) == -1) | 1202 | fd = openat(parentfd, "var/tmp", O_PATH|O_CLOEXEC); |
1193 | errExit("asprintf"); | 1203 | if (fd == -1) { |
1194 | if (stat(name, &s) == -1) { | ||
1195 | fprintf(stderr, "Error: cannot find /var/tmp in chroot directory\n"); | 1204 | fprintf(stderr, "Error: cannot find /var/tmp in chroot directory\n"); |
1196 | exit(1); | 1205 | exit(1); |
1197 | } | 1206 | } |
1207 | if (fstat(fd, &s) == -1) | ||
1208 | errExit("fstat"); | ||
1198 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | 1209 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { |
1199 | fprintf(stderr, "Error: chroot /var/tmp should be a directory owned by root\n"); | 1210 | fprintf(stderr, "Error: chroot /var/tmp should be a directory owned by root\n"); |
1200 | exit(1); | 1211 | exit(1); |
1201 | } | 1212 | } |
1202 | free(name); | 1213 | close(fd); |
1203 | 1214 | ||
1204 | // check /proc | 1215 | // check /proc |
1205 | if (asprintf(&name, "%s/proc", rootdir) == -1) | 1216 | fd = openat(parentfd, "proc", O_PATH|O_CLOEXEC); |
1206 | errExit("asprintf"); | 1217 | if (fd == -1) { |
1207 | if (stat(name, &s) == -1) { | ||
1208 | fprintf(stderr, "Error: cannot find /proc in chroot directory\n"); | 1218 | fprintf(stderr, "Error: cannot find /proc in chroot directory\n"); |
1209 | exit(1); | 1219 | exit(1); |
1210 | } | 1220 | } |
1221 | if (fstat(fd, &s) == -1) | ||
1222 | errExit("fstat"); | ||
1211 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | 1223 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { |
1212 | fprintf(stderr, "Error: chroot /proc should be a directory owned by root\n"); | 1224 | fprintf(stderr, "Error: chroot /proc should be a directory owned by root\n"); |
1213 | exit(1); | 1225 | exit(1); |
1214 | } | 1226 | } |
1215 | free(name); | 1227 | close(fd); |
1216 | 1228 | ||
1217 | // check /tmp | 1229 | // check /tmp |
1218 | if (asprintf(&name, "%s/tmp", rootdir) == -1) | 1230 | fd = openat(parentfd, "tmp", O_PATH|O_CLOEXEC); |
1219 | errExit("asprintf"); | 1231 | if (fd == -1) { |
1220 | if (stat(name, &s) == -1) { | ||
1221 | fprintf(stderr, "Error: cannot find /tmp in chroot directory\n"); | 1232 | fprintf(stderr, "Error: cannot find /tmp in chroot directory\n"); |
1222 | exit(1); | 1233 | exit(1); |
1223 | } | 1234 | } |
1235 | if (fstat(fd, &s) == -1) | ||
1236 | errExit("fstat"); | ||
1224 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | 1237 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { |
1225 | fprintf(stderr, "Error: chroot /tmp should be a directory owned by root\n"); | 1238 | fprintf(stderr, "Error: chroot /tmp should be a directory owned by root\n"); |
1226 | exit(1); | 1239 | exit(1); |
1227 | } | 1240 | } |
1228 | free(name); | 1241 | close(fd); |
1229 | 1242 | ||
1230 | // check /etc | 1243 | // check /etc |
1231 | if (asprintf(&name, "%s/etc", rootdir) == -1) | 1244 | fd = openat(parentfd, "etc", O_PATH|O_CLOEXEC); |
1232 | errExit("asprintf"); | 1245 | if (fd == -1) { |
1233 | if (stat(name, &s) == -1) { | ||
1234 | fprintf(stderr, "Error: cannot find /etc in chroot directory\n"); | 1246 | fprintf(stderr, "Error: cannot find /etc in chroot directory\n"); |
1235 | exit(1); | 1247 | exit(1); |
1236 | } | 1248 | } |
1249 | if (fstat(fd, &s) == -1) | ||
1250 | errExit("fstat"); | ||
1237 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | 1251 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { |
1238 | fprintf(stderr, "Error: chroot /etc should be a directory owned by root\n"); | 1252 | fprintf(stderr, "Error: chroot /etc should be a directory owned by root\n"); |
1239 | exit(1); | 1253 | exit(1); |
@@ -1242,7 +1256,7 @@ void fs_check_chroot_dir(const char *rootdir) { | |||
1242 | fprintf(stderr, "Error: chroot /etc should not be world-writable\n"); | 1256 | fprintf(stderr, "Error: chroot /etc should not be world-writable\n"); |
1243 | exit(1); | 1257 | exit(1); |
1244 | } | 1258 | } |
1245 | free(name); | 1259 | close(fd); |
1246 | 1260 | ||
1247 | // there should be no checking on <chrootdir>/etc/resolv.conf | 1261 | // there should be no checking on <chrootdir>/etc/resolv.conf |
1248 | // the file is replaced with the real /etc/resolv.conf anyway | 1262 | // the file is replaced with the real /etc/resolv.conf anyway |
@@ -1274,19 +1288,21 @@ void fs_check_chroot_dir(const char *rootdir) { | |||
1274 | 1288 | ||
1275 | // check x11 socket directory | 1289 | // check x11 socket directory |
1276 | if (getenv("FIREJAIL_X11")) { | 1290 | if (getenv("FIREJAIL_X11")) { |
1277 | char *name; | 1291 | fd = openat(parentfd, "tmp/.X11-unix", O_PATH|O_CLOEXEC); |
1278 | if (asprintf(&name, "%s/tmp/.X11-unix", rootdir) == -1) | 1292 | if (fd == -1) { |
1279 | errExit("asprintf"); | ||
1280 | if (stat(name, &s) == -1) { | ||
1281 | fprintf(stderr, "Error: cannot find /tmp/.X11-unix in chroot directory\n"); | 1293 | fprintf(stderr, "Error: cannot find /tmp/.X11-unix in chroot directory\n"); |
1282 | exit(1); | 1294 | exit(1); |
1283 | } | 1295 | } |
1296 | if (fstat(fd, &s) == -1) | ||
1297 | errExit("fstat"); | ||
1284 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { | 1298 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) { |
1285 | fprintf(stderr, "Error: chroot /tmp/.X11-unix should be a directory owned by root\n"); | 1299 | fprintf(stderr, "Error: chroot /tmp/.X11-unix should be a directory owned by root\n"); |
1286 | exit(1); | 1300 | exit(1); |
1287 | } | 1301 | } |
1288 | free(name); | 1302 | close(fd); |
1289 | } | 1303 | } |
1304 | |||
1305 | close(parentfd); | ||
1290 | } | 1306 | } |
1291 | 1307 | ||
1292 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf | 1308 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf |
diff --git a/src/firejail/main.c b/src/firejail/main.c index 6170806f9..b5d46808a 100644 --- a/src/firejail/main.c +++ b/src/firejail/main.c | |||
@@ -1507,7 +1507,7 @@ int main(int argc, char **argv) { | |||
1507 | 1507 | ||
1508 | // check chroot dirname exists, don't allow "--chroot=/" | 1508 | // check chroot dirname exists, don't allow "--chroot=/" |
1509 | char *rpath = realpath(cfg.chrootdir, NULL); | 1509 | char *rpath = realpath(cfg.chrootdir, NULL); |
1510 | if (rpath == NULL || !is_dir(rpath) || strcmp(rpath, "/") == 0) { | 1510 | if (rpath == NULL || strcmp(rpath, "/") == 0) { |
1511 | fprintf(stderr, "Error: invalid chroot directory\n"); | 1511 | fprintf(stderr, "Error: invalid chroot directory\n"); |
1512 | exit(1); | 1512 | exit(1); |
1513 | } | 1513 | } |