@@ -17,6 +17,9 @@ use ic_replicated_state::canister_state::execution_state::{
17
17
} ;
18
18
use ic_types:: { NumBytes , NumInstructions } ;
19
19
use maplit:: btreemap;
20
+ use wasmtime_environ:: WASM_PAGE_SIZE ;
21
+
22
+ const KB : u32 = 1024 ;
20
23
21
24
fn wat2wasm ( wat : & str ) -> Result < BinaryEncodedWasm , wat:: Error > {
22
25
wat:: parse_str ( wat) . map ( BinaryEncodedWasm :: new)
@@ -958,3 +961,144 @@ fn complex_function_rejected() {
958
961
} )
959
962
)
960
963
}
964
+
965
+ /// Creates a was with roughly the given sizes for the code and data sections
966
+ /// (may be off by a few bytes).
967
+ fn wasm_with_fixed_sizes ( code_section_size : u32 , data_section_size : u32 ) -> BinaryEncodedWasm {
968
+ // Initial memory needs to be large enough to fit the data
969
+ let memory_size = data_section_size / WASM_PAGE_SIZE + 1 ;
970
+ let mut wat = "(module (func" . to_string ( ) ;
971
+ // Each (block) is 3 bytes: 2 bytes for "block" and 1 for "end"
972
+ for _ in 0 ..code_section_size / 3 {
973
+ wat. push_str ( "(block)" ) ;
974
+ }
975
+ wat. push ( ')' ) ;
976
+ wat. push_str ( & format ! ( "(memory {})" , memory_size) ) ;
977
+ wat. push_str ( & format ! (
978
+ "(data (i32.const 0) \" {}\" )" ,
979
+ "a" . repeat( data_section_size as usize ) ,
980
+ ) ) ;
981
+ wat. push ( ')' ) ;
982
+ wat2wasm ( & wat) . unwrap ( )
983
+ }
984
+
985
+ #[ test]
986
+ fn large_code_section_rejected ( ) {
987
+ let wasm = wasm_with_fixed_sizes ( 10 * KB * KB + 10 , 0 ) ;
988
+ let embedder = WasmtimeEmbedder :: new ( EmbeddersConfig :: default ( ) , no_op_logger ( ) ) ;
989
+ let result = validate_and_instrument_for_testing ( & embedder, & wasm) ;
990
+ assert_matches ! (
991
+ result,
992
+ Err ( HypervisorError :: InvalidWasm (
993
+ WasmValidationError :: CodeSectionTooLarge { .. } ,
994
+ ) )
995
+ )
996
+ }
997
+
998
+ #[ test]
999
+ fn large_wasm_with_small_code_accepted ( ) {
1000
+ let wasm = wasm_with_fixed_sizes ( KB , 20 * KB * KB ) ;
1001
+ let embedder = WasmtimeEmbedder :: new ( EmbeddersConfig :: default ( ) , no_op_logger ( ) ) ;
1002
+ let result = validate_and_instrument_for_testing ( & embedder, & wasm) ;
1003
+ assert_matches ! ( result, Ok ( _) )
1004
+ }
1005
+
1006
+ /// We are trusting the code section size reported in the header when
1007
+ /// determining if the code section is too long. A Wasm which has been
1008
+ /// manipulated to report an incorrectly small size in the header should be
1009
+ /// rejected should later be rejected when we try to validate it with Wasmtime.
1010
+ #[ test]
1011
+ fn incorrect_wasm_code_size_is_invalid ( ) {
1012
+ use wasmparser:: { Parser , Payload } ;
1013
+
1014
+ let wasm = wasm_with_fixed_sizes ( 10 * KB * KB + 10 , 0 ) ;
1015
+
1016
+ let parser = Parser :: new ( 0 ) ;
1017
+ let payloads = parser. parse_all ( wasm. as_slice ( ) ) ;
1018
+ let mut manipulated_wasm = vec ! [ ] ;
1019
+ for payload in payloads {
1020
+ if let Payload :: CodeSectionStart { range, .. } = payload. unwrap ( ) {
1021
+ // The section header contains the byte 10 as the section id
1022
+ // followed by a variable length encoded u32 for the size.
1023
+ //
1024
+ // Note that the `size` field doesn't include the encoding of the
1025
+ // function count (which is 1), so it differs from the length of the
1026
+ // `range` (which does include the count encoding).
1027
+ //
1028
+ // The code section should have size 0xa0000f, which is 0x8f808005
1029
+ // as a variable-length u32.
1030
+ assert_eq ! ( range. end - range. start, 0xa0000f ) ;
1031
+ assert_eq ! (
1032
+ wasm. as_slice( ) [ range. start - 5 ..range. start] ,
1033
+ [ 0xa /*Code section id*/ , 0x8f , 0x80 , 0x80 , 0x05 ]
1034
+ ) ;
1035
+ // Copy everything up to and including the code section id.
1036
+ manipulated_wasm. extend_from_slice ( & wasm. as_slice ( ) [ ..range. start - 4 ] ) ;
1037
+ // Push 0x7f = 127 for the code section size.
1038
+ manipulated_wasm. push ( 0x7f ) ;
1039
+ // Copy everything after the code section size unchanged.
1040
+ manipulated_wasm. extend_from_slice ( & wasm. as_slice ( ) [ range. start ..] ) ;
1041
+ break ;
1042
+ }
1043
+ }
1044
+
1045
+ let manipulated_wasm = BinaryEncodedWasm :: new ( manipulated_wasm) ;
1046
+ let embedder = WasmtimeEmbedder :: new ( EmbeddersConfig :: default ( ) , no_op_logger ( ) ) ;
1047
+ let result = validate_and_instrument_for_testing ( & embedder, & manipulated_wasm) ;
1048
+ assert_matches ! (
1049
+ result,
1050
+ Err ( HypervisorError :: InvalidWasm (
1051
+ WasmValidationError :: WasmtimeValidation ( _) ,
1052
+ ) )
1053
+ )
1054
+ }
1055
+
1056
+ /// We're assuming there is at most one code section in the Wasm. The spec
1057
+ /// doesn't allow multiple code sections, so if there are multiple code sections
1058
+ /// the module should fail validation.
1059
+ #[ test]
1060
+ fn wasm_with_multiple_code_sections_is_invalid ( ) {
1061
+ use wasmparser:: { Parser , Payload } ;
1062
+
1063
+ let wasm = wasm_with_fixed_sizes ( 10 , 0 ) ;
1064
+
1065
+ let parser = Parser :: new ( 0 ) ;
1066
+ let payloads = parser. parse_all ( wasm. as_slice ( ) ) ;
1067
+ let mut manipulated_wasm = vec ! [ ] ;
1068
+ for payload in payloads {
1069
+ if let Payload :: CodeSectionStart { range, .. } = payload. unwrap ( ) {
1070
+ // The section header contains the byte 10 as the section id
1071
+ // followed by a variable length encoded u32 for the size.
1072
+ //
1073
+ // Note that the `size` field doesn't include the encoding of the
1074
+ // function count (which is 1), so it differs from the length of the
1075
+ // `range` (which does include the count encoding).
1076
+ //
1077
+ // The code section should have size 0xa, which is unchanged as a
1078
+ // variable-length u32.
1079
+ assert_eq ! ( range. end - range. start, 0xd ) ;
1080
+ assert_eq ! (
1081
+ wasm. as_slice( ) [ range. start - 2 ..range. start] ,
1082
+ [ 0xa /*Code section id*/ , 0x0d ]
1083
+ ) ;
1084
+ // Copy everything before the code section
1085
+ manipulated_wasm. extend_from_slice ( & wasm. as_slice ( ) [ ..range. start - 2 ] ) ;
1086
+ // Copy the code section twice
1087
+ manipulated_wasm. extend_from_slice ( & wasm. as_slice ( ) [ range. start - 2 ..range. end ] ) ;
1088
+ manipulated_wasm. extend_from_slice ( & wasm. as_slice ( ) [ range. start - 2 ..range. end ] ) ;
1089
+ // Copy everything after the code section size unchanged.
1090
+ manipulated_wasm. extend_from_slice ( & wasm. as_slice ( ) [ range. start ..] ) ;
1091
+ break ;
1092
+ }
1093
+ }
1094
+
1095
+ let manipulated_wasm = BinaryEncodedWasm :: new ( manipulated_wasm) ;
1096
+ let embedder = WasmtimeEmbedder :: new ( EmbeddersConfig :: default ( ) , no_op_logger ( ) ) ;
1097
+ let result = validate_and_instrument_for_testing ( & embedder, & manipulated_wasm) ;
1098
+ assert_matches ! (
1099
+ result,
1100
+ Err ( HypervisorError :: InvalidWasm (
1101
+ WasmValidationError :: WasmtimeValidation ( _) ,
1102
+ ) )
1103
+ )
1104
+ }
0 commit comments