Skip to content

feat(sandbox): support rootfs tar as --from source for VM driver - #2863

Open
feloy wants to merge 5 commits into
NVIDIA:mainfrom
feloy:docker-tar
Open

feat(sandbox): support rootfs tar as --from source for VM driver#2863
feloy wants to merge 5 commits into
NVIDIA:mainfrom
feloy:docker-tar

Conversation

@feloy

Copy link
Copy Markdown
Contributor

Summary

  • Add rootfs tar archive (.tar, .tar.gz, .tgz) as a new --from source for VM-driver sandboxes, enabling daemon-free sandbox creation from flat filesystem archives produced by docker export, podman export, or buildah mount + tar
  • CLI detects the archive by file extension, validates the gateway is local and uses the VM compute driver, and passes the path via driver_config
  • VM driver copies the tar into its staging area and feeds it into the existing rootfs extraction and ext4 disk creation pipeline, with mtime-based caching

Related Issue

Closes#2175

Changes

CLI (crates/openshell-cli/)

  • New ResolvedSource::RootfsTar variant in resolve_from() with extension-based detection
  • validate_rootfs_tar_source() checks local-gateway and VM-driver constraints via GetGatewayInfo RPC
  • rootfs_tar_driver_config() and merge_driver_config() encode the tar path into driver_config
  • Updated --from help text and error messages

VM driver (crates/openshell-driver-vm/)

  • rootfs_tar_path field on VmSandboxDriverConfig
  • ensure_prepared_rootfs_tar_disk() with double-checked cache locking and mtime-based identity
  • Updated validate_sandbox(), create_sandbox(), prepare_runtime_images(), and reconciliation to accept rootfs tar as an alternative to image references
  • Extracted bootstrap_image_ref_default() for reuse in rootfs tar and reconciliation paths

Docs (docs/sandboxes/manage-sandboxes.mdx)

  • Updated --from documentation with rootfs tar example and VM-driver constraint

Tests

  • 6 unit tests in run.rs: extension detection for .tar/.tar.gz/.tgz, missing archive rejection, filename_looks_like_rootfs_tar coverage
  • E2E test (e2e/rust/tests/rootfs_tar.rs): builds a Docker image, exports a flat rootfs tar, creates a VM sandbox from it, and verifies a marker file

Testing

  • mise run pre-commit passes
  • Unit tests added/updated
  • E2E tests added/updated

Checklist

  • Follows conventional commit format
  • No proto schema changes required (driver_config carries the tar path)
  • Scoped to VM driver only — Docker and Podman drivers unaffected
  • Local-gateway constraint enforced (same as Dockerfile sources)
  • User-facing docs updated
  • DCO sign-off included

@copy-pr-bot

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@johntmyersjohntmyers left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

The VM rootfs-tar feature is project-valid and documented, but this head has three blocking implementation issues: the new raw host-path field crosses the driver trust boundary, the CLI encodes that field outside the required vm configuration object, and the cache key can collide or remain stale.

Action required: @feloy, please address GATOR-28b5152e-01, GATOR-28b5152e-02, and GATOR-28b5152e-03, including the requested regression coverage.

Blocking findings:

  • GATOR-28b5152e-01: caller-controlled host paths reach privileged VM-driver filesystem reads.
  • GATOR-28b5152e-02: the CLI-generated rootfs path is discarded by driver-config selection.
  • GATOR-28b5152e-03: rootfs archive cache identities are collision-prone, stale at sub-second updates, and unbounded.

Carried findings:

  • None
Gator metadata
  • Validation: Project-valid implementation of maintainer-supported issue #2175, scoped to local VM gateways.
  • Docs: Fern sandbox documentation updated for the direct UX change.
  • Checks: Current required Branch Checks and Helm Lint have not run and remain pending.
  • E2E: test:e2e is required for VM sandbox lifecycle behavior but is not dispatched while review blockers remain.
  • Head SHA: 28b5152ee0e3bc377b555cdbb89faa7cc8bc1f5c
  • Base SHA: 40d1b4866691be03b5617089739c7f2325487479
  • Merge base SHA: 40d1b4866691be03b5617089739c7f2325487479
  • Patch ID: 4f052d6ec908bc7bf57cfeb49f381929c826deee
  • Gator payload: 7
  • Review mode: initial
  • Previous reviewed SHA: none
  • Review budget exhausted: no
  • Maintainer decision required: no
  • Next state: gator:in-review

