Skip to content

refactor(guest): replace external chown with syscalls - #1210

Merged
DorianZheng merged 1 commit into
mainfrom
codex/guest-syscall-chown
Aug 12, 2026
Merged

DorianZheng merged 1 commit into
mainfrom
codex/guest-syscall-chown

Conversation

@ltstriker

@ltstriker ltstriker commented Aug 12, 2026 •

Copy link
Copy Markdown
Member

Summary

Replace the guest's PATH-resolved chown -R subprocess with an in-process, fd-relative syscall implementation while preserving the existing ownership sampling and best-effort repair behavior.

Call graph

Before
mount (BlockDeviceMount · src/guest/src/storage/block_device.rs:25) — mounts and initializes the guest filesystem
└─ fix_if_needed (OwnershipFixer · src/guest/src/storage/perms.rs:17) — samples ownership before repair
├─ ownership_matches (OwnershipFixer · src/guest/src/storage/perms.rs:74) — skips an already-matching tree
└─ Command::new("chown") (std::process::Command · src/guest/src/storage/perms.rs:43) — runs PATH-resolved chown -R

After
mount (BlockDeviceMount · src/guest/src/storage/block_device.rs:25) — mounts and initializes the guest filesystem
└─ fix_if_needed (OwnershipFixer · src/guest/src/storage/perms.rs:23) — preserves the existing ownership sample gate
├─ ownership_matches (OwnershipFixer · src/guest/src/storage/perms.rs:82) — keeps the original root-plus-five sampling policy
└─ chown_path (RecursiveChowner · src/guest/src/storage/perms.rs:134) — walks the tree iteratively with fd-relative syscalls
├─ chown_operand (RecursiveChowner · src/guest/src/storage/perms.rs:316) — changes a non-directory root without following symlinks
├─ chown_entry (RecursiveChowner · src/guest/src/storage/perms.rs:329) — changes descendant entries with fchownat
└─ chown_directory (RecursiveChowner · src/guest/src/storage/perms.rs:342) — changes directories post-order with fchown

Changes

  • Remove the runtime chown binary, PATH lookup, and reaper-fence dependency from guest ownership repair.
  • Add an iterative, fd-relative walker that handles non-UTF-8 names, symlinks, special files, cross-mount traversal, ancestor cycles, and bounded warning samples.
  • Add focused ordinary and privileged ownership tests plus a Linux CI target.

How to verify

  • make test:unit:guest
  • make test:guest-perms
  • make fmt:check:rust
  • make clippy
  • git diff --check

Risks / rollout

  • Ownership repair now depends on Linux filesystem syscalls instead of GNU chown; privileged Linux tests cover recursive repair, read-only mounts, cross-mount traversal, and bind-mount cycles.

Summary by CodeRabbit

  • Bug Fixes

    • Improved guest ownership repair across files, directories, symlinks, and special files.
    • Ownership repair now continues safely when individual entries cannot be accessed.
    • Added protection against directory cycles and unsafe symlink traversal.
    • Added detailed repair results and warning reporting.
  • Tests

    • Expanded coverage for permissions, ownership, filesystem errors, links, mounts, and edge cases.
    • Added automated privileged ownership tests on Linux.

@ltstriker
ltstriker requested a review from a team as a code owner August 12, 2026 12:04
Copilot AI lite review requested due to automatic review settings August 12, 2026 12:04
@boxlite-agent

boxlite-agent Bot commented Aug 12, 2026 •

Copy link
Copy Markdown

📦 BoxLite review — couldn't complete

claude exited 1

stdout:
{"is_error":true,"duration_api_ms":0,"num_turns":1,"stop_reason":"stop_sequence","session_id":"0b2373be-d694-41c8-820c-4d3e74ed14aa","total_cost_usd":0,"usage":{"output_tokens_details":{"thinking_tokens":0},"input_tokens":0,"cache_creation_input_tokens":0,"cache_read_input_tokens":0,"output_tokens":0,"server_tool_use":{"web_search_requests":0,"web_fetch_requests":0},"service_tier":"standard","cache_creation":{"ephemeral_1h_input_tokens":0,"ephemeral_5m_input_tokens":0},"inference_geo":"","iterations":[],"speed":"standard"},"modelUsage":{},"permission_denials":[],"terminal_reason":"api_error","fast_mode_state":"off","fast_mode_disabled_reason":"sdk_opt_in_required","subtype":"success","api_error_status":403,"result":"Your organization has disabled Claude subscription access for Claude Code · Use an Anthropic API key instead, or ask your admin to enable access","type":"result","duration_ms":273,"uuid":"0ecaaac5-e8c7-44cd-a3ca-0eb5bef71e36"}

stderr:
<empty>

