172 const std::string& worker_path,
173 const std::string& input_path,
178 if (!fs::exists(worker_path) || !fs::is_regular_file(worker_path)) {
183 if (!fs::exists(input_path) || !fs::is_regular_file(input_path)) {
188 char tmp_template[] =
"/tmp/bastionguard-archive-XXXXXX";
189 char* tmp_dir = ::mkdtemp(tmp_template);
195 const fs::path sandbox_root(tmp_dir);
196 const fs::path work_dir = sandbox_root /
"work";
198 fs::create_directories(work_dir, ec);
201 fs::remove_all(sandbox_root, ec);
205 int out_pipe[2] = {-1, -1};
206 int err_pipe[2] = {-1, -1};
208 if (::pipe(out_pipe) != 0 || ::pipe(err_pipe) != 0) {
210 for (
int fd : {out_pipe[0], out_pipe[1], err_pipe[0], err_pipe[1]}) {
211 if (fd >= 0) ::close(fd);
213 fs::remove_all(sandbox_root, ec);
217 const pid_t pid = ::fork();
220 ::close(out_pipe[0]);
221 ::close(out_pipe[1]);
222 ::close(err_pipe[0]);
223 ::close(err_pipe[1]);
224 fs::remove_all(sandbox_root, ec);
229 ::close(out_pipe[0]);
230 ::close(err_pipe[0]);
232 ::dup2(out_pipe[1], STDOUT_FILENO);
233 ::dup2(err_pipe[1], STDERR_FILENO);
235 ::close(out_pipe[1]);
236 ::close(err_pipe[1]);
240 std::vector<std::string> args = {
247 "--unshare-cgroup-try",
250 "--ro-bind",
"/usr",
"/usr",
251 "--ro-bind",
"/bin",
"/bin",
252 "--ro-bind",
"/lib",
"/lib",
253 "--ro-bind",
"/lib64",
"/lib64",
256 "--ro-bind", input_path,
"/work/input.zip",
257 "--ro-bind", worker_path,
"/worker",
259 "--setenv",
"HOME",
"/tmp",
260 "--setenv",
"TMPDIR",
"/tmp",
265 std::vector<char*> argv;
266 argv.reserve(args.size() + 1);
267 for (
auto& s : args) {
268 argv.push_back(s.data());
270 argv.push_back(
nullptr);
272 ::execvp(
"bwrap", argv.data());
278 ::close(out_pipe[1]);
279 ::close(err_pipe[1]);
288 const auto start = std::chrono::steady_clock::now();
289 bool child_done =
false;
292 bool out_eof =
false;
293 bool err_eof =
false;
295 const int effective_timeout = std::max(1, timeout_seconds);
300 const pid_t w = ::waitpid(pid, &status, WNOHANG);
303 }
else if (w < 0 && errno != EINTR) {
306 ::kill(pid, SIGKILL);
307 ::waitpid(pid, &status, 0);
313 const auto now = std::chrono::steady_clock::now();
315 std::chrono::duration_cast<std::chrono::seconds>(now - start).count();
316 if (elapsed >= effective_timeout && (!child_done || !out_eof || !err_eof)) {
321 ::kill(pid, SIGKILL);
322 ::waitpid(pid, &status, 0);
329 struct pollfd fds[2];
331 if (!out_eof) { fds[nfds].fd = out_pipe[0]; fds[nfds].events = POLLIN; ++nfds; }
332 if (!err_eof) { fds[nfds].fd = err_pipe[0]; fds[nfds].events = POLLIN; ++nfds; }
338 const int poll_timeout_ms = child_done ? 100 : 50;
339 const int pr = ::poll(fds, nfds, poll_timeout_ms);
342 for (
int i = 0; i < nfds; ++i) {
343 if (!(fds[i].revents & (POLLIN | POLLHUP | POLLERR)))
continue;
345 bool eof_here =
false;
346 if (fds[i].fd == out_pipe[0]) {
348 if (eof_here) out_eof =
true;
349 }
else if (fds[i].fd == err_pipe[0]) {
351 if (eof_here) err_eof =
true;
359 if (child_done && (out_eof || nfds == 0) && (err_eof || nfds == 0)) {
365 if (!child_done && nfds == 0) {
366 std::this_thread::sleep_for(std::chrono::milliseconds(50));
374 ::close(out_pipe[0]);
375 ::close(err_pipe[0]);
379 if (WIFEXITED(status)) {
381 }
else if (WIFSIGNALED(status)) {
382 result.
exit_code = 128 + WTERMSIG(status);
386 fs::remove_all(sandbox_root, ec);
391 const std::string& worker_path,
392 const std::string& input_path,
399 if (!exec.launched) {
401 r.sandbox_failed =
true;
402 r.reason_code =
"ARCHIVE_SANDBOX_LAUNCH_FAILED";
403 r.detail = exec.stderr_text;
407 if (exec.output_truncated) {
409 r.sandbox_failed =
true;
410 r.reason_code =
"ARCHIVE_SANDBOX_OUTPUT_LIMIT";
411 r.detail =
"archive worker exceeded output limit";
415 if (exec.timed_out) {
417 r.sandbox_timeout =
true;
418 r.reason_code =
"ARCHIVE_SANDBOX_TIMEOUT";
419 r.detail =
"archive worker timeout";
420 r.raw_json = exec.stdout_text;
424 if (!exec.exited || exec.exit_code != 0) {
426 r.sandbox_failed =
true;
427 r.reason_code =
"ARCHIVE_SANDBOX_ANALYSIS_FAILED";
428 r.detail = exec.stderr_text.empty() ?
"archive worker failed" : exec.stderr_text;
429 r.raw_json = exec.stdout_text;
434 r.raw_json = exec.stdout_text;
436 r.stored_size_mismatch =
json_has_true(exec.stdout_text,
"stored_size_mismatch");
437 r.header_mismatch =
json_has_true(exec.stdout_text,
"header_mismatch");
438 r.invalid_offset =
json_has_true(exec.stdout_text,
"invalid_offset");
439 r.overlapping_entries =
json_has_true(exec.stdout_text,
"overlapping_entries");
440 r.nested_archive =
json_has_true(exec.stdout_text,
"nested_archive");
441 r.risk_score =
json_read_int(exec.stdout_text,
"risk_score", 0);
446 if (risk ==
"EVASIVE") {
448 }
else if (risk ==
"MALFORMED") {
450 }
else if (risk ==
"SUSPICIOUS") {