Skip to content

Commit a044776

Browse files
Merge branch 'abk/run-647-larger-wasms' into 'master'
RUN-647: Allow large Wasms with smaller code sections This change allows the runtime to accept larger Wasm modules. In order to keep compilation times fast, we still impose a 10MB limit on the size of the code sections. See merge request dfinity-lab/public/ic!12528
2 parents 09eb676 + 33f9c17 commit a044776

File tree

7 files changed

+186
-1
lines changed

7 files changed

+186
-1
lines changed

rs/embedders/src/wasm_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ impl SystemApiFunc {
175175
}
176176
}
177177

178+
#[derive(Debug)]
178179
pub struct InstrumentationOutput {
179180
/// All exported methods that are relevant to the IC.
180181
/// Methods relevant to the IC are:

rs/embedders/src/wasm_utils/decoding.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::io::Read;
33
use std::sync::Arc;
44

55
/// Maximum size of a WebAssembly module.
6-
pub const MAX_WASM_MODULE_SIZE_BYTES: usize = 10 * 1024 * 1024;
6+
pub const MAX_WASM_MODULE_SIZE_BYTES: usize = 30 * 1024 * 1024;
77

88
fn make_module_too_large_error() -> WasmValidationError {
99
WasmValidationError::DecodingError(format!(

rs/embedders/src/wasm_utils/validation.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub const RESERVED_SYMBOLS: [&str; 6] = [
3737

3838
const WASM_FUNCTION_COMPLEXITY_LIMIT: usize = 15_000;
3939
const WASM_FUNCTION_SIZE_LIMIT: usize = 1_000_000;
40+
const MAX_CODE_SECTION_SIZE_IN_BYTES: u32 = 10 * 1024 * 1024;
4041

4142
// Represents the expected function signature for any System APIs the Internet
4243
// Computer provides or any special exported user functions.
@@ -1148,6 +1149,30 @@ fn can_compile(wasm: &BinaryEncodedWasm) -> Result<(), WasmValidationError> {
11481149
})
11491150
}
11501151

1152+
fn check_code_section_size(wasm: &BinaryEncodedWasm) -> Result<(), WasmValidationError> {
1153+
let parser = wasmparser::Parser::new(0);
1154+
let payloads = parser.parse_all(wasm.as_slice());
1155+
for payload in payloads {
1156+
if let wasmparser::Payload::CodeSectionStart {
1157+
count: _,
1158+
range: _,
1159+
size,
1160+
} = payload.map_err(|e| {
1161+
WasmValidationError::DecodingError(format!("Error finding code section: {}", e))
1162+
})? {
1163+
if size > MAX_CODE_SECTION_SIZE_IN_BYTES {
1164+
return Err(WasmValidationError::CodeSectionTooLarge {
1165+
size,
1166+
allowed: MAX_CODE_SECTION_SIZE_IN_BYTES,
1167+
});
1168+
} else {
1169+
return Ok(());
1170+
}
1171+
}
1172+
}
1173+
Ok(())
1174+
}
1175+
11511176
/// Validates a Wasm binary against the requirements of the interface spec
11521177
/// defined in https://sdk.dfinity.org/docs/interface-spec/index.html.
11531178
///
@@ -1167,6 +1192,7 @@ pub(super) fn validate_wasm_binary<'a>(
11671192
wasm: &'a BinaryEncodedWasm,
11681193
config: &EmbeddersConfig,
11691194
) -> Result<(WasmValidationDetails, Module<'a>), WasmValidationError> {
1195+
check_code_section_size(wasm)?;
11701196
can_compile(wasm)?;
11711197
let module = Module::parse(wasm.as_slice(), false)
11721198
.map_err(|err| WasmValidationError::DecodingError(format!("{}", err)))?;
20.9 KB
Binary file not shown.

rs/embedders/tests/validation.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ use ic_replicated_state::canister_state::execution_state::{
1717
};
1818
use ic_types::{NumBytes, NumInstructions};
1919
use maplit::btreemap;
20+
use wasmtime_environ::WASM_PAGE_SIZE;
21+
22+
const KB: u32 = 1024;
2023

2124
fn wat2wasm(wat: &str) -> Result<BinaryEncodedWasm, wat::Error> {
2225
wat::parse_str(wat).map(BinaryEncodedWasm::new)
@@ -958,3 +961,144 @@ fn complex_function_rejected() {
958961
})
959962
)
960963
}
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+
}

rs/types/wasm_types/src/errors.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ pub enum WasmValidationError {
5656
size: usize,
5757
allowed: usize,
5858
},
59+
/// The code section is too large.
60+
CodeSectionTooLarge { size: u32, allowed: u32 },
5961
}
6062

6163
impl std::fmt::Display for WasmValidationError {
@@ -118,6 +120,11 @@ impl std::fmt::Display for WasmValidationError {
118120
"Wasm module contains a function at index {} of size {} that exceeds the maximum allowed size of {}",
119121
index, size, allowed,
120122
),
123+
Self::CodeSectionTooLarge{size, allowed} => write!(
124+
f,
125+
"Wasm model code section size of {} exceeds the maximum allowed size of {}",
126+
size, allowed,
127+
),
121128
}
122129
}
123130
}

rs/types/wasm_types/src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ const WASM_HASH_LENGTH: usize = 32;
1919
#[derive(Clone)]
2020
pub struct BinaryEncodedWasm(Arc<Vec<u8>>);
2121

22+
impl std::fmt::Debug for BinaryEncodedWasm {
23+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24+
// Ignore the actual binary contents when debug formatting.
25+
f.debug_tuple("BinaryEncodedWasm").finish()
26+
}
27+
}
28+
2229
impl BinaryEncodedWasm {
2330
pub fn new(wasm: Vec<u8>) -> Self {
2431
Self::new_shared(Arc::new(wasm))

0 commit comments

Comments
 (0)