Skip to content

[Bubblewrap/LXC/WSLC] Add filesystem-policy delegation check#598

Open
SohamDas2021 wants to merge 3 commits into
mainfrom
user/sodas/fs-delegation-check
Open

[Bubblewrap/LXC/WSLC] Add filesystem-policy delegation check#598
SohamDas2021 wants to merge 3 commits into
mainfrom
user/sodas/fs-delegation-check

Conversation

@SohamDas2021

@SohamDas2021 SohamDas2021 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

📖 Description

Enforce that the container is never granted more filesystem access than the invoking user already holds: every readwritePaths entry requires the user to have read+write access, and every readonlyPaths entry requires read access. deniedPaths are unbounded (denying needs no access) and are not checked. A path the user cannot access is rejected, so a sandboxed process can't reach files the caller themselves couldn't.

🔗 References

🔍 Validation

✅ Checklist

📋 Issue Type

  • Bug fix
  • Feature
  • Task

GitHub Actions runs the PR validation build automatically. The ADO pipeline
(MXC-PR-Build) is the official build pipeline that signs the binaries; it
runs on merge to main and nightly, and Microsoft reviewers can trigger it
on a PR with /azp run. See docs/pull-requests.md.

Microsoft Reviewers: Open in CodeFlow

Copilot AI review requested due to automatic review settings July 1, 2026 20:05

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

Adds a cross-backend “filesystem-policy delegation” gate so Linux (LXC/Bubblewrap) and WSLC runs reject readonlyPaths/readwritePaths entries that the invoking user cannot access, preventing the sandbox from being granted more filesystem access than the caller has.

Changes:

  • Introduces wxc_common::filesystem_access with a per-platform access probe (access(2) / CreateFileW) and check_delegation(ContainerPolicy).
  • Wires the delegation check into Bubblewrap, LXC, and WSLC runners close to mount/enforcement.
  • Adds unit tests for common allow/skip/reject cases.

Reviewed changes

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

Show a summary per file
File Description
src/core/wxc_common/src/lib.rs Exposes the new filesystem_access module from wxc_common.
src/core/wxc_common/src/filesystem_access.rs Implements the delegation check and associated unit tests.
src/backends/wslc/common/src/wsl_container_runner.rs Runs delegation check early in WSLC runner to reject inaccessible policy paths.
src/backends/lxc/common/src/lxc_runner.rs Runs delegation check at start of LXC execution path.
src/backends/bubblewrap/common/src/bwrap_runner.rs Runs delegation check before spawning bwrap.

Comment on lines +77 to +91
fn user_can_access(path: &str, mode: AccessMode) -> Option<bool> {
use std::ffi::CString;

if std::fs::metadata(path).is_err() {
return None;
}
let c_path = CString::new(path).ok()?;
let mask = match mode {
AccessMode::Read => libc::R_OK,
AccessMode::ReadWrite => libc::R_OK | libc::W_OK,
};
// SAFETY: `c_path` is a valid NUL-terminated C string for the duration of the call.
let rc = unsafe { libc::access(c_path.as_ptr(), mask) };
Some(rc == 0)
}
Comment on lines +13 to +16
//! This does file I/O (`access(2)` / `CreateFileW`), so — like the object-based
//! normalization in [`crate::filesystem_object`] and per design review — it runs
//! in each backend runner **close to the point of enforcement**, NOT in
//! `config_parser` (which stays string-only). Two reasons:
Comment on lines +25 to +28
//! When both this and object normalization are wired into a runner,
//! [`crate::filesystem_object::normalize_object_conflicts`] must run **first**,
//! so delegation is checked against the already-tightened intents (a path moved
//! `rw → denied` must not then be required to have write access).
@SohamDas2021 SohamDas2021 force-pushed the user/sodas/fs-delegation-check branch from 3381a65 to e1623d1 Compare July 2, 2026 20:18
@SohamDas2021 SohamDas2021 marked this pull request as ready for review July 2, 2026 21:32
@SohamDas2021 SohamDas2021 requested a review from a team as a code owner July 2, 2026 21:32
Some(true)
}
_ => {
// Only an explicit access denial is a delegation failure. Any other

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.

Copilot tells me - Unix uses a skip-list (only genuine "missing" is skipped; the default is reject). Windows uses a reject-list (only explicit access-denied is rejected; the default is skip). A ERROR_SHARING_VIOLATION, ERROR_LOCK_VIOLATION, or transient IO error on a real, existing file hits the Windows default and is silently skipped — the exact opposite of what the same situation does on Unix.

So on Unix "non-existent is surfaced separately" is achieved by listing the missing errnos and rejecting everything else; on Windows the author achieves it by skipping everything except one error. Those are not the same posture.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the catch, seems to be a valid concern, will address.

@microsoft-github-policy-service microsoft-github-policy-service Bot added Needs-Author-Feedback Issue needs attention from issue or PR author Needs-Attention Issue needs attention from Microsoft and removed Needs-Author-Feedback Issue needs attention from issue or PR author labels Jul 2, 2026
The delegation access-probe used asymmetric error handling across platforms:
Unix skipped only genuine-missing paths and rejected everything else
(fail-closed), while Windows rejected only ERROR_ACCESS_DENIED and skipped
every other failure (fail-open). A sharing/lock violation or transient I/O
error on a real, existing file was therefore silently allowed on Windows but
rejected on Unix — the opposite posture for a security delegation gate.

Align Windows to the Unix posture: skip only ERROR_FILE_NOT_FOUND /
ERROR_PATH_NOT_FOUND (surfaced separately by the existence warning); treat
every other failure, including access-denied and ambiguous errors, as
'cannot confirm access' and reject. Add a Windows test locking the
fail-closed behavior for an ambiguous (ERROR_INVALID_NAME) error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Needs-Attention Issue needs attention from Microsoft

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants