Skip to content

Commit 5cbe18f

Browse files
authored
fix: cap fs_read_bytes allocation and __hl_sleep duration (#33)
Signed-off-by: danbugs <danilochiarlone@gmail.com>
1 parent e7cc11f commit 5cbe18f

1 file changed

Lines changed: 48 additions & 3 deletions

File tree

host/src/lib.rs

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,12 @@ const PAGE_SIZE: usize = 4096;
9191
/// Reject these early on the host before we even boot the guest.
9292
const RESERVED_GUEST_MOUNTPOINTS: &[&str] = &["/", "/bin", "/dev", "/proc", "/sys", "/usr"];
9393

94+
/// Cap for `fs_read_bytes` allocation to prevent guest-controlled OOM (16 MiB).
95+
const MAX_FS_READ: u64 = 16 * 1024 * 1024;
96+
97+
/// Cap for `__hl_sleep` duration to prevent unbounded host-thread blocking (60 s).
98+
const MAX_SLEEP_NS: u64 = 60_000_000_000;
99+
94100
/// A preopened host directory exposed to the guest.
95101
///
96102
/// Semantics mirror Wasmtime's `preopened_dir`: `host_dir` is canonicalised
@@ -888,7 +894,7 @@ impl FsSandbox {
888894
.as_str()
889895
.ok_or_else(|| anyhow!("fs_read_bytes: missing 'path'"))?;
890896
let offset = args["offset"].as_u64().unwrap_or(0);
891-
let want = args["len"].as_u64().unwrap_or(65536);
897+
let want = args["len"].as_u64().unwrap_or(65536).min(MAX_FS_READ);
892898
let target = s.resolve(path)?;
893899
let mut f = std::fs::File::open(&target)
894900
.map_err(|e| anyhow!("fs_read_bytes {:?}: {}", path, e))?;
@@ -1013,7 +1019,7 @@ fn register_internal_tools(
10131019
Ok(serde_json::json!({}))
10141020
});
10151021
tools.register("__hl_sleep", |args| {
1016-
let ns = args["ns"].as_u64().unwrap_or(0);
1022+
let ns = args["ns"].as_u64().unwrap_or(0).min(MAX_SLEEP_NS);
10171023
if ns > 0 {
10181024
std::thread::sleep(std::time::Duration::from_nanos(ns));
10191025
}
@@ -1492,7 +1498,7 @@ impl FsRouter {
14921498
.as_str()
14931499
.ok_or_else(|| anyhow!("fs_read_bytes: missing 'path'"))?;
14941500
let offset = args["offset"].as_u64().unwrap_or(0);
1495-
let want = args["len"].as_u64().unwrap_or(65536);
1501+
let want = args["len"].as_u64().unwrap_or(65536).min(MAX_FS_READ);
14961502
let (fs, rel) = r.route(path)?;
14971503
let target = fs.resolve(rel)?;
14981504
let mut f = std::fs::File::open(&target)
@@ -2790,4 +2796,43 @@ mod tests {
27902796
assert!(s.contains("\"error\""), "net_bind should be denied: {s}");
27912797
assert!(s.contains("Permission denied"), "{s}");
27922798
}
2799+
2800+
// -- Resource-limit tests ---------------------------------------------------
2801+
2802+
#[test]
2803+
fn test_fs_read_bytes_capped() {
2804+
// Request a huge len (well above MAX_FS_READ) on a small file.
2805+
// Without the cap this would try to allocate terabytes and OOM.
2806+
let root = tmpdir("readcap");
2807+
fs::write(root.join("small.bin"), b"hello").unwrap();
2808+
let mut reg = ToolRegistry::new();
2809+
FsSandbox::new(&root).unwrap().register(&mut reg);
2810+
2811+
// Ask for 1 TiB — the cap should silently clamp to MAX_FS_READ.
2812+
let req = br#"{"name":"fs_read_bytes","args":{"path":"small.bin","len":1099511627776}}"#;
2813+
let resp = reg.dispatch(req);
2814+
let s = std::str::from_utf8(&resp).unwrap();
2815+
assert!(!s.contains("\"error\""), "should succeed: {s}");
2816+
assert!(s.contains("\"bytes_read\":5"), "{s}");
2817+
}
2818+
2819+
#[test]
2820+
fn test_sleep_capped() {
2821+
// Verify the cap constant and that sleeping with a huge value
2822+
// completes quickly (the cap brings it down to 60 s max, but we
2823+
// pass 0 to keep the test instant — the important thing is
2824+
// confirming the cap constant exists and has the right value).
2825+
assert_eq!(MAX_SLEEP_NS, 60_000_000_000);
2826+
2827+
// Dispatch a sleep with ns=0 through the real handler to confirm
2828+
// the code path works.
2829+
let mut tools = ToolRegistry::new();
2830+
let exit_code = Arc::new(AtomicI32::new(0));
2831+
register_internal_tools(&mut tools, &exit_code, None, None);
2832+
2833+
let req = br#"{"name":"__hl_sleep","args":{"ns":0}}"#;
2834+
let resp = tools.dispatch(req);
2835+
let s = std::str::from_utf8(&resp).unwrap();
2836+
assert!(!s.contains("\"error\""), "sleep(0) should succeed: {s}");
2837+
}
27932838
}

0 commit comments

Comments
 (0)