@@ -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).
9494const 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).
97100const 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