Skip to content

Commit 56f8710

Browse files
committed
fix: cap SocketTable size and clear on restore
Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent a4831d7 commit 56f8710

1 file changed

Lines changed: 58 additions & 13 deletions

File tree

host/src/lib.rs

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,7 @@ fn register_internal_tools(
892892
exit_code: &Arc<AtomicI32>,
893893
network: Option<&NetworkPolicy>,
894894
listen_ports: Option<&ListenPorts>,
895-
) {
895+
) -> Option<Arc<Mutex<SocketTable>>> {
896896
let ec = exit_code.clone();
897897
tools.register("__hl_exit", move |args| {
898898
let code = args["code"].as_i64().unwrap_or(1) as i32;
@@ -906,9 +906,7 @@ fn register_internal_tools(
906906
}
907907
Ok(serde_json::json!({}))
908908
});
909-
if let Some(policy) = network {
910-
register_net_tools(tools, policy, listen_ports);
911-
}
909+
network.map(|policy| register_net_tools(tools, policy, listen_ports))
912910
}
913911

914912
// ---------------------------------------------------------------------------
@@ -923,6 +921,8 @@ enum HostSocket {
923921
Socket(Socket, i32),
924922
}
925923

924+
const MAX_SOCKETS: usize = 1024;
925+
926926
struct SocketTable {
927927
sockets: HashMap<u64, HostSocket>,
928928
next_id: u64,
@@ -936,11 +936,19 @@ impl SocketTable {
936936
}
937937
}
938938

