Skip to content

Commit c9d37ee

Browse files
authored
fix: add size caps to fs_read and net_send/net_sendto (#60)
Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent a9913ef commit c9d37ee

1 file changed

Lines changed: 104 additions & 0 deletions

File tree

host/src/lib.rs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ const RESERVED_GUEST_MOUNTPOINTS: &[&str] = &["/", "/bin", "/dev", "/proc", "/sy
9393
/// Cap for `fs_read_bytes` allocation to prevent guest-controlled OOM (16 MiB).
9494
const MAX_FS_READ: u64 = 16 * 1024 * 1024;
9595

96+
/// Cap for `net_send`/`net_sendto` decoded payload (1 MiB).
97+
const MAX_NET_SEND: usize = 1024 * 1024;
98+
9699
/// Cap for `__hl_sleep` duration to prevent unbounded host-thread blocking (60 s).
97100
const MAX_SLEEP_NS: u64 = 60_000_000_000;
98101

@@ -1261,6 +1264,13 @@ fn register_net_tools(
12611264
let data = base64::engine::general_purpose::STANDARD
12621265
.decode(data_b64)
12631266
.map_err(|e| anyhow!("base64 decode: {}", e))?;
1267+
if data.len() > MAX_NET_SEND {
1268+
return Err(anyhow!(
1269+
"net_send: payload too large ({} bytes, max {})",
1270+
data.len(),
1271+
MAX_NET_SEND
1272+
));
1273+
}
12641274
let tbl = t.lock().unwrap();
12651275
let sock = tbl.get_socket(fd)?;
12661276
let sent = sock.send(&data)?;
@@ -1278,6 +1288,13 @@ fn register_net_tools(
12781288
let data = base64::engine::general_purpose::STANDARD
12791289
.decode(data_b64)
12801290
.map_err(|e| anyhow!("base64 decode: {}", e))?;
1291+
if data.len() > MAX_NET_SEND {
1292+
return Err(anyhow!(
1293+
"net_sendto: payload too large ({} bytes, max {})",
1294+
data.len(),
1295+
MAX_NET_SEND
1296+
));
1297+
}
12811298
let addr = parse_sockaddr(&args)?;
12821299
pol.check(&addr)?;
12831300
let sa: SockAddr = addr.into();
@@ -1487,6 +1504,17 @@ impl FsRouter {
14871504
.ok_or_else(|| anyhow!("fs_read: missing 'path'"))?;
14881505
let (fs, rel) = r.route(path)?;
14891506
let target = fs.resolve(rel)?;
1507+
let size = std::fs::metadata(&target)
1508+
.map_err(|e| anyhow!("fs_read {:?}: {}", path, e))?
1509+
.len();
1510+
if size > MAX_FS_READ {
1511+
return Err(anyhow!(
1512+
"fs_read {:?}: file too large ({} bytes, max {})",
1513+
path,
1514+
size,
1515+
MAX_FS_READ
1516+
));
1517+
}
14901518
let text = std::fs::read_to_string(&target)
14911519
.map_err(|e| anyhow!("fs_read {:?}: {}", path, e))?;
14921520
Ok(json!({ "text": text }))
@@ -3110,4 +3138,80 @@ mod tests {
31103138
assert_eq!(table.sockets.len(), 0);
31113139
assert_eq!(table.next_id, 1);
31123140
}
3141+
3142+
#[test]
3143+
fn test_fs_read_size_cap() {
3144+
use std::io::Write;
3145+
3146+
let root = tmpdir("fs_read_cap");
3147+
// Create a file that exceeds MAX_FS_READ (16 MiB) using a sparse file
3148+
let big_path = root.join("big.txt");
3149+
let f = std::fs::File::create(&big_path).unwrap();
3150+
f.set_len(MAX_FS_READ + 1).unwrap();
3151+
drop(f);
3152+
3153+
// Also create a small file to verify normal reads work
3154+
let small_path = root.join("small.txt");
3155+
let mut sf = std::fs::File::create(&small_path).unwrap();
3156+
sf.write_all(b"hello").unwrap();
3157+
drop(sf);
3158+
3159+
let preopens = vec![Preopen::new(&root, "/host").unwrap()];
3160+
let mut reg = ToolRegistry::new();
3161+
FsRouter::new(&preopens).unwrap().register(&mut reg);
3162+
3163+
// Small file should succeed
3164+
let req = br#"{"name":"fs_read","args":{"path":"/host/small.txt"}}"#;
3165+
let resp = reg.dispatch(req);
3166+
let s = std::str::from_utf8(&resp).unwrap();
3167+
assert!(!s.contains("\"error\""), "small read should succeed: {s}");
3168+
3169+
// Large file should fail with "too large"
3170+
let req = br#"{"name":"fs_read","args":{"path":"/host/big.txt"}}"#;
3171+
let resp = reg.dispatch(req);
3172+
let s = std::str::from_utf8(&resp).unwrap();
3173+
assert!(
3174+
s.contains("too large"),
3175+
"expected 'too large' error, got: {s}"
3176+
);
3177+
}
3178+
3179+
#[test]
3180+
fn test_net_send_size_cap() {
3181+
use base64::Engine;
3182+
3183+
let mut reg = ToolRegistry::new();
3184+
let policy = NetworkPolicy::AllowAll;
3185+
register_net_tools(&mut reg, &policy, None);
3186+
3187+
// Create a socket
3188+
let req = br#"{"name":"net_socket","args":{"family":2,"type":2}}"#;
3189+
let resp = std::str::from_utf8(&reg.dispatch(req)).unwrap().to_string();
3190+
let v: serde_json::Value = serde_json::from_str(&resp).unwrap();
3191+
let fd = v["result"]["fd"].as_u64().unwrap();
3192+
3193+
// Create payload larger than MAX_NET_SEND (1 MiB)
3194+
let big_payload = vec![0u8; MAX_NET_SEND + 1];
3195+
let b64 = base64::engine::general_purpose::STANDARD.encode(&big_payload);
3196+
let req = format!(r#"{{"name":"net_send","args":{{"fd":{fd},"data":"{b64}"}}}}"#);
3197+
let resp = std::str::from_utf8(&reg.dispatch(req.as_bytes()))
3198+
.unwrap()
3199+
.to_string();
3200+
assert!(
3201+
resp.contains("too large"),
3202+
"expected 'too large' error for net_send, got: {resp}"
3203+
);
3204+
3205+
// Also test net_sendto
3206+
let req = format!(
3207+
r#"{{"name":"net_sendto","args":{{"fd":{fd},"data":"{b64}","addr":"127.0.0.1","port":9999}}}}"#
3208+
);
3209+
let resp = std::str::from_utf8(&reg.dispatch(req.as_bytes()))
3210+
.unwrap()
3211+
.to_string();
3212+
assert!(
3213+
resp.contains("too large"),
3214+
"expected 'too large' error for net_sendto, got: {resp}"
3215+
);
3216+
}
31133217
}

0 commit comments

Comments
 (0)