Files
codex/codex-rs/linux-sandbox/src/wslg.rs
T
Eric Traut 8ece31a7bf Hide WSLg's duplicate root in restricted Linux sandboxes (#45837)
## Why

WSLg's duplicate distro root can expose filesystem contents outside the sandbox's path masks.

## What changed

- Detect the duplicate root using filesystem identity, with mount metadata as a fallback, and hide it after applying filesystem grants and denials.
- Reject explicit grants, working directories, and executable paths that use the WSLg alias, directing users to the primary filesystem paths.
- Hide host procfs when a fresh procfs cannot be mounted so process roots cannot restore access to the masked view.

## Testing

Add regression tests for duplicate-root detection, mask ordering, existing ancestor masks, alias rejection, and unrestricted filesystem behavior. Add a WSLg runtime test covering masking with and without fresh procfs, continued access to an allowed file, and rejection of an executable using the alias.

GitOrigin-RevId: 9ea31724c00fb24e3a504b6658339e8dfea35343
2026-09-16 02:17:36 +00:00

67 lines
2.5 KiB
Rust

//! Identify WSLg's duplicate root without relying on filtered environment variables
//! or kernel branding. An unrelated directory at the same path is left untouched.
//! Explicit alias paths are rejected before building the restricted filesystem.
use std::fs;
use std::io;
use std::os::unix::fs::MetadataExt;
use std::path::Path;
pub(crate) fn is_duplicate_root(path: &Path) -> io::Result<bool> {
match fs::symlink_metadata(path) {
Ok(metadata) => {
let root = fs::metadata("/")?;
Ok(metadata.is_dir() && (metadata.dev(), metadata.ino()) == (root.dev(), root.ino()))
}
Err(err)
if matches!(
err.kind(),
io::ErrorKind::NotFound | io::ErrorKind::NotADirectory
) =>
{
Ok(false)
}
Err(_) => {
// Mount metadata remains readable when an ancestor is not searchable.
// If neither observation is available, fail rather than guess that
// an inaccessible duplicate can safely remain in the sandbox.
let mountinfo = fs::read_to_string("/proc/self/mountinfo")?;
let root = mount_identity(&mountinfo, "/")?
.ok_or_else(|| io::Error::other("root mount identity is unavailable"))?;
Ok(Some(root) == mount_identity(&mountinfo, &path.to_string_lossy())?)
}
}
}
pub(crate) fn ensure_supported_path(path: &Path) -> codex_protocol::error::Result<()> {
let alias = crate::bwrap::WSLG_DISTRO_ROOT;
if path.starts_with(alias)
|| fs::canonicalize(path).is_ok_and(|resolved| resolved.starts_with(alias))
{
return Err(codex_protocol::error::CodexErr::Fatal(format!(
"restricted sandboxes do not support paths under {alias}: {}; use the corresponding path under the primary filesystem root instead",
path.display()
)));
}
Ok(())
}
fn mount_identity<'a>(mountinfo: &'a str, path: &str) -> io::Result<Option<(&'a str, &'a str)>> {
let mut identities = mountinfo.lines().filter_map(|line| {
let mut fields = line.split_ascii_whitespace();
let device = fields.nth(2)?;
let root = fields.next()?;
let mountpoint = fields.next()?;
(mountpoint == path).then_some((device, root))
});
let identity = identities.next();
if identities.any(|other| Some(other) != identity) {
return Err(io::Error::other("mount identity is ambiguous"));
}
Ok(identity)
}
#[cfg(test)]
#[path = "wslg_tests.rs"]
mod tests;