Allow unrelated namespace mounts in Linux sandbox socket checks (#46535)

Namespace mounts can have roots such as `mnt:[inode]` or `net:[inode]` that are not filesystem paths. Treating every mount root as a path rejects these unrelated mounts and prevents sandbox startup.

Parse mount roots as paths only for the daemon socket's filesystem. Continue validating every mount destination and checking mount ancestry, nested mounts, and socket aliases.

Add regression cases for namespace mounts, invalid destinations, and socket aliases, both with and without a mount ID. Extend the socket-isolation integration fixture with an unrelated network namespace mount to exercise successful startup and rejection of a real socket alias.

GitOrigin-RevId: 9a1d2a69e5bd3f8d09b1de7cf998ee6d9c6bb980
This commit is contained in:
pash-openai
2026-09-23 11:42:23 -07:00
committed by Ian MacLeod
parent 21504b1d18
commit bbbda446a0
2 changed files with 48 additions and 5 deletions
+10 -5
View File
@@ -39,14 +39,19 @@ fn check_mounts(
let [_, _, mount_device, root, destination] = fields.as_slice() else {
return Err(invalid());
};
let root = mount_path(root)?;
let destination = mount_path(destination)?;
if *mount_device == device.as_bytes()
// Only roots on the socket filesystem can identify aliases. Other
// filesystems can use non-path roots such as nsfs `mnt:[inode]`, but
// their destinations still matter for nested-mount checks.
let root = (*mount_device == device.as_bytes())
.then(|| mount_path(root))
.transpose()?;
if let Some(root) = &root
&& let Ok(relative) = directory.strip_prefix(&destination)
{
locations.insert(root.join(relative));
}
mounts.push((*mount_device, root, destination));
mounts.push((root, destination));
}
// Overmounts can leave hidden entries in mountinfo. Require every possible
// containing mount to agree instead of guessing which root is visible.
@@ -54,10 +59,10 @@ fn check_mounts(
return Err(invalid());
}
let location = locations.into_iter().next().ok_or_else(invalid)?;
for (mount_device, root, destination) in &mounts {
for (root, destination) in &mounts {
// Nested mounts can introduce another filesystem (or an individual socket) under the mask.
let nested = destination != directory && destination.starts_with(directory);
let alias = if *mount_device == device.as_bytes() {
let alias = if let Some(root) = root {
if let Ok(relative) = location.strip_prefix(root) {
Some(destination.join(relative))
} else if root.starts_with(&location) {
@@ -29,6 +29,44 @@ fn rejects_only_mounts_that_compromise_the_directory(root: &str, destination: &s
);
}
#[test_case("0:2", "mnt:[4026532835]", "/run/snapd/ns/example.mnt", Ok(()); "unrelated mount namespace")]
#[test_case("0:2", "net:[4026531840]", "/run/netns/example", Ok(()); "unrelated network namespace")]
#[test_case("0:2", "mnt:[4026532835]", "/tmp/codex-daemon-1000/ns", Err(io::ErrorKind::PermissionDenied); "nested namespace mount")]
#[test_case("0:1", "mnt:[4026532835]", "/run/snapd/ns/example.mnt", Err(io::ErrorKind::Other); "non-path root on socket filesystem")]
#[test_case("0:2", "mnt:[4026532835]", "relative/ns", Err(io::ErrorKind::Other); "relative destination")]
#[test_case("0:2", "mnt:[4026532835]", "/run/snapd/ns/\\invalid", Err(io::ErrorKind::Other); "invalid destination escape")]
fn validates_namespace_mounts_by_device_and_destination(
device: &str,
root: &str,
destination: &str,
expected: Result<(), io::ErrorKind>,
) {
let mounts = format!(
"1 0 0:1 / / rw - ext4 disk rw\n2 1 {device} {root} {destination} rw - nsfs nsfs rw\n"
);
assert_eq!(
check_mounts(
Path::new("/tmp/codex-daemon-1000"),
"0:1",
mounts.as_bytes()
)
.map_err(|error| error.kind()),
expected
);
}
#[test]
fn unrelated_namespace_mount_does_not_hide_a_socket_alias() {
let mounts = b"1 0 0:1 / / rw - ext4 disk rw\n\
2 1 0:2 net:[4026531840] /run/netns/example rw - nsfs nsfs rw\n\
3 1 0:1 /tmp/codex-daemon-1000 /alias rw - ext4 disk rw\n";
assert_eq!(
check_mounts(Path::new("/tmp/codex-daemon-1000"), "0:1", mounts)
.map_err(|error| error.kind()),
Err(io::ErrorKind::PermissionDenied)
);
}
#[test]
fn rejects_alias_when_tmp_is_itself_a_bind_mount() {
let mounts = b"1 0 0:1 / / rw - ext4 disk rw\n2 1 0:1 /backing/tmp /tmp rw - ext4 disk rw\n";