powered by BoxLite

@coderabbitai

coderabbitai Bot commented Aug 12, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR replaces external recursive ownership repair with descriptor-based traversal. It adds fault-injection and privileged tests for filesystem edge cases, plus a Linux Make target and CI wiring to run them.

Guest ownership repair

Layer / File(s) Summary
Descriptor-based ownership traversal
src/guest/Cargo.toml, src/guest/src/storage/perms.rs
Ownership repair uses descriptor-based traversal, avoids following symlinks, detects cycles, continues after per-entry failures, and reports bounded warning samples.
Ownership traversal validation
src/guest/src/storage/perms/tests.rs
Tests cover deep paths, special files, links, injected failures, permission errors, read-only mounts, mount traversal, and privileged ownership repair.
Guest permission test wiring
make/test.mk, make/help.mk, .github/workflows/test.yml
The new target builds and locates the test binary, runs unprivileged and privileged tests on Linux, and installs required tools in CI.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CI as Linux CI runner
  participant Make as make test:guest-perms
  participant Cargo as Cargo
  participant Tests as Guest permission tests
  participant Root as Root or passwordless sudo
  CI->>Make: invoke guest permission tests
  Make->>Cargo: build tests and emit JSON metadata
  Cargo-->>Make: test executable path
  Make->>Tests: run unprivileged tests
  Make->>Root: run ignored privileged tests in private mount namespace
  Root->>Tests: execute privileged tests
Loading

Possibly related PRs

  • boxlite-ai/boxlite#1197: Both PRs modify src/guest/src/storage/perms.rs and add descriptor-based ownership traversal tests.

Suggested labels: e2e-local

Suggested reviewers: dorianzheng

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: replacing the external chown command with syscall-based implementation.
Description check ✅ Passed The description includes the required summary, call graph, changes, verification steps, and risks sections with relevant details.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/guest-syscall-chown

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
make/test.mk (2)

252-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Constrain the jq selection to the bin test artifact.

The filter matches any compiler-artifact whose target.name is boxlite-guest and whose profile.test is true. A library or integration-test target that shares the package name also matches, and last then selects the wrong executable. Add a target.kind check so the selection stays deterministic.

🔧 Proposed fix
 	if ! test_binary=$$(jq --slurp --raw-output --exit-status \
-		'[.[] | select(.reason == "compiler-artifact" and .profile.test == true and .target.name == "boxlite-guest" and .executable != null) | .executable] | last' \
+		'[.[] | select(.reason == "compiler-artifact" and .profile.test == true and .target.name == "boxlite-guest" and (.target.kind | index("bin")) and .executable != null) | .executable] | last' \
 		"$$test_metadata"); then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@make/test.mk` around lines 252 - 257, Update the jq filter used to assign
test_binary so it also requires the artifact target kind to be the bin kind,
while preserving the existing compiler-artifact, test-profile, boxlite-guest
name, and executable checks. Keep the final selection deterministic by
continuing to select the last matching executable.

262-274: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: skip the privileged stage instead of failing on developer machines.

The recipe exits 1 when the caller is neither root nor able to run passwordless sudo. CI satisfies that condition, so the strict check helps there. On a local checkout the whole target fails after the unprivileged tests already passed. Consider a variable such as GUEST_PERMS_REQUIRE_PRIVILEGED=1 that CI sets, and print a skip notice otherwise.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@make/test.mk` around lines 262 - 274, Update the privileged ownership test
stage around the test_binary invocation to support a
GUEST_PERMS_REQUIRE_PRIVILEGED=1 configuration. Continue running the privileged
tests for root or passwordless sudo users; when unavailable, skip with a clear
notice by default, but retain the existing failure behavior when the variable is
enabled for CI.
src/guest/src/storage/perms/tests.rs (1)

62-68: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: do not panic inside MountGuard::drop.

If an assertion fails first, the expect in drop panics during unwinding. The process then aborts and the original assertion message is lost. Log the unmount error instead, so the real failure stays visible.

