Skip to content

Commit c6128d7

Browse files
authored
Merge pull request #55 from hyperlight-dev/fix/socket-table-lifecycle
fix: cap SocketTable size and clear on restore
2 parents e2d16db + 56f8710 commit c6128d7

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
@@ -910,7 +910,7 @@ fn register_internal_tools(
910910
exit_code: &Arc<AtomicI32>,
911911
network: Option<&NetworkPolicy>,
912912
listen_ports: Option<&ListenPorts>,
913-
) {
913+
) -> Option<Arc<Mutex<SocketTable>>> {
914914
let ec = exit_code.clone();
915915
tools.register("__hl_exit", move |args| {
916916
let code = args["code"].as_i64().unwrap_or(1) as i32;
@@ -924,9 +924,7 @@ fn register_internal_tools(
924924
}
925925
Ok(serde_json::json!({}))
926926
});
927-
if let Some(policy) = network {
928-
register_net_tools(tools, policy, listen_ports);
929-
}
927+
network.map(|policy| register_net_tools(tools, policy, listen_ports))
930928
}
931929

932930
// ---------------------------------------------------------------------------
@@ -941,6 +939,8 @@ enum HostSocket {
941939
Socket(Socket, i32),
942940
}
943941

942+
const MAX_SOCKETS: usize = 1024;
943+
944944
struct SocketTable {
945945
sockets: HashMap<u64, HostSocket>,
946946
next_id: u64,
@@ -954,11 +954,19 @@ impl SocketTable {
954954
}
955955
}
956956

