Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ const RESERVED_GUEST_MOUNTPOINTS: &[&str] = &["/", "/bin", "/dev", "/proc", "/sy
/// Cap for `fs_read_bytes` allocation to prevent guest-controlled OOM (16 MiB).
const MAX_FS_READ: u64 = 16 * 1024 * 1024;

/// Cap for `net_send`/`net_sendto` decoded payload (1 MiB).
const MAX_NET_SEND: usize = 1024 * 1024;

/// Cap for `__hl_sleep` duration to prevent unbounded host-thread blocking (60 s).
const MAX_SLEEP_NS: u64 = 60_000_000_000;

Expand Down Expand Up @@ -1264,6 +1267,13 @@ fn register_net_tools(
let data = base64::engine::general_purpose::STANDARD
.decode(data_b64)
.map_err(|e| anyhow!("base64 decode: {}", e))?;
if data.len() > MAX_NET_SEND {
return Err(anyhow!(
"net_send: payload too large ({} bytes, max {})",
data.len(),
MAX_NET_SEND
));
}
let tbl = t.lock().unwrap();
let sock = tbl.get_socket(fd)?;
let sent = sock.send(&data)?;
Expand All @@ -1281,6 +1291,13 @@ fn register_net_tools(
let data = base64::engine::general_purpose::STANDARD
.decode(data_b64)
.map_err(|e| anyhow!("base64 decode: {}", e))?;
if data.len() > MAX_NET_SEND {
return Err(anyhow!(
"net_sendto: payload too large ({} bytes, max {})",
data.len(),
MAX_NET_SEND
));
}
let addr = parse_sockaddr(&args)?;
pol.check(&addr)?;
let sa: SockAddr = addr.into();
Expand Down Expand Up @@ -1490,6 +1507,17 @@ impl FsRouter {
.ok_or_else(|| anyhow!("fs_read: missing 'path'"))?;
let (fs, rel) = r.route(path)?;
let target = fs.resolve(rel)?;
let size = std::fs::metadata(&target)
.map_err(|e| anyhow!("fs_read {:?}: {}", path, e))?
.len();
if size > MAX_FS_READ {
return Err(anyhow!(
"fs_read {:?}: file too large ({} bytes, max {})",
path,
size,
MAX_FS_READ
));
}
let text = std::fs::read_to_string(&target)
.map_err(|e| anyhow!("fs_read {:?}: {}", path, e))?;
Ok(json!({ "text": text }))
Expand Down Expand Up @@ -3149,4 +3177,80 @@ mod tests {
assert_eq!(table.sockets.len(), 0);
assert_eq!(table.next_id, 1);
}

#[test]
fn test_fs_read_size_cap() {
use std::io::Write;

let root = tmpdir("fs_read_cap");
// Create a file that exceeds MAX_FS_READ (16 MiB) using a sparse file
let big_path = root.join("big.txt");
let f = std::fs::File::create(&big_path).unwrap();
f.set_len(MAX_FS_READ + 1).unwrap();
drop(f);

// Also create a small file to verify normal reads work
let small_path = root.join("small.txt");
let mut sf = std::fs::File::create(&small_path).unwrap();
sf.write_all(b"hello").unwrap();
drop(sf);

let preopens = vec![Preopen::new(&root, "/host").unwrap()];
let mut reg = ToolRegistry::new();
FsRouter::new(&preopens).unwrap().register(&mut reg);

// Small file should succeed
let req = br#"{"name":"fs_read","args":{"path":"/host/small.txt"}}"#;
let resp = reg.dispatch(req);
let s = std::str::from_utf8(&resp).unwrap();
assert!(!s.contains("\"error\""), "small read should succeed: {s}");

// Large file should fail with "too large"
let req = br#"{"name":"fs_read","args":{"path":"/host/big.txt"}}"#;
let resp = reg.dispatch(req);
let s = std::str::from_utf8(&resp).unwrap();
assert!(
s.contains("too large"),
"expected 'too large' error, got: {s}"
);
}

#[test]
fn test_net_send_size_cap() {
use base64::Engine;

let mut reg = ToolRegistry::new();
let policy = NetworkPolicy::AllowAll;
register_net_tools(&mut reg, &policy, None);

// Create a socket
let req = br#"{"name":"net_socket","args":{"family":2,"type":2}}"#;
let resp = std::str::from_utf8(&reg.dispatch(req)).unwrap().to_string();
let v: serde_json::Value = serde_json::from_str(&resp).unwrap();
let fd = v["result"]["fd"].as_u64().unwrap();

// Create payload larger than MAX_NET_SEND (1 MiB)
let big_payload = vec![0u8; MAX_NET_SEND + 1];
let b64 = base64::engine::general_purpose::STANDARD.encode(&big_payload);
let req = format!(r#"{{"name":"net_send","args":{{"fd":{fd},"data":"{b64}"}}}}"#);
let resp = std::str::from_utf8(&reg.dispatch(req.as_bytes()))
.unwrap()
.to_string();
assert!(
resp.contains("too large"),
"expected 'too large' error for net_send, got: {resp}"
);

// Also test net_sendto
let req = format!(
r#"{{"name":"net_sendto","args":{{"fd":{fd},"data":"{b64}","addr":"127.0.0.1","port":9999}}}}"#
);
let resp = std::str::from_utf8(&reg.dispatch(req.as_bytes()))
.unwrap()
.to_string();
assert!(
resp.contains("too large"),
"expected 'too large' error for net_sendto, got: {resp}"
);
}
}
Loading