mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
fix(linux-sandbox): deny missing read-only subtrees
Mask the first missing component of read-only carveouts under writable roots so sandboxed processes cannot create protected paths when the final leaf does not exist yet. Co-authored-by: Codex noreply@openai.com
This commit is contained in:
co-authored by
Codex noreply@openai.com
parent
3b0c2edb7f
commit
361dae22b2
@@ -439,11 +439,9 @@ fn append_read_only_subpath_args(
|
||||
}
|
||||
|
||||
if !subpath.exists() {
|
||||
// A missing protected leaf, such as `<workspace>/.codex`, can be
|
||||
// blocked with a file mask. Missing intermediate directories need
|
||||
// mountpoint cleanup and are handled by a separate follow-up.
|
||||
// Mask the first missing component so the process cannot create a
|
||||
// protected subtree under a writable root before reaching the leaf.
|
||||
if let Some(first_missing_component) = find_first_non_existent_component(subpath)
|
||||
&& first_missing_component.as_path() == subpath
|
||||
&& is_within_allowed_write_paths(&first_missing_component, allowed_write_paths)
|
||||
{
|
||||
append_empty_file_read_only_bind_args(args, preserved_files, &first_missing_component)?;
|
||||
@@ -818,6 +816,55 @@ mod tests {
|
||||
assert!(codex_mask_index < dev_rebind_index);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn masks_first_missing_component_for_nested_read_only_subpaths() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
let protected_path = temp_dir.path().join("missing").join("protected");
|
||||
let first_missing_component = temp_dir.path().join("missing");
|
||||
let policy = FileSystemSandboxPolicy::restricted(vec![
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Special {
|
||||
value: FileSystemSpecialPath::Minimal,
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(temp_dir.path()).expect("absolute temp dir"),
|
||||
},
|
||||
access: FileSystemAccessMode::Write,
|
||||
},
|
||||
FileSystemSandboxEntry {
|
||||
path: FileSystemPath::Path {
|
||||
path: AbsolutePathBuf::try_from(protected_path.as_path())
|
||||
.expect("absolute protected path"),
|
||||
},
|
||||
access: FileSystemAccessMode::Read,
|
||||
},
|
||||
]);
|
||||
|
||||
let args = create_filesystem_args(&policy, temp_dir.path()).expect("filesystem args");
|
||||
let first_missing_component = path_to_string(&first_missing_component);
|
||||
let protected_path = path_to_string(&protected_path);
|
||||
|
||||
assert_eq!(args.preserved_files.len(), 1);
|
||||
assert!(
|
||||
args.args.windows(3).any(|window| {
|
||||
window[0] == "--ro-bind-data" && window[2] == first_missing_component
|
||||
}),
|
||||
"missing protected subtree should be masked at first missing component: {:#?}",
|
||||
args.args
|
||||
);
|
||||
assert!(
|
||||
!args
|
||||
.args
|
||||
.windows(3)
|
||||
.any(|window| window[0] == "--ro-bind-data" && window[2] == protected_path),
|
||||
"mask should target the first missing component, not the unreachable leaf: {:#?}",
|
||||
args.args
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restricted_read_only_uses_scoped_read_roots_instead_of_erroring() {
|
||||
let temp_dir = TempDir::new().expect("temp dir");
|
||||
|
||||
Reference in New Issue
Block a user