[codex] Allow socketpair in proxy-routed Linux sandbox (#26625)

## Summary

- allow `socketpair(AF_UNIX, ...)` in the proxy-routed Linux seccomp
mode
- continue denying `socket(AF_UNIX, ...)` so user commands cannot create
pathname or abstract Unix sockets
- extend the managed-proxy integration test to verify both behaviors

## Root cause

`NetworkSeccompMode::ProxyRouted` treated anonymous Unix socket pairs
like externally addressable Unix sockets and returned `EPERM`. This
breaks tools that use socket pairs for local child-process IPC even
though a socket pair cannot connect outside the sandbox or bypass the
routed proxy.

`dangerously_allow_all_unix_sockets` controls Unix-socket requests
forwarded by the managed network proxy; it does not currently configure
the Linux seccomp filter. Socket pairs should not require that dangerous
setting because they are unnamed, process-local IPC.

Related but independent: #26553 fixes host proxy bridge socket path
length handling.

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
viyatb-oai
2026-06-05 09:34:36 -07:00
committed by GitHub
Unverified
parent 40c8f1a007
commit d40454522e
2 changed files with 11 additions and 10 deletions
+8 -7
View File
@@ -217,10 +217,11 @@ fn install_network_seccomp_filter_on_current_thread(
}
NetworkSeccompMode::ProxyRouted => {
// In proxy-routed mode we allow IP sockets in the isolated
// namespace (used to reach the local TCP bridge) but deny all
// other socket families, including AF_UNIX. This prevents
// bypassing the routed bridge via new Unix sockets and narrows the
// socket surface in proxy-only mode.
// namespace (used to reach the local TCP bridge) but deny socket()
// for all other families, including AF_UNIX. Only AF_UNIX
// socketpair() remains available for process-local IPC because it
// cannot connect to a socket outside the sandbox or bypass the
// bridge.
let deny_non_ip_socket = SeccompRule::new(vec![
SeccompCondition::new(
0,
@@ -235,14 +236,14 @@ fn install_network_seccomp_filter_on_current_thread(
libc::AF_INET6 as u64,
)?,
])?;
let deny_unix_socketpair = SeccompRule::new(vec![SeccompCondition::new(
let deny_non_unix_socketpair = SeccompRule::new(vec![SeccompCondition::new(
0,
SeccompCmpArgLen::Dword,
SeccompCmpOp::Eq,
SeccompCmpOp::Ne,
libc::AF_UNIX as u64,
)?])?;
rules.insert(libc::SYS_socket, vec![deny_non_ip_socket]);
rules.insert(libc::SYS_socketpair, vec![deny_unix_socketpair]);
rules.insert(libc::SYS_socketpair, vec![deny_non_unix_socketpair]);
}
}
@@ -266,7 +266,7 @@ async fn managed_proxy_mode_routes_through_bridge_and_blocks_direct_egress() {
}
#[tokio::test]
async fn managed_proxy_mode_denies_af_unix_creation_for_user_command() {
async fn managed_proxy_mode_denies_af_unix_socket_but_allows_socketpair() {
if let Some(skip_reason) = managed_proxy_skip_reason().await {
eprintln!("skipping managed proxy test: {skip_reason}");
return;
@@ -292,7 +292,7 @@ async fn managed_proxy_mode_denies_af_unix_creation_for_user_command() {
&[
"python3",
"-c",
"import socket,sys\ntry:\n socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\nexcept PermissionError:\n sys.exit(0)\nexcept OSError:\n sys.exit(2)\nsys.exit(1)\n",
"import socket,sys\ntry:\n socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\nexcept PermissionError:\n pass\nexcept OSError:\n sys.exit(2)\nelse:\n sys.exit(1)\nleft,right = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)\nleft.sendall(b'ok')\nif right.recv(2) != b'ok':\n sys.exit(3)\n",
],
&PermissionProfile::Disabled,
/*allow_network_for_proxy*/ true,
@@ -304,7 +304,7 @@ async fn managed_proxy_mode_denies_af_unix_creation_for_user_command() {
assert_eq!(
output.status.code(),
Some(0),
"expected AF_UNIX creation to be denied cleanly for user command; status={:?}; stdout={}; stderr={}",
"expected AF_UNIX socket creation to be denied and socketpair to work; status={:?}; stdout={}; stderr={}",
output.status.code(),
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)