957-
fn insert(&mut self, sock: HostSocket) -> u64 {
957+
fn insert(&mut self, sock: HostSocket) -> Result<u64> {
958+
if self.sockets.len() >= MAX_SOCKETS {
959+
return Err(anyhow!("socket limit reached (max {})", MAX_SOCKETS));
960+
}
958961
let id = self.next_id;
959962
self.next_id += 1;
960963
self.sockets.insert(id, sock);
961-
id
964+
Ok(id)
965+
}
966+
967+
fn clear(&mut self) {
968+
self.sockets.clear();
969+
self.next_id = 1;
962970
}
963971

964972
fn get(&self, fd: u64) -> Result<&HostSocket> {
@@ -1012,7 +1020,7 @@ fn register_net_tools(
10121020
tools: &mut ToolRegistry,
10131021
policy: &NetworkPolicy,
10141022
listen_ports: Option<&ListenPorts>,
1015-
) {
1023+
) -> Arc<Mutex<SocketTable>> {
10161024
use base64::Engine;
10171025
use serde_json::json;
10181026

@@ -1045,7 +1053,7 @@ fn register_net_tools(
10451053
let fd = t
10461054
.lock()
10471055
.unwrap()
1048-
.insert(HostSocket::Socket(sock, sock_type));
1056+
.insert(HostSocket::Socket(sock, sock_type))?;
10491057
Ok(json!({ "fd": fd }))
10501058
});
10511059

@@ -1106,7 +1114,7 @@ fn register_net_tools(
11061114
let new_fd = t
11071115
.lock()
11081116
.unwrap()
1109-
.insert(HostSocket::Socket(new_sock, parent_type));
1117+
.insert(HostSocket::Socket(new_sock, parent_type))?;
11101118
let mut resp = json!({ "fd": new_fd });
11111119
if let Some(pa) = peer_addr {
11121120
resp["addr"] = json!(pa.ip().to_string());
@@ -1288,6 +1296,8 @@ fn register_net_tools(
12881296
Ok(json!({ "addr": "0.0.0.0", "port": 0 }))
12891297
}
12901298
});
1299+
1300+
table
12911301
}
12921302

12931303
/// Routes incoming fs_* tool calls to the matching `FsSandbox` by
@@ -1541,6 +1551,9 @@ pub struct Sandbox {
15411551
file_mapping_path: Option<std::path::PathBuf>,
15421552
file_mapping_base: u64,
15431553
exit_code: Arc<AtomicI32>,
1554+
/// Shared socket table — cleared on [`Sandbox::restore`] so that
1555+
/// host-side fds don't leak across guest restore cycles.
1556+
socket_table: Option<Arc<Mutex<SocketTable>>>,
15441557
}
15451558

15461559
/// Where the initrd comes from — either a file (zero-copy `map_file_cow`)
@@ -1750,14 +1763,14 @@ impl Sandbox {
17501763

17511764
let exit_code = Arc::new(AtomicI32::new(0));
17521765
let mut tools = build_tools(tools, preopens)?.unwrap_or_default();
1753-
register_internal_tools(&mut tools, &exit_code, network, listen_ports);
1766+
let socket_table = register_internal_tools(&mut tools, &exit_code, network, listen_ports);
17541767
let tools = Arc::new(tools);
17551768
let tools_ref = tools.clone();
17561769
usbox.register_host_function("__dispatch", move |payload: Vec<u8>| -> Vec<u8> {
17571770
tools_ref.dispatch(&payload)
17581771
})?;
17591772

1760-
Self::finish_evolve(usbox, None, 0, exit_code)
1773+
Self::finish_evolve(usbox, None, 0, exit_code, socket_table)
17611774
}
17621775

17631776
/// Low-level: boot with a zero-copy mapped initrd file. Prefer the builder.
@@ -1802,7 +1815,7 @@ impl Sandbox {
18021815

18031816
let exit_code = Arc::new(AtomicI32::new(0));
18041817
let mut tools = build_tools(tools, preopens)?.unwrap_or_default();
1805-
register_internal_tools(&mut tools, &exit_code, network, listen_ports);
1818+
let socket_table = register_internal_tools(&mut tools, &exit_code, network, listen_ports);
18061819
let tools = Arc::new(tools);
18071820
let tools_ref = tools.clone();
18081821
usbox.register_host_function("__dispatch", move |payload: Vec<u8>| -> Vec<u8> {
@@ -1814,6 +1827,7 @@ impl Sandbox {
18141827
initrd_path.map(|p| p.to_path_buf()),
18151828
INITRD_MAP_BASE,
18161829
exit_code,
1830+
socket_table,
18171831
)
18181832
}
18191833

@@ -1822,6 +1836,7 @@ impl Sandbox {
18221836
file_mapping_path: Option<std::path::PathBuf>,
18231837
file_mapping_base: u64,
18241838
exit_code: Arc<AtomicI32>,
1839+
socket_table: Option<Arc<Mutex<SocketTable>>>,
18251840
) -> Result<Self> {
18261841
let mut inner = usbox.evolve()?;
18271842
let snapshot = inner.snapshot().ok();
@@ -1831,6 +1846,7 @@ impl Sandbox {
18311846
file_mapping_path,
18321847
file_mapping_base,
18331848
exit_code,
1849+
socket_table,
18341850
})
18351851
}
18361852

@@ -1848,6 +1864,10 @@ impl Sandbox {
18481864
self.inner
18491865
.map_file_cow(path, self.file_mapping_base, Some("initrd"))?;
18501866
}
1867+
// Close leaked host-side sockets the guest "forgot" about.
1868+
if let Some(ref table) = self.socket_table {
1869+
table.lock().unwrap().clear();
1870+
}
18511871
Ok(())
18521872
}
18531873

@@ -2007,7 +2027,7 @@ impl Sandbox {
20072027

20082028
let exit_code = Arc::new(AtomicI32::new(0));
20092029
let mut tools = build_tools(None, preopens)?.unwrap_or_default();
2010-
register_internal_tools(&mut tools, &exit_code, network, listen_ports);
2030+
let socket_table = register_internal_tools(&mut tools, &exit_code, network, listen_ports);
20112031
let tools = Arc::new(tools);
20122032
let tools_ref = tools.clone();
20132033

@@ -2029,6 +2049,7 @@ impl Sandbox {
20292049
file_mapping_path: initrd,
20302050
file_mapping_base: INITRD_MAP_BASE,
20312051
exit_code,
2052+
socket_table,
20322053
})
20332054
}
20342055
}
@@ -2946,4 +2967,28 @@ mod tests {
29462967
"SO_TYPE should return 2 (DGRAM), got: {resp}"
29472968
);
29482969
}
2970+
2971+
// -- SocketTable lifecycle tests ---------------------------------------------
2972+
2973+
#[test]
2974+
fn net_socket_cap() {
2975+
let mut table = SocketTable::new();
2976+
for _ in 0..MAX_SOCKETS {
2977+
let sock = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
2978+
table.insert(HostSocket::Socket(sock, 1)).unwrap();
2979+
}
2980+
let sock = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
2981+
assert!(table.insert(HostSocket::Socket(sock, 1)).is_err());
2982+
}
2983+
2984+
#[test]
2985+
fn socket_table_clear() {
2986+
let mut table = SocketTable::new();
2987+
let sock = Socket::new(Domain::IPV4, Type::STREAM, None).unwrap();
2988+
table.insert(HostSocket::Socket(sock, 1)).unwrap();
2989+
assert_eq!(table.sockets.len(), 1);
2990+
table.clear();
2991+
assert_eq!(table.sockets.len(), 0);
2992+
assert_eq!(table.next_id, 1);
2993+
}
29492994
}

0 commit comments

Comments
 (0)