939-
fn insert(&mut self, sock: HostSocket) -> u64 {
939+
fn insert(&mut self, sock: HostSocket) -> Result<u64> {
940+
if self.sockets.len() >= MAX_SOCKETS {
941+
return Err(anyhow!("socket limit reached (max {})", MAX_SOCKETS));
942+
}
940943
let id = self.next_id;
941944
self.next_id += 1;
942945
self.sockets.insert(id, sock);
943-
id
946+
Ok(id)
947+
}
948+
949+
fn clear(&mut self) {
950+
self.sockets.clear();
951+
self.next_id = 1;
944952
}
945953

946954
fn get(&self, fd: u64) -> Result<&HostSocket> {
@@ -994,7 +1002,7 @@ fn register_net_tools(
9941002
tools: &mut ToolRegistry,
9951003
policy: &NetworkPolicy,
9961004
listen_ports: Option<&ListenPorts>,
997-
) {
1005+
) -> Arc<Mutex<SocketTable>> {
9981006
use base64::Engine;
9991007
use serde_json::json;
10001008

@@ -1027,7 +1035,7 @@ fn register_net_tools(
10271035
let fd = t
10281036
.lock()
10291037
.unwrap()
1030-
.insert(HostSocket::Socket(sock, sock_type));
1038+
.insert(HostSocket::Socket(sock, sock_type))?;
10311039
Ok(json!({ "fd": fd }))
10321040
});
10331041

@@ -1088,7 +1096,7 @@ fn register_net_tools(
10881096
let new_fd = t
10891097
.lock()
10901098
.unwrap()
1091-
.insert(HostSocket::Socket(new_sock, parent_type));
1099+
.insert(HostSocket::Socket(new_sock, parent_type))?;
10921100
let mut resp = json!({ "fd": new_fd });
10931101
if let Some(pa) = peer_addr {
10941102
resp["addr"] = json!(pa.ip().to_string());
@@ -1270,6 +1278,8 @@ fn register_net_tools(
12701278
Ok(json!({ "addr": "0.0.0.0", "port": 0 }))
12711279
}
12721280
});
1281+
1282+
table
12731283
}
12741284

12751285
/// Routes incoming fs_* tool calls to the matching `FsSandbox` by
@@ -1523,6 +1533,9 @@ pub struct Sandbox {
15231533
file_mapping_path: Option<std::path::PathBuf>,
15241534
file_mapping_base: u64,
15251535
exit_code: Arc<AtomicI32>,
1536+
/// Shared socket table — cleared on [`Sandbox::restore`] so that
1537+
/// host-side fds don't leak across guest restore cycles.
1538+
socket_table: Option<Arc<Mutex<SocketTable>>>,
15261539
}
15271540

15281541
/// Where the initrd comes from — either a file (zero-copy `map_file_cow`)
@@ -1732,14 +1745,14 @@ impl Sandbox {
17321745

17331746
let exit_code = Arc::new(AtomicI32::new(0));
17341747
let mut tools = build_tools(tools, preopens)?.unwrap_or_default();
1735-
register_internal_tools(&mut tools, &exit_code, network, listen_ports);
1748+
let socket_table = register_internal_tools(&mut tools, &exit_code, network, listen_ports);
17361749
let tools = Arc::new(tools);
17371750
let tools_ref = tools.clone();
17381751
usbox.register_host_function("__dispatch", move |payload: Vec<u8>| -> Vec<u8> {
17391752
tools_ref.dispatch(&payload)
17401753
})?;
17411754

1742-
Self::finish_evolve(usbox, None, 0, exit_code)
1755+
Self::finish_evolve(usbox, None, 0, exit_code, socket_table)
17431756
}
17441757

17451758
/// Low-level: boot with a zero-copy mapped initrd file. Prefer the builder.
@@ -1784,7 +1797,7 @@ impl Sandbox {
17841797

17851798
let exit_code = Arc::new(AtomicI32::new(0));
17861799
let mut tools = build_tools(tools, preopens)?.unwrap_or_default();
1787-
register_internal_tools(&mut tools, &exit_code, network, listen_ports);
1800+
let socket_table = register_internal_tools(&mut tools, &exit_code, network, listen_ports);
17881801
let tools = Arc::new(tools);
17891802
let tools_ref = tools.clone();
17901803
usbox.register_host_function("__dispatch", move |payload: Vec<u8>| -> Vec<u8> {
@@ -1796,6 +1809,7 @@ impl Sandbox {
17961809
initrd_path.map(|p| p.to_path_buf()),
17971810
INITRD_MAP_BASE,
17981811
exit_code,
1812+
socket_table,
17991813
)
18001814
}
18011815

@@ -1804,6 +1818,7 @@ impl Sandbox {
18041818
file_mapping_path: Option<std::path::PathBuf>,
18051819
file_mapping_base: u64,
18061820
exit_code: Arc<AtomicI32>,
1821+
socket_table: Option<Arc<Mutex<SocketTable>>>,
18071822
) -> Result<Self> {
18081823
let mut inner = usbox.evolve()?;
18091824
let snapshot = inner.snapshot().ok();
@@ -1813,6 +1828,7 @@ impl Sandbox {
18131828
file_mapping_path,
18141829
file_mapping_base,
18151830
exit_code,
1831+
socket_table,
18161832
})
18171833
}
18181834

@@ -1830,6 +1846,10 @@ impl Sandbox {
18301846
self.inner
18311847
.map_file_cow(path, self.file_mapping_base, Some("initrd"))?;
18321848
}
1849+
// Close leaked host-side sockets the guest "forgot" about.
1850+
if let Some(ref table) = self.socket_table {
1851+
table.lock().unwrap().clear();
1852+
}
18331853
Ok(())
18341854
}
18351855

@@ -1989,7 +2009,7 @@ impl Sandbox {
19892009

19902010
let exit_code = Arc::new(AtomicI32::new(0));
19912011
let mut tools = build_tools(None, preopens)?.unwrap_or_default();
1992-
register_internal_tools(&mut tools, &exit_code, network, listen_ports);
2012+
let socket_table = register_internal_tools(&mut tools, &exit_code, network, listen_ports);
19932013
let tools = Arc::new(tools);
19942014
let tools_ref = tools.clone();
19952015

@@ -2011,6 +2031,7 @@ impl Sandbox {
20112031
file_mapping_path: initrd,
20122032
file_mapping_base: INITRD_MAP_BASE,
20132033
exit_code,
2034+
socket_table,
20142035
})
20152036
}
20162037
}
@@ -2897,4 +2918,28 @@ mod tests {
28972918
"SO_TYPE should return 2 (DGRAM), got: {resp}"
28982919
);
28992920
}
2921+
2922+
// -- SocketTable lifecycle tests ---------------------------------------------
2923+
2924+
#[test]
2925+
fn net_socket_cap() {
2926+
let mut table = SocketTable::new();
2927+
for _ in 0..MAX_SOCKETS {
2928+
let sock = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
2929+
table.insert(HostSocket::Socket(sock, 1)).unwrap();
2930+
}
2931+
let sock = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
2932+
assert!(table.insert(HostSocket::Socket(sock, 1)).is_err());
2933+
}
2934+
2935+
#[test]
2936+
fn socket_table_clear() {
2937+
let mut table = SocketTable::new();
2938+
let sock = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
2939+
table.insert(HostSocket::Socket(sock, 1)).unwrap();
2940+
assert_eq!(table.sockets.len(), 1);
2941+
table.clear();
2942+
assert_eq!(table.sockets.len(), 0);
2943+
assert_eq!(table.next_id, 1);
2944+
}
29002945
}

0 commit comments

Comments
 (0)