Skip to content

refactor(images): split OCI extractor and fix containment bugs#446

Merged
DorianZheng merged 3 commits into
mainfrom
refactor/oci-extractor-harden
Apr 23, 2026
Merged

refactor(images): split OCI extractor and fix containment bugs#446
DorianZheng merged 3 commits into
mainfrom
refactor/oci-extractor-harden

Conversation

@DorianZheng

Copy link
Copy Markdown
Member

Summary

  • Split archive/tar.rs (1.7k lines, 40 tests) into focused modules — compression, metadata, extractor, verifier, and safe_root/{mod,path,linux,fallback,tests}. Free functions (extract_layer_tarball_streaming, verify_diff_id, apply_oci_layer) are replaced with LayerExtractor / LayerVerifier structs; storage.rs, blob_source.rs, object.rs, and rootfs/builder.rs use them directly.
  • Introduce SafeRoot as the single containment boundary. On Linux every mutation flows through pathrs::Root (kernel-atomic openat2(RESOLVE_IN_ROOT)). On other platforms, a lexical fallback scrubs escape symlinks in the ancestor chain before each create_dir_all.
  • Fix four audit findings, each with a regression test:
    • unix_time panic on crafted mtime — saturate at i64::MAX seconds before UNIX_EPOCH + Duration::from_secs(..).
    • Whiteout traversal through pre-existing escape symlinks — handle_whiteout / apply_opaque_whiteout route through SafeRoot::{ensure_parent, remove_nofollow}, scrubbing ancestry before deletion.
    • SafeRoot::create_hardlink accepted target_rel with .. underflow on the fallback backend — normalize both paths, reject on escape.
    • apply_permissions_and_times / apply_ownership mutated shared inode via hardlink entries — skip for EntryType::Link.
  • Make build.rs self-sign the embedded boxlite-shim on macOS so cargo test -p boxlite's implicit shim rebuild no longer drops the hypervisor entitlement and breaks every VM integration test.

Test plan

  • cargo test -p boxlite --lib images::archive — 44/44 pass (GHSA-f396-4rp4-7v2j regression + four new bug reproducers)
  • cargo test -p boxlite --features krun,gvproxy --test zygote_integration — previously-failing zygote tests pass
  • cargo clippy -p boxlite --tests -- -D warnings clean
  • cargo fmt --check clean
  • Full pre-push hook (make test) green
  • Manual smoke: pull a real OCI image and run a box end-to-end

Split `archive/tar.rs` into focused modules and replace free functions
with structs that call sites interact with directly:

  archive/
  ├── compression.rs   TarballReader::open (gzip auto-detect)
  ├── metadata.rs      EntryMetadata + builder
  ├── extractor.rs     LayerExtractor
  ├── verifier.rs      LayerVerifier
  ├── override_stat.rs (unchanged)
  ├── time.rs          (unchanged)
  └── safe_root/       containment: pathrs on Linux, scrub+std fallback

Call sites in storage.rs, blob_source.rs, object.rs and rootfs/builder.rs
now use `LayerExtractor::new(dest).extract_tarball(..)` and
`LayerVerifier::new(diff_id)?.verify_tarball(..)` directly.

Introduces SafeRoot as the single containment boundary. On Linux every
op routes through pathrs::Root (kernel-atomic openat2 RESOLVE_IN_ROOT).
On other platforms, a lexical fallback scrubs escape symlinks in the
ancestor chain before each create_dir_all.

Fixes four audit findings, each with a regression test:

* unix_time panic on crafted mtime: saturate at i64::MAX seconds so
  `UNIX_EPOCH + Duration::from_secs(..)` can't overflow on a tar header
  near u64::MAX.
* whiteout traversal through pre-existing escape symlinks:
  handle_whiteout and apply_opaque_whiteout now route through
  SafeRoot::{ensure_parent, remove_nofollow} so ancestry escape
  symlinks are scrubbed before deletion.
* SafeRoot::create_hardlink accepted target_rel containing `..`
  underflow on the fallback backend, letting fs::hard_link resolve
  outside the extraction root: normalize both args, reject on escape.
* apply_permissions_and_times / apply_ownership mutated the target's
  shared inode when processing hardlink entries: skip for
  EntryType::Link.

Tests: 44/44 archive tests pass, including the GHSA-f396-4rp4-7v2j
regression plus the four new reproducers. clippy clean, fmt clean.
…lement

`cargo test -p boxlite` implicitly rebuilds the boxlite-shim bin target,
which wipes the ad-hoc signature that scripts/build/sign.sh puts on it.
The copy that build.rs makes into the runtime dir then embeds an
unsigned shim, and every VM-dependent integration test fails with
"Hypervisor.framework access denied" — including the pre-push hook's
zygote_integration suite.

Make the embed step self-sufficient: after copying boxlite-shim into
the runtime dir on macOS, codesign it ad-hoc with
com.apple.security.hypervisor + disable-library-validation. Entitlements
are written to a sibling plist since codesign's --entitlements flag
needs a real file path.
… rustix

CI clippy on Linux failed with "cannot find module or crate `rustix`"
because safe_root::linux used `rustix::fs::makedev` without adding
`rustix` as a direct dependency. Swap to `libc::makedev`, which is
already a dependency and returns the same `u64` that
`pathrs::InodeType::{CharacterDevice, BlockDevice}` expects
(`rustix_fs::Dev = u64` on Linux). No behavior change, just fewer
transitive-crate assumptions.
@DorianZheng DorianZheng merged commit b68ba0f into main Apr 23, 2026
30 checks passed
@DorianZheng DorianZheng deleted the refactor/oci-extractor-harden branch April 23, 2026 00:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant