diff options
-rw-r--r-- | src/firejail/fs.c | 130 |
1 files changed, 47 insertions, 83 deletions
diff --git a/src/firejail/fs.c b/src/firejail/fs.c index 9a15d825e..13f01a51b 100644 --- a/src/firejail/fs.c +++ b/src/firejail/fs.c | |||
@@ -1127,11 +1127,36 @@ void fs_overlayfs(void) { | |||
1127 | 1127 | ||
1128 | #ifdef HAVE_CHROOT | 1128 | #ifdef HAVE_CHROOT |
1129 | // exit if error | 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 | ||
1130 | void fs_check_chroot_dir(const char *rootdir) { | 1157 | void fs_check_chroot_dir(const char *rootdir) { |
1131 | EUID_ASSERT(); | 1158 | EUID_ASSERT(); |
1132 | assert(rootdir); | 1159 | assert(rootdir); |
1133 | char *dir = EMPTY_STRING; | ||
1134 | struct stat s; | ||
1135 | 1160 | ||
1136 | char *overlay; | 1161 | char *overlay; |
1137 | if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) | 1162 | if (asprintf(&overlay, "%s/.firejail", cfg.homedir) == -1) |
@@ -1150,6 +1175,7 @@ void fs_check_chroot_dir(const char *rootdir) { | |||
1150 | } | 1175 | } |
1151 | // rootdir has to be owned by root and is not allowed to be generally writable, | 1176 | // rootdir has to be owned by root and is not allowed to be generally writable, |
1152 | // this also excludes /tmp, /var/tmp and such | 1177 | // this also excludes /tmp, /var/tmp and such |
1178 | struct stat s; | ||
1153 | if (fstat(parentfd, &s) == -1) | 1179 | if (fstat(parentfd, &s) == -1) |
1154 | errExit("fstat"); | 1180 | errExit("fstat"); |
1155 | if (s.st_uid != 0) { | 1181 | if (s.st_uid != 0) { |
@@ -1161,64 +1187,24 @@ void fs_check_chroot_dir(const char *rootdir) { | |||
1161 | exit(1); | 1187 | exit(1); |
1162 | } | 1188 | } |
1163 | 1189 | ||
1164 | // check /dev | 1190 | // check subdirectories in rootdir |
1165 | dir = "dev"; | 1191 | typedef struct { |
1166 | int fd = openat(parentfd, dir, O_PATH|O_CLOEXEC); | 1192 | char *dname; |
1167 | if (fd == -1) | 1193 | int check_writable; |
1168 | goto error1; | 1194 | } chrootsubdir; |
1169 | if (fstat(fd, &s) == -1) | 1195 | chrootsubdir dirs[] = { |
1170 | errExit("fstat"); | 1196 | {"dev", 0}, |
1171 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) | 1197 | {"etc", 1}, |
1172 | goto error2; | 1198 | {"proc", 0}, |
1173 | close(fd); | 1199 | {"tmp", 0}, |
1174 | 1200 | {"var/tmp", 0}, | |
1175 | // check /var/tmp | 1201 | {NULL, 0} |
1176 | dir = "var/tmp"; | 1202 | }; |
1177 | fd = openat(parentfd, dir, O_PATH|O_CLOEXEC); | 1203 | chrootsubdir *tmp = dirs; |
1178 | if (fd == -1) | 1204 | while (tmp->dname) { |
1179 | goto error1; | 1205 | fs_check_chroot_subdir(tmp->dname, parentfd, tmp->check_writable); |
1180 | if (fstat(fd, &s) == -1) | 1206 | tmp++; |
1181 | errExit("fstat"); | ||
1182 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) | ||
1183 | goto error2; | ||
1184 | close(fd); | ||
1185 | |||
1186 | // check /proc | ||
1187 | dir = "proc"; | ||
1188 | fd = openat(parentfd, dir, O_PATH|O_CLOEXEC); | ||
1189 | if (fd == -1) | ||
1190 | goto error1; | ||
1191 | if (fstat(fd, &s) == -1) | ||
1192 | errExit("fstat"); | ||
1193 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) | ||
1194 | goto error2; | ||
1195 | close(fd); | ||
1196 | |||
1197 | // check /tmp | ||
1198 | dir = "tmp"; | ||
1199 | fd = openat(parentfd, dir, O_PATH|O_CLOEXEC); | ||
1200 | if (fd == -1) | ||
1201 | goto error1; | ||
1202 | if (fstat(fd, &s) == -1) | ||
1203 | errExit("fstat"); | ||
1204 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) | ||
1205 | goto error2; | ||
1206 | close(fd); | ||
1207 | |||
1208 | // check /etc | ||
1209 | dir = "etc"; | ||
1210 | fd = openat(parentfd, dir, O_PATH|O_CLOEXEC); | ||
1211 | if (fd == -1) | ||
1212 | goto error1; | ||
1213 | if (fstat(fd, &s) == -1) | ||
1214 | errExit("fstat"); | ||
1215 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) | ||
1216 | goto error2; | ||
1217 | if (((S_IWGRP|S_IWOTH) & s.st_mode) != 0) { | ||
1218 | fprintf(stderr, "Error: only root user should be given write permission on chroot /etc\n"); | ||
1219 | exit(1); | ||
1220 | } | 1207 | } |
1221 | close(fd); | ||
1222 | 1208 | ||
1223 | // there should be no checking on <chrootdir>/etc/resolv.conf | 1209 | // there should be no checking on <chrootdir>/etc/resolv.conf |
1224 | // the file is replaced with the real /etc/resolv.conf anyway | 1210 | // the file is replaced with the real /etc/resolv.conf anyway |
@@ -1249,32 +1235,10 @@ void fs_check_chroot_dir(const char *rootdir) { | |||
1249 | #endif | 1235 | #endif |
1250 | 1236 | ||
1251 | // check x11 socket directory | 1237 | // check x11 socket directory |
1252 | if (getenv("FIREJAIL_X11")) { | 1238 | if (getenv("FIREJAIL_X11")) |
1253 | dir = "tmp/.X11-unix"; | 1239 | fs_check_chroot_subdir("tmp/.X11-unix", parentfd, 0); |
1254 | fd = openat(parentfd, dir, O_PATH|O_CLOEXEC); | ||
1255 | if (fd == -1) | ||
1256 | goto error1; | ||
1257 | if (fstat(fd, &s) == -1) | ||
1258 | errExit("fstat"); | ||
1259 | if (!S_ISDIR(s.st_mode) || s.st_uid != 0) | ||
1260 | goto error2; | ||
1261 | close(fd); | ||
1262 | } | ||
1263 | 1240 | ||
1264 | close(parentfd); | 1241 | close(parentfd); |
1265 | return; | ||
1266 | |||
1267 | error1: | ||
1268 | if (errno == ENOENT) | ||
1269 | fprintf(stderr, "Error: cannot find /%s in chroot directory\n", dir); | ||
1270 | else { | ||
1271 | perror("open"); | ||
1272 | fprintf(stderr, "Error: cannot open /%s in chroot directory\n", dir); | ||
1273 | } | ||
1274 | exit(1); | ||
1275 | error2: | ||
1276 | fprintf(stderr, "Error: chroot /%s should be a directory owned by root\n", dir); | ||
1277 | exit(1); | ||
1278 | } | 1242 | } |
1279 | 1243 | ||
1280 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf | 1244 | // chroot into an existing directory; mount exiting /dev and update /etc/resolv.conf |