Skip to content

Commit 2ad29b8

Browse files
authored
Merge pull request #35 from hyperlight-dev/fix/bugs
fix: correct SO_TYPE for DGRAM sockets, stderr race, socket ID wrap
2 parents b230d50 + 3410596 commit 2ad29b8

2 files changed

Lines changed: 103 additions & 47 deletions

File tree

host/src/lib.rs

Lines changed: 70 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,12 +1098,12 @@ use std::net::SocketAddr;
10981098
use std::sync::Mutex;
10991099

11001100
enum HostSocket {
1101-
Socket(Socket),
1101+
Socket(Socket, i32),
11021102
}
11031103

11041104
struct SocketTable {
1105-
sockets: HashMap<u32, HostSocket>,
1106-
next_id: u32,
1105+
sockets: HashMap<u64, HostSocket>,
1106+
next_id: u64,
11071107
}
11081108

11091109
impl SocketTable {
@@ -1114,26 +1114,32 @@ impl SocketTable {
11141114
}
11151115
}
11161116

1117-
fn insert(&mut self, sock: HostSocket) -> u32 {
1117+
fn insert(&mut self, sock: HostSocket) -> u64 {
11181118
let id = self.next_id;
11191119
self.next_id += 1;
11201120
self.sockets.insert(id, sock);
11211121
id
11221122
}
11231123

1124-
fn get(&self, fd: u32) -> Result<&HostSocket> {
1124+
fn get(&self, fd: u64) -> Result<&HostSocket> {
11251125
self.sockets
11261126
.get(&fd)
11271127
.ok_or_else(|| anyhow!("bad_fd: {}", fd))
11281128
}
11291129

1130-
fn get_socket(&self, fd: u32) -> Result<&Socket> {
1130+
fn get_socket(&self, fd: u64) -> Result<&Socket> {
11311131
match self.get(fd)? {
1132-
HostSocket::Socket(s) => Ok(s),
1132+
HostSocket::Socket(s, _) => Ok(s),
11331133
}
11341134
}
11351135

1136-
fn remove(&mut self, fd: u32) -> Result<()> {
1136+
fn get_sock_type(&self, fd: u64) -> Result<i32> {
1137+
match self.get(fd)? {
1138+
HostSocket::Socket(_, t) => Ok(*t),
1139+
}
1140+
}
1141+
1142+
fn remove(&mut self, fd: u64) -> Result<()> {
11371143
self.sockets
11381144
.remove(&fd)
11391145
.map(|_| ())
@@ -1196,15 +1202,18 @@ fn register_net_tools(
11961202
Some(Protocol::from(protocol))
11971203
};
11981204
let sock = Socket::new(domain, stype, proto)?;
1199-
let fd = t.lock().unwrap().insert(HostSocket::Socket(sock));
1205+
let fd = t
1206+
.lock()
1207+
.unwrap()
1208+
.insert(HostSocket::Socket(sock, sock_type));
12001209
Ok(json!({ "fd": fd }))
12011210
});
12021211

12031212
// net_connect
12041213
let t = table.clone();
12051214
let pol = policy.clone();
12061215
tools.register("net_connect", move |args| {
1207-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1216+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
12081217
let addr = parse_sockaddr(&args)?;
12091218
pol.check(&addr)?;
12101219
let sa: SockAddr = addr.into();
@@ -1218,7 +1227,7 @@ fn register_net_tools(
12181227
let t = table.clone();
12191228
let lp = listen_ports.cloned().map(Arc::new);
12201229
tools.register("net_bind", move |args| {
1221-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1230+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
12221231
let addr = parse_sockaddr(&args)?;
12231232
match lp.as_ref() {
12241233
Some(ports) => ports.check(addr.port())?,
@@ -1234,7 +1243,7 @@ fn register_net_tools(
12341243
// net_listen
12351244
let t = table.clone();
12361245
tools.register("net_listen", move |args| {
1237-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1246+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
12381247
let backlog = args["backlog"].as_i64().unwrap_or(128) as i32;
12391248
let tbl = t.lock().unwrap();
12401249
let sock = tbl.get_socket(fd)?;
@@ -1245,14 +1254,19 @@ fn register_net_tools(
12451254
// net_accept
12461255
let t = table.clone();
12471256
tools.register("net_accept", move |args| {
1248-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1249-
let (new_sock, peer) = {
1257+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
1258+
let (new_sock, peer, parent_type) = {
12501259
let tbl = t.lock().unwrap();
12511260
let sock = tbl.get_socket(fd)?;
1252-
sock.accept()?
1261+
let (s, p) = sock.accept()?;
1262+
let st = tbl.get_sock_type(fd)?;
1263+
(s, p, st)
12531264
};
12541265
let peer_addr: Option<SocketAddr> = peer.as_socket();
1255-
let new_fd = t.lock().unwrap().insert(HostSocket::Socket(new_sock));
1266+
let new_fd = t
1267+
.lock()
1268+
.unwrap()
1269+
.insert(HostSocket::Socket(new_sock, parent_type));
12561270
let mut resp = json!({ "fd": new_fd });
12571271
if let Some(pa) = peer_addr {
12581272
resp["addr"] = json!(pa.ip().to_string());
@@ -1264,7 +1278,7 @@ fn register_net_tools(
12641278
// net_send
12651279
let t = table.clone();
12661280
tools.register("net_send", move |args| {
1267-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1281+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
12681282
let data_b64 = args["data"]
12691283
.as_str()
12701284
.ok_or_else(|| anyhow!("missing 'data'"))?;
@@ -1281,7 +1295,7 @@ fn register_net_tools(
12811295
let t = table.clone();
12821296
let pol = policy.clone();
12831297
tools.register("net_sendto", move |args| {
1284-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1298+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
12851299
let data_b64 = args["data"]
12861300
.as_str()
12871301
.ok_or_else(|| anyhow!("missing 'data'"))?;
@@ -1300,7 +1314,7 @@ fn register_net_tools(
13001314
// net_recv (alias for net_recvfrom with no addr returned for stream)
13011315
let t = table.clone();
13021316
tools.register("net_recv", move |args| {
1303-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1317+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
13041318
let len = args["len"].as_u64().unwrap_or(4096) as usize;
13051319
let mut buf = vec![std::mem::MaybeUninit::uninit(); len.min(65536)];
13061320
let tbl = t.lock().unwrap();
@@ -1317,7 +1331,7 @@ fn register_net_tools(
13171331
// net_recvfrom
13181332
let t = table.clone();
13191333
tools.register("net_recvfrom", move |args| {
1320-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1334+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
13211335
let len = args["len"].as_u64().unwrap_or(4096) as usize;
13221336
let mut buf = vec![0u8; len.min(65536)];
13231337

@@ -1342,15 +1356,15 @@ fn register_net_tools(
13421356
// net_close
13431357
let t = table.clone();
13441358
tools.register("net_close", move |args| {
1345-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1359+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
13461360
t.lock().unwrap().remove(fd)?;
13471361
Ok(json!({}))
13481362
});
13491363

13501364
// net_shutdown
13511365
let t = table.clone();
13521366
tools.register("net_shutdown", move |args| {
1353-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1367+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
13541368
let how = args["how"].as_i64().unwrap_or(2) as i32;
13551369
let shutdown = match how {
13561370
0 => std::net::Shutdown::Read,
@@ -1366,7 +1380,7 @@ fn register_net_tools(
13661380
// net_setsockopt
13671381
let t = table.clone();
13681382
tools.register("net_setsockopt", move |args| {
1369-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1383+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
13701384
let level = args["level"].as_i64().unwrap_or(0) as i32;
13711385
let optname = args["optname"].as_i64().unwrap_or(0) as i32;
13721386
let value = args["value"].as_i64().unwrap_or(0) as i32;
@@ -1390,14 +1404,14 @@ fn register_net_tools(
13901404
// net_getsockopt
13911405
let t = table.clone();
13921406
tools.register("net_getsockopt", move |args| {
1393-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1407+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
13941408
let level = args["level"].as_i64().unwrap_or(0) as i32;
13951409
let optname = args["optname"].as_i64().unwrap_or(0) as i32;
13961410
let tbl = t.lock().unwrap();
13971411
let sock = tbl.get_socket(fd)?;
13981412
let val: i32 = if level == 1 && optname == 3 {
1399-
// SOL_SOCKET + SO_TYPE — all our sockets are SOCK_STREAM
1400-
1
1413+
// SOL_SOCKET + SO_TYPE — return the actual socket type
1414+
tbl.get_sock_type(fd)?
14011415
} else if level == 1 && optname == 2 {
14021416
sock.reuse_address()? as i32
14031417
} else if level == 6 && optname == 1 {
@@ -1411,7 +1425,7 @@ fn register_net_tools(
14111425
// net_getpeername
14121426
let t = table.clone();
14131427
tools.register("net_getpeername", move |args| {
1414-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1428+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
14151429
let tbl = t.lock().unwrap();
14161430
let sock = tbl.get_socket(fd)?;
14171431
let peer = sock.peer_addr()?;
@@ -1425,7 +1439,7 @@ fn register_net_tools(
14251439
// net_getsockname
14261440
let t = table.clone();
14271441
tools.register("net_getsockname", move |args| {
1428-
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))? as u32;
1442+
let fd = args["fd"].as_u64().ok_or_else(|| anyhow!("missing 'fd'"))?;
14291443
let tbl = t.lock().unwrap();
14301444
let sock = tbl.get_socket(fd)?;
14311445
let local = sock.local_addr()?;
@@ -2267,7 +2281,11 @@ pub fn run_vm_capture_output(
22672281
let setup_time = setup_start.elapsed();
22682282

22692283
// Redirect stderr to a temp file before the call phase
2270-
let capture_file = std::env::temp_dir().join(format!("hl-capture-{}", std::process::id()));
2284+
let capture_file = std::env::temp_dir().join(format!(
2285+
"hl-capture-{}-{:?}",
2286+
std::process::id(),
2287+
std::thread::current().id()
2288+
));
22712289
let capture = stderr_capture::Capture::redirect_to_file(&capture_file)?;
22722290

22732291
// Phase 2: restore + call — application runs and produces output
@@ -2986,14 +3004,11 @@ mod tests {
29863004

29873005
#[test]
29883006
fn test_fs_read_bytes_capped() {
2989-
// Request a huge len (well above MAX_FS_READ) on a small file.
2990-
// Without the cap this would try to allocate terabytes and OOM.
29913007
let root = tmpdir("readcap");
29923008
fs::write(root.join("small.bin"), b"hello").unwrap();
29933009
let mut reg = ToolRegistry::new();
29943010
FsSandbox::new(&root).unwrap().register(&mut reg);
29953011

2996-
// Ask for 1 TiB — the cap should silently clamp to MAX_FS_READ.
29973012
let req = br#"{"name":"fs_read_bytes","args":{"path":"small.bin","len":1099511627776}}"#;
29983013
let resp = reg.dispatch(req);
29993014
let s = std::str::from_utf8(&resp).unwrap();
@@ -3003,14 +3018,8 @@ mod tests {
30033018

30043019
#[test]
30053020
fn test_sleep_capped() {
3006-
// Verify the cap constant and that sleeping with a huge value
3007-
// completes quickly (the cap brings it down to 60 s max, but we
3008-
// pass 0 to keep the test instant — the important thing is
3009-
// confirming the cap constant exists and has the right value).
30103021
assert_eq!(MAX_SLEEP_NS, 60_000_000_000);
30113022

3012-
// Dispatch a sleep with ns=0 through the real handler to confirm
3013-
// the code path works.
30143023
let mut tools = ToolRegistry::new();
30153024
let exit_code = Arc::new(AtomicI32::new(0));
30163025
register_internal_tools(&mut tools, &exit_code, None, None);
@@ -3020,4 +3029,27 @@ mod tests {
30203029
let s = std::str::from_utf8(&resp).unwrap();
30213030
assert!(!s.contains("\"error\""), "sleep(0) should succeed: {s}");
30223031
}
3032+
3033+
#[test]
3034+
fn net_getsockopt_returns_correct_type_for_dgram() {
3035+
let mut reg = ToolRegistry::new();
3036+
let policy = NetworkPolicy::AllowAll;
3037+
register_net_tools(&mut reg, &policy, None);
3038+
3039+
let req = br#"{"name":"net_socket","args":{"family":2,"type":2}}"#;
3040+
let resp = std::str::from_utf8(&reg.dispatch(req)).unwrap().to_string();
3041+
let v: serde_json::Value = serde_json::from_str(&resp).unwrap();
3042+
let fd = v["result"]["fd"].as_u64().unwrap();
3043+
3044+
let req =
3045+
format!(r#"{{"name":"net_getsockopt","args":{{"fd":{fd},"level":1,"optname":3}}}}"#);
3046+
let resp = std::str::from_utf8(&reg.dispatch(req.as_bytes()))
3047+
.unwrap()
3048+
.to_string();
3049+
let v: serde_json::Value = serde_json::from_str(&resp).unwrap();
3050+
assert_eq!(
3051+
v["result"]["value"], 2,
3052+
"SO_TYPE should return 2 (DGRAM), got: {resp}"
3053+
);
3054+
}
30233055
}

host/src/stderr_capture.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,52 @@
88
mod imp {
99
use anyhow::Result;
1010
use nix::unistd;
11-
use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd};
11+
use std::os::fd::{AsRawFd, FromRawFd, OwnedFd};
1212
use std::path::Path;
13+
use std::sync::{Mutex, MutexGuard};
14+
15+
/// Process-wide lock protecting fd 2 (stderr) manipulation.
16+
/// Held from `redirect_to_file` until `restore` to prevent concurrent
17+
/// VMs from racing on dup2.
18+
static STDERR_LOCK: Mutex<()> = Mutex::new(());
1319

1420
pub struct Capture {
15-
original_stderr: OwnedFd,
21+
original_stderr: Option<OwnedFd>,
22+
_guard: MutexGuard<'static, ()>,
1623
}
1724

1825
impl Capture {
1926
pub fn redirect_to_file(path: &Path) -> Result<Self> {
20-
let capture_fd = std::fs::File::create(path)?.into_raw_fd();
21-
let original_stderr_raw = unistd::dup(2)?;
22-
unistd::dup2(capture_fd, 2)?;
23-
unistd::close(capture_fd)?;
24-
let original_stderr = unsafe { OwnedFd::from_raw_fd(original_stderr_raw) };
25-
Ok(Self { original_stderr })
27+
let guard = STDERR_LOCK
28+
.lock()
29+
.unwrap_or_else(|poisoned| poisoned.into_inner());
30+
let capture_file = std::fs::File::create(path)?;
31+
let original_stderr = unistd::dup(2).map(|fd| unsafe { OwnedFd::from_raw_fd(fd) })?;
32+
unistd::dup2(capture_file.as_raw_fd(), 2)?;
33+
// capture_file dropped here — its OwnedFd closes the fd via RAII
34+
Ok(Self {
35+
original_stderr: Some(original_stderr),
36+
_guard: guard,
37+
})
2638
}
2739

2840
pub fn restore(self) -> Result<()> {
29-
unistd::dup2(self.original_stderr.as_raw_fd(), 2)?;
41+
if let Some(ref fd) = self.original_stderr {
42+
unistd::dup2(fd.as_raw_fd(), 2)?;
43+
}
44+
// Drop restores via Drop impl if restore() wasn't called,
45+
// but explicit call lets caller handle errors.
3046
Ok(())
3147
}
3248
}
49+
50+
impl Drop for Capture {
51+
fn drop(&mut self) {
52+
if let Some(ref fd) = self.original_stderr.take() {
53+
let _ = unistd::dup2(fd.as_raw_fd(), 2);
54+
}
55+
}
56+
}
3357
}
3458

3559
#[cfg(windows)]

0 commit comments

Comments
 (0)