fix(verifier): confine image archive extraction - #881
Merged
Conversation
kvinwang
force-pushed
the
codex/fix-verifier-archive-confinement
branch
from
August 4, 2026 06:42
75b591c to
5122812
Compare
5 tasks
The archive path check rejected any `Component::CurDir`, but `tar -czf out.tar.gz .` prefixes every member with `./` and 16 of the 53 images published on download.dstack.org are packed that way. Those images are already whitelisted on-chain, so the stricter check made them permanently unverifiable. `tar::Entry::unpack_in` strips `.` components and cannot escape through them, so the local check was stricter than the confinement it backs up without gaining anything. Mirror the library rule instead: reject `..`, absolute paths and prefixes, accept `.`. Split the manifest rule out into `is_flat_manifest_name`. It expresses what `prune_unlisted_image_files` and `sha256sum -c` actually need — the name must literally be a file name — instead of reusing the archive rule plus a component count. Also switch to `MultiGzDecoder`. `GzDecoder` stops at the first member of a concatenated gzip stream and reports clean EOF, so `tar::Archive` would end iteration without an error and extract the archive partially. Verification: - Both published archive shapes now extract, pass `sha256sum -c`, prune to the same 5 files, and reproduce their on-chain `os_image_hash` (`f82e55d7...`, `1ae9dbdc...`). - All 53 published manifests re-checked against `is_flat_manifest_name`: 4 flat entries each, none affected. - `cargo test -p dstack-verifier --all-features`: 18 passed. - `cargo clippy -p dstack-verifier --all-features -- -D warnings -D clippy::expect_used -D clippy::unwrap_used`: passed. - `cargo fmt --check --all`, `git diff --check`: passed.
Contributor
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (3)
dstack/verifier/src/verification.rs:507
prune_unlisted_image_filestakes the raw second field fromsha256sum.txt. If the manifest uses the*filenamebinary-mode marker format, this will treat*filenameas the literal file name and may prune the real file even thoughsha256sum -cverified it. Strip a leading*before comparing to top-level directory entry names (matching the normalization invalidate_image_manifest_paths).
let listed_files: Vec<&OsStr> = files_doc
.lines()
.flat_map(|line| line.split_whitespace().nth(1))
.map(|s| s.as_ref())
.collect();
dstack/verifier/src/verification.rs:465
sha256summanifest lines may prefix the filename with*(binary-mode marker) whichsha256sum -ctreats as metadata, not part of the path. Here the code treats the raw token as the path, so a line like... *metadata.jsonwould pass the currentis_flat_manifest_namecheck but laterprune_unlisted_image_fileswould look for a literal*metadata.jsonentry and could delete the realmetadata.json. Consider normalizing the parsed name by stripping a leading*before validating/using it.
This issue also appears on line 503 of the same file.
let name = fields
.next()
.context("image manifest entry is missing a path")?;
dstack/verifier/src/verification.rs:1254
extract_image_archiveperforms gzip decompression and tar extraction using blocking I/O and CPU work. Calling it directly insidedownload_image(an async function) can block a Tokio worker thread for large images, reducing concurrency for the verifier service. Consider running extraction insidetokio::task::spawn_blockingand awaiting the join handle.
file.flush()
.await
.context("Failed to flush image archive")?;
drop(file);
Self::extract_image_archive(&tarball_path, &extracted_dir)?;
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Verifier extracted downloaded image archives with the system
tarcommand. Archive-controlled absolute paths,..components, links, or special entries could escape or weaken the intended extraction boundary.Fix
Use the workspace's existing
flate2andtarcrates and extract each entry withtar::Entry::unpack_in.unpack_inconfinement failure as an error.sha256sum -cverification and cache pruning.This intentionally avoids adding a custom checksum parser, hashing implementation, or recursive cache layout.
Verification
cargo test -p dstack-verifier: 16 passed.cargo clippy -p dstack-verifier -- -D warnings -D clippy::expect_used -D clippy::unwrap_used --allow unused_variables: passed.cargo fmt --check --all: passed.git diff --check: passed.