1 use super::REALLOC_AND_FREE; 2 use anyhow::Result; 3 use wasmtime::component::{Component, Linker}; 4 use wasmtime::{Engine, Store, StoreContextMut, Trap, TrapCode}; 5 6 const UTF16_TAG: u32 = 1 << 31; 7 8 // Special cases that this tries to test: 9 // 10 // * utf8 -> utf8 11 // * various code point sizes 12 // 13 // * utf8 -> utf16 - the adapter here will make a pessimistic allocation that's 14 // twice the size of the utf8 encoding for the utf16 destination 15 // * utf16 byte size is twice the utf8 size 16 // * utf16 byte size is less than twice the utf8 size 17 // 18 // * utf8 -> latin1+utf16 - attempts to convert to latin1 then falls back to a 19 // pessimistic utf16 allocation that's downsized if necessary 20 // * utf8 fits exactly in latin1 21 // * utf8 fits latin1 but is bigger byte-wise 22 // * utf8 is not latin1 and fits utf16 allocation precisely (NOT POSSIBLE) 23 // * utf8 is not latin1 and utf16 is smaller than allocation 24 // 25 // * utf16 -> utf8 - this starts with an optimistic size and then reallocates to 26 // a pessimistic size, interesting cases are: 27 // * utf8 size is 0.5x the utf16 byte size (perfect fit in initial alloc) 28 // * utf8 size is 1.5x the utf16 byte size (perfect fit in larger alloc) 29 // * utf8 size is 0.5x-1.5x the utf16 size (larger alloc is downsized) 30 // 31 // * utf16 -> utf16 32 // * various code point sizes 33 // 34 // * utf16 -> latin1+utf16 - attempts to convert to latin1 then falls back to a 35 // pessimistic utf16 allocation that's downsized if necessary 36 // * utf16 fits exactly in latin1 37 // * utf16 fits latin1 but is bigger byte-wise (NOT POSSIBLE) 38 // * utf16 is not latin1 and fits utf16 allocation precisely 39 // * utf16 is not latin1 and utf16 is smaller than allocation (NOT POSSIBLE) 40 // 41 // * compact-utf16 -> utf8 dynamically determines between one of 42 // * latin1 -> utf8 43 // * latin1 size matches utf8 size 44 // * latin1 is smaller than utf8 size 45 // * utf16 -> utf8 46 // * covered above 47 // 48 // * compact-utf16 -> utf16 dynamically determines between one of 49 // * latin1 -> utf16 - latin1 size always matches utf16 50 // * test various code points 51 // * utf16 -> utf16 52 // * covered above 53 // 54 // * compact-utf16 -> compact-utf16 dynamically determines between one of 55 // * latin1 -> latin1 56 // * not much interesting here 57 // * utf16 -> compact-utf16-to-compact-probably-utf16 58 // * utf16 actually fits within latin1 59 // * otherwise not more interesting than utf16 -> utf16 60 // 61 const STRINGS: &[&str] = &[ 62 "", 63 // 1 byte in utf8, 2 bytes in utf16 64 "x", 65 "hello this is a particularly long string yes it is it keeps going", 66 // 35 bytes in utf8, 23 units in utf16, 23 bytes in latin1 67 "à á â ã ä å æ ç è é ê ë", 68 // 47 bytes in utf8, 31 units in utf16 69 "Ξ Ο Π Ρ Σ Τ Υ Φ Χ Ψ Ω Ϊ Ϋ ά έ ή", 70 // 24 bytes in utf8, 8 units in utf16 71 "STUVWXYZ", 72 // 16 bytes in utf8, 8 units in utf16 73 "ËÌÍÎÏÐÑÒ", 74 // 4 bytes in utf8, 1 unit in utf16 75 "\u{10000}", 76 // latin1-compatible prefix followed by utf8/16-requiring suffix 77 // 78 // 24 bytes in utf8, 13 units in utf16, first 8 usvs are latin1-compatible 79 "à ascii VWXYZ", 80 ]; 81 82 static ENCODINGS: [&str; 3] = ["utf8", "utf16", "latin1+utf16"]; 83 84 #[test] 85 fn roundtrip() -> Result<()> { 86 for debug in [true, false] { 87 let mut config = component_test_util::config(); 88 config.debug_adapter_modules(debug); 89 let engine = Engine::new(&config)?; 90 for src in ENCODINGS { 91 for dst in ENCODINGS { 92 test_roundtrip(&engine, src, dst)?; 93 } 94 } 95 } 96 Ok(()) 97 } 98 99 fn test_roundtrip(engine: &Engine, src: &str, dst: &str) -> Result<()> { 100 println!("src={src} dst={dst}"); 101 102 let mk_echo = |name: &str, encoding: &str| { 103 format!( 104 r#" 105 (component {name} 106 (import "echo" (func $echo (param string) (result string))) 107 (core instance $libc (instantiate $libc)) 108 (core func $echo (canon lower (func $echo) 109 (memory $libc "memory") 110 (realloc (func $libc "realloc")) 111 string-encoding={encoding} 112 )) 113 (core instance $echo (instantiate $echo 114 (with "libc" (instance $libc)) 115 (with "" (instance (export "echo" (func $echo)))) 116 )) 117 (func (export "echo") (param string) (result string) 118 (canon lift 119 (core func $echo "echo") 120 (memory $libc "memory") 121 (realloc (func $libc "realloc")) 122 string-encoding={encoding} 123 ) 124 ) 125 ) 126 "# 127 ) 128 }; 129 130 let src = mk_echo("$src", src); 131 let dst = mk_echo("$dst", dst); 132 let component = format!( 133 r#" 134 (component 135 (import "host" (func $host (param string) (result string))) 136 137 (core module $libc 138 (memory (export "memory") 1) 139 {REALLOC_AND_FREE} 140 ) 141 (core module $echo 142 (import "" "echo" (func $echo (param i32 i32 i32))) 143 (import "libc" "memory" (memory 0)) 144 (import "libc" "realloc" (func $realloc (param i32 i32 i32 i32) (result i32))) 145 146 (func (export "echo") (param i32 i32) (result i32) 147 (local $retptr i32) 148 (local.set $retptr 149 (call $realloc 150 (i32.const 0) 151 (i32.const 0) 152 (i32.const 4) 153 (i32.const 8))) 154 (call $echo 155 (local.get 0) 156 (local.get 1) 157 (local.get $retptr)) 158 local.get $retptr 159 ) 160 ) 161 162 {src} 163 {dst} 164 165 (instance $dst (instantiate $dst (with "echo" (func $host)))) 166 (instance $src (instantiate $src (with "echo" (func $dst "echo")))) 167 (export "echo" (func $src "echo")) 168 ) 169 "# 170 ); 171 let component = Component::new(engine, &component)?; 172 let mut store = Store::new(engine, String::new()); 173 let mut linker = Linker::new(engine); 174 linker 175 .root() 176 .func_wrap("host", |store: StoreContextMut<String>, arg: String| { 177 assert_eq!(*store.data(), arg); 178 Ok(arg) 179 })?; 180 let instance = linker.instantiate(&mut store, &component)?; 181 let func = instance.get_typed_func::<(String,), String, _>(&mut store, "echo")?; 182 183 for string in STRINGS { 184 println!("testing string {string:?}"); 185 *store.data_mut() = string.to_string(); 186 let ret = func.call(&mut store, (string.to_string(),))?; 187 assert_eq!(ret, *string); 188 func.post_return(&mut store)?; 189 } 190 Ok(()) 191 } 192 193 #[test] 194 fn ptr_out_of_bounds() -> Result<()> { 195 let engine = component_test_util::engine(); 196 for src in ENCODINGS { 197 for dst in ENCODINGS { 198 test_ptr_out_of_bounds(&engine, src, dst)?; 199 } 200 } 201 Ok(()) 202 } 203 204 fn test_ptr_out_of_bounds(engine: &Engine, src: &str, dst: &str) -> Result<()> { 205 let test = |len: u32| -> Result<()> { 206 let component = format!( 207 r#" 208 (component 209 (component $c 210 (core module $m 211 (func (export "") (param i32 i32)) 212 (func (export "realloc") (param i32 i32 i32 i32) (result i32) i32.const 0) 213 (memory (export "memory") 1) 214 ) 215 (core instance $m (instantiate $m)) 216 (func (export "") (param string) 217 (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory") 218 string-encoding={dst}) 219 ) 220 ) 221 222 (component $c2 223 (import "" (func $f (param string))) 224 (core module $libc 225 (memory (export "memory") 1) 226 ) 227 (core instance $libc (instantiate $libc)) 228 (core func $f (canon lower (func $f) string-encoding={src} (memory $libc "memory"))) 229 (core module $m 230 (import "" "" (func $f (param i32 i32))) 231 232 (func $start (call $f (i32.const 0x8000_0000) (i32.const {len}))) 233 (start $start) 234 ) 235 (core instance (instantiate $m (with "" (instance (export "" (func $f)))))) 236 ) 237 238 (instance $c (instantiate $c)) 239 (instance $c2 (instantiate $c2 (with "" (func $c "")))) 240 ) 241 "# 242 ); 243 let component = Component::new(engine, &component)?; 244 let mut store = Store::new(engine, ()); 245 let trap = Linker::new(engine) 246 .instantiate(&mut store, &component) 247 .err() 248 .unwrap() 249 .downcast::<Trap>()?; 250 assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)); 251 Ok(()) 252 }; 253 254 test(0)?; 255 test(1)?; 256 257 Ok(()) 258 } 259 260 // Test that even if the ptr+len calculation overflows then a trap still 261 // happens. 262 #[test] 263 fn ptr_overflow() -> Result<()> { 264 let engine = component_test_util::engine(); 265 for src in ENCODINGS { 266 for dst in ENCODINGS { 267 test_ptr_overflow(&engine, src, dst)?; 268 } 269 } 270 Ok(()) 271 } 272 273 fn test_ptr_overflow(engine: &Engine, src: &str, dst: &str) -> Result<()> { 274 let component = format!( 275 r#" 276 (component 277 (component $c 278 (core module $m 279 (func (export "") (param i32 i32)) 280 (func (export "realloc") (param i32 i32 i32 i32) (result i32) i32.const 0) 281 (memory (export "memory") 1) 282 ) 283 (core instance $m (instantiate $m)) 284 (func (export "") (param string) 285 (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory") 286 string-encoding={dst}) 287 ) 288 ) 289 290 (component $c2 291 (import "" (func $f (param string))) 292 (core module $libc 293 (memory (export "memory") 1) 294 ) 295 (core instance $libc (instantiate $libc)) 296 (core func $f (canon lower (func $f) string-encoding={src} (memory $libc "memory"))) 297 (core module $m 298 (import "" "" (func $f (param i32 i32))) 299 300 (func (export "f") (param i32) (call $f (i32.const 1000) (local.get 0))) 301 ) 302 (core instance $m (instantiate $m (with "" (instance (export "" (func $f)))))) 303 (func (export "f") (param u32) (canon lift (core func $m "f"))) 304 ) 305 306 (instance $c (instantiate $c)) 307 (instance $c2 (instantiate $c2 (with "" (func $c "")))) 308 (export "f" (func $c2 "f")) 309 ) 310 "# 311 ); 312 313 let component = Component::new(engine, &component)?; 314 let mut store = Store::new(engine, ()); 315 316 let mut test_overflow = |size: u32| -> Result<()> { 317 println!("src={src} dst={dst} size={size:#x}"); 318 let instance = Linker::new(engine).instantiate(&mut store, &component)?; 319 let func = instance.get_typed_func::<(u32,), (), _>(&mut store, "f")?; 320 let trap = func 321 .call(&mut store, (size,)) 322 .unwrap_err() 323 .downcast::<Trap>()?; 324 assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)); 325 Ok(()) 326 }; 327 328 let max = 1 << 31; 329 330 match src { 331 "utf8" => { 332 // This exceeds MAX_STRING_BYTE_LENGTH 333 test_overflow(max)?; 334 335 if dst == "utf16" { 336 // exceeds MAX_STRING_BYTE_LENGTH when multiplied 337 test_overflow(max / 2)?; 338 339 // Technically this fails on the first string, not the second. 340 // Ideally this would test the overflow check on the second 341 // string though. 342 test_overflow(max / 2 - 100)?; 343 } else { 344 // This will point into unmapped memory 345 test_overflow(max - 100)?; 346 } 347 } 348 349 "utf16" => { 350 test_overflow(max / 2)?; 351 test_overflow(max / 2 - 100)?; 352 } 353 354 "latin1+utf16" => { 355 test_overflow((max / 2) | UTF16_TAG)?; 356 // tag a utf16 string with the max length and it should overflow. 357 test_overflow((max / 2 - 100) | UTF16_TAG)?; 358 } 359 360 _ => unreachable!(), 361 } 362 363 Ok(()) 364 } 365 366 // Test that that the pointer returned from `realloc` is bounds-checked. 367 #[test] 368 fn realloc_oob() -> Result<()> { 369 let engine = component_test_util::engine(); 370 for src in ENCODINGS { 371 for dst in ENCODINGS { 372 test_realloc_oob(&engine, src, dst)?; 373 } 374 } 375 Ok(()) 376 } 377 378 fn test_realloc_oob(engine: &Engine, src: &str, dst: &str) -> Result<()> { 379 let component = format!( 380 r#" 381 (component 382 (component $c 383 (core module $m 384 (func (export "") (param i32 i32)) 385 (func (export "realloc") (param i32 i32 i32 i32) (result i32) i32.const 100_000) 386 (memory (export "memory") 1) 387 ) 388 (core instance $m (instantiate $m)) 389 (func (export "") (param string) 390 (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory") 391 string-encoding={dst}) 392 ) 393 ) 394 395 (component $c2 396 (import "" (func $f (param string))) 397 (core module $libc 398 (memory (export "memory") 1) 399 ) 400 (core instance $libc (instantiate $libc)) 401 (core func $f (canon lower (func $f) string-encoding={src} (memory $libc "memory"))) 402 (core module $m 403 (import "" "" (func $f (param i32 i32))) 404 405 (func (export "f") (call $f (i32.const 1000) (i32.const 10))) 406 ) 407 (core instance $m (instantiate $m (with "" (instance (export "" (func $f)))))) 408 (func (export "f") (canon lift (core func $m "f"))) 409 ) 410 411 (instance $c (instantiate $c)) 412 (instance $c2 (instantiate $c2 (with "" (func $c "")))) 413 (export "f" (func $c2 "f")) 414 ) 415 "# 416 ); 417 418 let component = Component::new(engine, &component)?; 419 let mut store = Store::new(engine, ()); 420 421 let instance = Linker::new(engine).instantiate(&mut store, &component)?; 422 let func = instance.get_typed_func::<(), (), _>(&mut store, "f")?; 423 let trap = func.call(&mut store, ()).unwrap_err().downcast::<Trap>()?; 424 assert_eq!(trap.trap_code(), Some(TrapCode::UnreachableCodeReached)); 425 Ok(()) 426 } 427 428 // Test that that the pointer returned from `realloc` is bounds-checked. 429 #[test] 430 fn raw_string_encodings() -> Result<()> { 431 let engine = component_test_util::engine(); 432 test_invalid_string_encoding(&engine, "utf8", "utf8", &[0xff], 1)?; 433 let array = b"valid string until \xffthen valid again"; 434 test_invalid_string_encoding(&engine, "utf8", "utf8", array, array.len() as u32)?; 435 test_invalid_string_encoding(&engine, "utf8", "utf16", array, array.len() as u32)?; 436 let array = b"symbol \xce\xa3 until \xffthen valid"; 437 test_invalid_string_encoding(&engine, "utf8", "utf8", array, array.len() as u32)?; 438 test_invalid_string_encoding(&engine, "utf8", "utf16", array, array.len() as u32)?; 439 test_invalid_string_encoding(&engine, "utf8", "latin1+utf16", array, array.len() as u32)?; 440 test_invalid_string_encoding(&engine, "utf16", "utf8", &[0x01, 0xd8], 1)?; 441 test_invalid_string_encoding(&engine, "utf16", "utf16", &[0x01, 0xd8], 1)?; 442 test_invalid_string_encoding( 443 &engine, 444 "utf16", 445 "latin1+utf16", 446 &[0xff, 0xff, 0x01, 0xd8], 447 2, 448 )?; 449 test_invalid_string_encoding( 450 &engine, 451 "latin1+utf16", 452 "utf8", 453 &[0x01, 0xd8], 454 1 | UTF16_TAG, 455 )?; 456 test_invalid_string_encoding( 457 &engine, 458 "latin1+utf16", 459 "utf16", 460 &[0x01, 0xd8], 461 1 | UTF16_TAG, 462 )?; 463 test_invalid_string_encoding( 464 &engine, 465 "latin1+utf16", 466 "utf16", 467 &[0xff, 0xff, 0x01, 0xd8], 468 2 | UTF16_TAG, 469 )?; 470 test_invalid_string_encoding( 471 &engine, 472 "latin1+utf16", 473 "latin1+utf16", 474 &[0xab, 0x00, 0xff, 0xff, 0x01, 0xd8], 475 3 | UTF16_TAG, 476 )?; 477 478 // This latin1+utf16 string should get compressed to latin1 across the 479 // boundary. 480 test_valid_string_encoding( 481 &engine, 482 "latin1+utf16", 483 "latin1+utf16", 484 &[0xab, 0x00, 0xff, 0x00], 485 2 | UTF16_TAG, 486 )?; 487 Ok(()) 488 } 489 490 fn test_invalid_string_encoding( 491 engine: &Engine, 492 src: &str, 493 dst: &str, 494 bytes: &[u8], 495 len: u32, 496 ) -> Result<()> { 497 let trap = test_raw_when_encoded(engine, src, dst, bytes, len)?.unwrap(); 498 let src = src.replace("latin1+", ""); 499 assert!( 500 trap.to_string() 501 .contains(&format!("invalid {src} encoding")), 502 "bad error: {}", 503 trap, 504 ); 505 Ok(()) 506 } 507 508 fn test_valid_string_encoding( 509 engine: &Engine, 510 src: &str, 511 dst: &str, 512 bytes: &[u8], 513 len: u32, 514 ) -> Result<()> { 515 let err = test_raw_when_encoded(engine, src, dst, bytes, len)?; 516 assert!(err.is_none()); 517 Ok(()) 518 } 519 520 fn test_raw_when_encoded( 521 engine: &Engine, 522 src: &str, 523 dst: &str, 524 bytes: &[u8], 525 len: u32, 526 ) -> Result<Option<Trap>> { 527 let component = format!( 528 r#" 529 (component 530 (component $c 531 (core module $m 532 (func (export "") (param i32 i32)) 533 (func (export "realloc") (param i32 i32 i32 i32) (result i32) i32.const 0) 534 (memory (export "memory") 1) 535 ) 536 (core instance $m (instantiate $m)) 537 (func (export "") (param string) 538 (canon lift (core func $m "") (realloc (func $m "realloc")) (memory $m "memory") 539 string-encoding={dst}) 540 ) 541 ) 542 543 (component $c2 544 (import "" (func $f (param string))) 545 (core module $libc 546 (memory (export "memory") 1) 547 (func (export "realloc") (param i32 i32 i32 i32) (result i32) i32.const 0) 548 ) 549 (core instance $libc (instantiate $libc)) 550 (core func $f (canon lower (func $f) string-encoding={src} (memory $libc "memory"))) 551 (core module $m 552 (import "" "" (func $f (param i32 i32))) 553 554 (func (export "f") (param i32 i32 i32) (call $f (local.get 0) (local.get 2))) 555 ) 556 (core instance $m (instantiate $m (with "" (instance (export "" (func $f)))))) 557 (func (export "f") (param (list u8)) (param u32) (canon lift (core func $m "f") 558 (memory $libc "memory") 559 (realloc (func $libc "realloc")))) 560 ) 561 562 (instance $c (instantiate $c)) 563 (instance $c2 (instantiate $c2 (with "" (func $c "")))) 564 (export "f" (func $c2 "f")) 565 ) 566 "# 567 ); 568 569 let component = Component::new(engine, &component)?; 570 let mut store = Store::new(engine, ()); 571 572 let instance = Linker::new(engine).instantiate(&mut store, &component)?; 573 let func = instance.get_typed_func::<(&[u8], u32), (), _>(&mut store, "f")?; 574 match func.call(&mut store, (bytes, len)) { 575 Ok(_) => Ok(None), 576 Err(e) => Ok(Some(e.downcast()?)), 577 } 578 } 579