("image_identity".to_string(), cache_identity.clone()),
]),
);
if let Err(err) = tokio::fs::copy(tar_path, &rootfs_archive).await {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Critical — GATOR-28b5152e-01 · Caller-controlled host paths reach the VM driver

Summary: An authenticated sandbox creator can set driver_config.vm.rootfs_tar_path directly, bypassing the CLI-only locality check. The privileged VM driver then copies that host path; a special or oversized file can exhaust gateway storage, and a readable host tar can be imported into the requester’s sandbox.

Fix: Replace the caller-controlled raw path with a server-created trusted staging reference. At the driver boundary, canonicalize it, require a regular file inside that staging root, enforce a configured size limit, and reject other paths before I/O.

Verify: Submit direct API requests using /dev/zero and a readable path outside staging; both must be rejected before metadata or copy operations, while a staged regular archive succeeds.

Agent context
  • Agent path:CreateSandbox driver config → VmSandboxDriverConfigtokio::fs::copy
  • Ownership: This PR adds the accepted path field and privileged copy sink.
  • Location:crates/openshell-driver-vm/src/driver.rs:2636

/// Build a `driver_config` struct carrying the rootfs tar path for the VM driver.
fn rootfs_tar_driver_config(tar_path: &Path) -> Result<prost_types::Struct> {
let fields = serde_json::Map::from_iter([(
"rootfs_tar_path".to_string(),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-28b5152e-02 · Rootfs configuration is dropped before the VM driver

Summary: An operator using the documented --from ./rootfs.tar flow gets a top-level rootfs_tar_path, but the server forwards only driver_config.vm. The new field is therefore discarded, and the image-less VM template fails validation instead of creating a sandbox.

Fix: Encode the generated value as {"vm": {"rootfs_tar_path": path}} and deep-merge it into an existing vm object so other VM settings remain intact.

Verify: Pass the CLI-produced template through server driver selection and assert the VM driver receives both rootfs_tar_path and an existing VM field; the included end-to-end rootfs-tar scenario must then provision successfully.

Agent context
  • Ownership: This helper is the new feature’s only archive-path transport.
  • Location:crates/openshell-cli/src/run.rs:1288

.duration_since(std::time::SystemTime::UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let tar_identity = format!("rootfs-tar:{}:{mtime}", tar_path.display());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

Warning — GATOR-28b5152e-03 · Rootfs archive cache keys can collide or remain stale

Summary: Archive identity combines an unbounded path with mtime rounded to seconds, then maps all punctuation to -. Distinct paths such as /tmp/a/b.tar and /tmp/a-b.tar can reuse the same prepared disk, same-second rewrites can keep stale contents, and long valid paths can exceed filesystem component limits.

Fix: Use a fixed-length cryptographic hash of canonical path bytes, file length, and the full available modified timestamp. Re-stat after copying and retry or reject if those attributes changed during staging.

Verify: Cover separator-colliding paths with equal mtimes, a same-second rewrite, and a long canonical path; each distinct archive version must select a distinct bounded key and produce its own filesystem.

Agent context
  • Ownership: This PR newly feeds arbitrary filesystem paths and second-truncated metadata into the existing sanitizer.
  • Location:crates/openshell-driver-vm/src/driver.rs:2589

@johntmyersjohntmyers added gator:in-review Gator is reviewing or awaiting PR review feedback gator:blocked Gator is blocked by process or repository gates and removed gator:in-review Gator is reviewing or awaiting PR review feedback gator:blocked Gator is blocked by process or repository gates labels Aug 25, 2026
Accept flat rootfs tar archives (.tar, .tar.gz, .tgz) via the --from
flag for VM-backed gateways. The CLI detects the archive extension,
validates that the gateway uses the VM compute driver, and passes the
tar path through driver_config. The VM driver copies the tar into its
staging area and feeds it into the existing rootfs extraction and ext4
disk creation pipeline, skipping the container image pull/export steps.
ClosesNVIDIA#2175
Signed-off-by: Philippe Martin <phmartin@redhat.com>
The rootfs_tar_path field in driver_config was passed from the API
caller directly to tokio::fs::copy without validation. An authenticated
user bypassing the CLI could supply arbitrary host paths (e.g.
/dev/zero for disk exhaustion, or readable host files for data
exfiltration).
Introduce a trusted staging directory that the VM driver creates on
startup and advertises via GetCapabilities. The CLI now copies the tar
into the staging directory before creating the sandbox, and the driver
validates that the received path is a regular file inside the staging
root and within a configurable size limit (default 10 GiB) before any
I/O.
New VmDriverConfig options:
- rootfs_tar_staging_dir: override the staging directory
(default: <state_dir>/rootfs-tar-staging)
- rootfs_tar_max_bytes: override the size limit (default: 10 GiB)
Addresses GATOR-28b5152e-01.
Signed-off-by: Philippe Martin <phmartin@redhat.com>

@johntmyersjohntmyers left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

Thanks @feloy, I checked the new staging-boundary validation against the three existing findings. The new head narrows arbitrary host-path access, but the documented CLI still copies archives into shared persistent staging before enforcing the configured limit, still encodes the rootfs path outside driver_config.vm, and leaves the cache identity and copy-consistency defects unchanged. No new blockers were added.

Action required: @feloy, please finish the three carried obligations and their requested regression coverage.

Blocking findings:

  • No new blocking findings.

Carried findings:

  • GATOR-28b5152e-01: use request-bound server-controlled staging, enforce the byte limit while receiving the archive, prevent cross-request path selection, and clean up staged data.
  • GATOR-28b5152e-02: nest rootfs_tar_path under driver_config.vm, deep-merge existing VM settings, and cover the CLI-to-driver transport.
  • GATOR-28b5152e-03: use a fixed-length collision-resistant version identity and reject or retry if the archive changes during copy.
Gator metadata
  • Validation: Project-valid implementation of maintainer-supported issue #2175, scoped to local VM gateways.
  • Docs: Fern sandbox documentation is present for the direct UX change.
  • Checks: Current-head Branch Checks and Helm Lint are pending; pipeline dispatch waits for review blockers to resolve.
  • E2E: test:e2e remains required for VM sandbox lifecycle behavior and will be dispatched after review blockers resolve.
  • Head SHA: 75e1f2be4b522e53381ab4e2a958c6dba7e031ff
  • Base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Merge base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Patch ID: 2c200e7c9e38781640f7fbeaa9eab2919ebce81b
  • Gator payload: 7
  • Review mode: follow_up
  • Previous reviewed SHA: 28b5152ee0e3bc377b555cdbb89faa7cc8bc1f5c
  • Review budget exhausted: no
  • Maintainer decision required: no
  • Next state: gator:in-review

@johntmyersjohntmyers added gator:in-review Gator is reviewing or awaiting PR review feedback and removed gator:blocked Gator is blocked by process or repository gates labels Aug 26, 2026
… rootfs tar
Tighten the rootfs tar staging flow to address the remaining GATOR-01
obligations:
- Request-scoped staging: the CLI creates a unique per-request
subdirectory (req-<pid>) under the staging root instead of placing
files directly in the shared directory. The driver enforces that the
tar path is at depth 2 (staging_root/<subdir>/<file>), preventing
cross-request path selection.
- Size pre-check: the driver advertises rootfs_tar_max_bytes via
GetCapabilities. The CLI reads this limit and rejects oversized files
before copying, avoiding disk exhaustion in the staging directory.
- Cleanup: the driver removes the request staging subdirectory after
consuming the tar (on cache hit, copy success, or copy failure),
ensuring staged data does not persist beyond the request.
Signed-off-by: Philippe Martin <phmartin@redhat.com>

@johntmyersjohntmyers left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gator-agent

PR Review Status

Thanks @feloy, I checked the latest staging-limit, request-directory, and cleanup changes against the three existing obligations. The raw staging reference is still not request-bound or bounded while copying, the CLI transport and cache identity remain unchanged, and the new cleanup makes a successfully created rootfs-tar sandbox unable to restart or restore.

Action required: @feloy, please address GATOR-168b9210-01 and finish the three carried obligations with their requested regression coverage.

Blocking findings:

  • GATOR-168b9210-01: deleting the one-shot staged archive leaves persisted sandbox state pointing to a path that restart and restoration must canonicalize.

Carried findings:

  • GATOR-28b5152e-01: replace caller-selected staging paths with request-bound server-controlled staging, enforce the byte limit while receiving, and clean up every terminal path safely.
  • GATOR-28b5152e-02: nest rootfs_tar_path under driver_config.vm, deep-merge existing VM settings, and cover CLI-to-driver transport.
  • GATOR-28b5152e-03: use a fixed-length collision-resistant archive-version identity and reject or retry if the source changes during copy.
Gator metadata
  • Validation: Project-valid implementation of maintainer-supported issue #2175, scoped to local VM gateways.
  • Docs: Fern sandbox documentation remains present for the direct UX change.
  • Checks: Current-head Branch Checks and Helm Lint are pending; pipeline dispatch remains gated on review blockers.
  • E2E: test:e2e is required for VM sandbox lifecycle behavior but must not be dispatched while review blockers remain.
  • Head SHA: 168b9210cc70a99b003a18b18b2bead91d30f0b9
  • Base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Merge base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Patch ID: 1f7bcbdefada29ff2276cb444f62a4b8984857ae
  • Gator payload: 7
  • Review mode: follow_up
  • Previous reviewed SHA: 75e1f2be4b522e53381ab4e2a958c6dba7e031ff
  • Review budget exhausted: yes
  • Maintainer decision required: no
  • Next state: gator:in-review

Comment threadcrates/openshell-driver-vm/src/driver.rs
On restore or restart, the one-shot staged tar archive has already been
cleaned up. Reading the persisted image identity from the sandbox state
directory and resolving the cached disk path directly avoids re-accessing
the deleted staging path.
Addresses GATOR-168b9210-01.
Signed-off-by: Philippe Martin <phmartin@redhat.com>
@johntmyers

Copy link
Copy Markdown
Collaborator

gator-agent

Maintainer Convergence Decision

Thanks @feloy. I checked the new persisted-image restoration path at head 8ec3e99844bc6061e55b6c28421af9f94a676264; it fixes GATOR-168b9210-01, and that thread is now resolved. The required critical-only delta review found no newly introduced Critical defect. Three earlier obligations remain unchanged, so a maintainer convergence decision is required before testing proceeds.

Root-cause findings:

  • GATOR-28b5152e-01: still open — staging is selected through a caller-visible path, the request directory is PID-based rather than request-bound, and the configured byte limit is not enforced while the CLI copies the archive into persistent staging.
  • GATOR-28b5152e-02: still open — the CLI still writes rootfs_tar_path at the top level and shallow-merges it, while VM driver selection consumes driver_config.vm.
  • GATOR-28b5152e-03: still open — the cache identity still uses the archive path plus a seconds-truncated mtime, without a bounded collision-resistant version key or a post-copy consistency check.
  • GATOR-168b9210-01: resolved — preserve-existing lifecycle paths now consume the persisted prepared-image identity instead of revalidating deleted request staging.

Scope growth:

  • None in this delta; it is confined to rootfs-tar restart and restoration.

Reviewer-quality signals:

  • No new Critical was proposed, and the existing obligations were carried without replacement findings.

Maintainer action: @NVIDIA/openshell-maintainers, please decide whether GATOR-28b5152e-01, GATOR-28b5152e-02, and GATOR-28b5152e-03 remain required before test dispatch; for any obligation that is acceptable as implemented, explicitly waive that finding, otherwise direct the author to complete it.

Gator metadata
  • Head SHA: 8ec3e99844bc6061e55b6c28421af9f94a676264
  • Base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Merge base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Patch ID: f50808f0a556f3895a7db93c521bb6dd2ee22159
  • Gator payload: 7
  • Review mode: critical_only
  • Previous reviewed SHA: 168b9210cc70a99b003a18b18b2bead91d30f0b9
  • Review budget exhausted: yes
  • Maintainer decision required: yes — three concrete prior obligations remain unresolved and unwaived
  • Next state: gator:blocked
  • Blocked reason: review_convergence_decision_required

@johntmyersjohntmyers added gator:blocked Gator is blocked by process or repository gates and removed gator:in-review Gator is reviewing or awaiting PR review feedback labels Aug 26, 2026
…s tar copy
Replace PID-based request staging directories with tempfile-generated
random names to prevent collisions and make paths unpredictable.
Replace bare tokio::fs::copy with a streaming copy loop that enforces
the advertised max_bytes limit during transfer, closing the TOCTOU gap
between the pre-copy size check and the actual copy.
Signed-off-by: Philippe Martin <phmartin@nvidia.com>
Signed-off-by: Philippe Martin <phmartin@redhat.com>
@johntmyers

Copy link
Copy Markdown
Collaborator

gator-agent

Maintainer Convergence Decision

Thanks @feloy. I checked the random request-staging directory and bounded-copy update at head c88045bd670bbfff1ba249aa196e2550b4541ea9. It resolves the PID-collision and copy-time byte-limit parts of GATOR-28b5152e-01, and the required critical-only delta review found no newly introduced Critical defect. The remaining request-binding obligation and two unchanged findings still need the maintainer convergence decision requested on the prior head.

Root-cause findings:

  • GATOR-28b5152e-01: partially addressed — staging directories are now random and copying enforces the advertised limit, but direct API input can still select another caller-visible two-component path under the shared staging root rather than a server-issued request-bound reference.
  • GATOR-28b5152e-02: still open — the CLI still writes rootfs_tar_path at the top level and shallow-merges it, while VM driver selection consumes driver_config.vm.
  • GATOR-28b5152e-03: still open — cache identity still uses the archive path plus a seconds-truncated mtime without a bounded collision-resistant version key or post-copy consistency check.
  • GATOR-168b9210-01: remains resolved — restart and restoration consume the persisted prepared-image identity.

Scope growth:

  • None; this delta is confined to request staging, bounded copying, and the already-reviewed restoration fix.

Reviewer-quality signals:

  • The current critical-only reviewer proposed no findings, duplicates, waived re-raises, or unchanged-code issues.

Maintainer action: @NVIDIA/openshell-maintainers, please decide whether the remaining part of GATOR-28b5152e-01 and GATOR-28b5152e-02/GATOR-28b5152e-03 remain required before test dispatch; explicitly waive any obligation acceptable as implemented, otherwise direct @feloy to complete it.

Gator metadata
  • Head SHA: c88045bd670bbfff1ba249aa196e2550b4541ea9
  • Base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Merge base SHA: 8be8b62ab58e42ee08213494b0d556ebc829e5de
  • Patch ID: db9b058cb31624200d415e472cef6872b0b115df
  • Gator payload: 7
  • Review mode: critical_only
  • Previous reviewed SHA: 168b9210cc70a99b003a18b18b2bead91d30f0b9
  • Review budget exhausted: yes
  • Maintainer decision required: yes — three concrete prior obligations remain unresolved or partially unresolved and unwaived
  • Checks: current-head Branch Checks and Helm Lint remain pending; test dispatch is gated on the convergence decision.
  • E2E: test:e2e remains required for VM sandbox lifecycle behavior but is not dispatched while review obligations remain.
  • Next state: gator:blocked
  • Blocked reason: review_convergence_decision_required

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gator:blockedGator is blocked by process or repository gates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support rootfs tar as --from source for VM driver sandboxes

2 participants

@feloy@johntmyers