♻️ Proposed refactor
 impl Drop for MountGuard {
     fn drop(&mut self) {
-        umount2(&self.0, MntFlags::MNT_DETACH).expect("unmount test bind mount");
+        if let Err(error) = umount2(&self.0, MntFlags::MNT_DETACH) {
+            eprintln!("failed to unmount test bind mount {}: {error}", self.0.display());
+        }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/guest/src/storage/perms/tests.rs` around lines 62 - 68, Update
MountGuard::drop to handle umount2 errors without panicking: replace the
expect-based failure path with logging of the unmount error, while leaving
successful cleanup unchanged so an earlier assertion failure remains visible.
src/guest/src/storage/perms.rs (1)

166-190: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: move the fault injection out of the traversal loop.

The loop repeats four #[cfg(test)] / #[cfg(not(test))] pairs for the read, open, stat, and stream stages. This duplicates the production call in each pair, so a future change can update one arm only. Small private helpers such as read_next(&mut self, frame), open_child(...), stat_child(...), and open_stream(...) keep the injection inside one place and keep chown_path linear.

Also use std::os::fd::RawFd instead of i32 for the parent_fd parameter of chown_entry (Line 333) to match the rest of the file.

Also applies to: 333-344

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/guest/src/storage/perms.rs` around lines 166 - 190, Refactor the
traversal in chown_path so test fault injection for read, open, stat, and stream
stages is encapsulated in small private helpers, leaving the loop with a single
production-style call for each stage; preserve the existing injected failures
and behavior. Also update chown_entry’s parent_fd parameter from i32 to
std::os::fd::RawFd to match the file’s descriptor types.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/guest/src/storage/perms/tests.rs`:
- Around line 226-230: Update the temporary-directory setup in the affected test
to derive its parent from Cargo’s configured target directory rather than
assuming ../../target, create that directory before calling tempdir_in, and add
a comment explaining that a target-local directory is used to keep Unix socket
paths within length limits.

---

Nitpick comments:
In `@make/test.mk`:
- Around line 252-257: Update the jq filter used to assign test_binary so it
also requires the artifact target kind to be the bin kind, while preserving the
existing compiler-artifact, test-profile, boxlite-guest name, and executable
checks. Keep the final selection deterministic by continuing to select the last
matching executable.
- Around line 262-274: Update the privileged ownership test stage around the
test_binary invocation to support a GUEST_PERMS_REQUIRE_PRIVILEGED=1
configuration. Continue running the privileged tests for root or passwordless
sudo users; when unavailable, skip with a clear notice by default, but retain
the existing failure behavior when the variable is enabled for CI.

In `@src/guest/src/storage/perms.rs`:
- Around line 166-190: Refactor the traversal in chown_path so test fault
injection for read, open, stat, and stream stages is encapsulated in small
private helpers, leaving the loop with a single production-style call for each
stage; preserve the existing injected failures and behavior. Also update
chown_entry’s parent_fd parameter from i32 to std::os::fd::RawFd to match the
file’s descriptor types.

In `@src/guest/src/storage/perms/tests.rs`:
- Around line 62-68: Update MountGuard::drop to handle umount2 errors without
panicking: replace the expect-based failure path with logging of the unmount
error, while leaving successful cleanup unchanged so an earlier assertion
failure remains visible.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 1b8ea997-9027-4e0a-a511-ecaafef08acb

📥 Commits

Reviewing files that changed from the base of the PR and between 077fe4f and 24df956.

📒 Files selected for processing (6)
  • .github/workflows/test.yml
  • make/help.mk
  • make/test.mk
  • src/guest/Cargo.toml
  • src/guest/src/storage/perms.rs
  • src/guest/src/storage/perms/tests.rs

Comment thread src/guest/src/storage/perms/tests.rs

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors guest filesystem ownership repair to remove the PATH-resolved chown -R subprocess and replace it with an in-process, fd-relative syscall-based walker, along with new tests and CI wiring to cover privileged cases.

Changes:

  • Replace Command::new("chown")-based recursive ownership repair with an iterative, fd-relative traversal using openat/fstatat/fchownat/fchown.
  • Add a dedicated guest perms test suite (including root-only tests) and a make test:guest-perms target that runs privileged tests inside a private mount namespace.
  • Update Linux CI to install dependencies and run the new guest ownership tests.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/guest/src/storage/perms.rs Swaps external chown -R for an in-process recursive chown implementation and reporting.
src/guest/src/storage/perms/tests.rs Adds focused unprivileged + privileged coverage for recursive chown behavior and edge cases.
src/guest/Cargo.toml Enables the needed nix feature(s) for directory iteration.
make/test.mk Introduces test:guest-perms target, including privileged execution via unshare.
make/help.mk Documents the new make test:guest-perms target.
.github/workflows/test.yml Installs required Linux deps and runs the new guest ownership test target in CI.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/guest/src/storage/perms.rs
Comment thread src/guest/src/storage/perms.rs
Comment thread src/guest/src/storage/perms/tests.rs
@DorianZheng
DorianZheng enabled auto-merge August 12, 2026 12:32
@DorianZheng
DorianZheng added this pull request to the merge queue Aug 12, 2026
Merged via the queue into main with commit 14b4374 Aug 12, 2026
39 checks passed
@DorianZheng
DorianZheng deleted the codex/guest-syscall-chown branch August 12, 2026 12:50
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.

3 participants