Skip to content

Commit dc4853c

Browse files
authored
Merge pull request #1631 from CosmWasm/check-table-section
Check table section
2 parents a3d5c4c + f5bab84 commit dc4853c

File tree

2 files changed

+86
-1
lines changed

2 files changed

+86
-1
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ and this project adheres to
66

77
## [Unreleased]
88

9+
### Changed
10+
11+
- cosmwasm-vm: Add checks for table section of Wasm blob ([#1631]).
12+
13+
[#1631]: https://github.com/CosmWasm/cosmwasm/pull/1631
14+
915
## [1.2.3] - 2023-03-22
1016

1117
- cosmwasm-vm: Use saturating increments for `Stats` fields to ensure we don't

packages/vm/src/compatibility.rs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use parity_wasm::elements::{External, ImportEntry, Module};
1+
use parity_wasm::elements::{External, ImportEntry, Module, TableType};
22
use std::collections::BTreeSet;
33
use std::collections::HashSet;
44

@@ -49,10 +49,25 @@ const SUPPORTED_INTERFACE_VERSIONS: &[&str] = &[
4949
];
5050

5151
const MEMORY_LIMIT: u32 = 512; // in pages
52+
/// The upper limit for the `max` value of each table. CosmWasm contracts have
53+
/// initial=max for 1 table. See
54+
///
55+
/// ```plain
56+
/// $ wasm-objdump --section=table -x packages/vm/testdata/hackatom.wasm
57+
/// Section Details:
58+
///
59+
/// Table[1]:
60+
/// - table[0] type=funcref initial=161 max=161
61+
/// ```
62+
///
63+
/// As of March 2023, on Juno mainnet the largest value for production contracts
64+
/// is 485. Most are between 100 and 300.
65+
const TABLE_SIZE_LIMIT: u32 = 2500; // entries
5266

5367
/// Checks if the data is valid wasm and compatibility with the CosmWasm API (imports and exports)
5468
pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) -> VmResult<()> {
5569
let module = deserialize_wasm(wasm_code)?;
70+
check_wasm_tables(&module)?;
5671
check_wasm_memories(&module)?;
5772
check_interface_version(&module)?;
5873
check_wasm_exports(&module)?;
@@ -61,6 +76,38 @@ pub fn check_wasm(wasm_code: &[u8], available_capabilities: &HashSet<String>) ->
6176
Ok(())
6277
}
6378

79+
fn check_wasm_tables(module: &Module) -> VmResult<()> {
80+
let sections: &[TableType] = module
81+
.table_section()
82+
.map_or(&[], |section| section.entries());
83+
match sections.len() {
84+
0 => Ok(()),
85+
1 => {
86+
let limits = sections[0].limits();
87+
if let Some(maximum) = limits.maximum() {
88+
if limits.initial() > maximum {
89+
return Err(VmError::static_validation_err(
90+
"Wasm contract's first table section has a initial limit > max limit",
91+
));
92+
}
93+
if maximum > TABLE_SIZE_LIMIT {
94+
return Err(VmError::static_validation_err(
95+
"Wasm contract's first table section has a too large max limit",
96+
));
97+
}
98+
Ok(())
99+
} else {
100+
Err(VmError::static_validation_err(
101+
"Wasm contract must not have unbound table section",
102+
))
103+
}
104+
}
105+
_ => Err(VmError::static_validation_err(
106+
"Wasm contract must not have more than 1 table section",
107+
)),
108+
}
109+
}
110+
64111
fn check_wasm_memories(module: &Module) -> VmResult<()> {
65112
let section = match module.memory_section() {
66113
Some(section) => section,
@@ -252,6 +299,38 @@ mod tests {
252299
};
253300
}
254301

302+
#[test]
303+
fn check_wasm_tables_works() {
304+
// No tables is fine
305+
let wasm = wat::parse_str("(module)").unwrap();
306+
check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap();
307+
308+
// One table (bound)
309+
let wasm = wat::parse_str("(module (table $name 123 123 funcref))").unwrap();
310+
check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap();
311+
312+
// One table (bound, initial > max)
313+
let wasm = wat::parse_str("(module (table $name 124 123 funcref))").unwrap();
314+
let err = check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap_err();
315+
assert!(err
316+
.to_string()
317+
.contains("Wasm contract's first table section has a initial limit > max limit"));
318+
319+
// One table (bound, max too large)
320+
let wasm = wat::parse_str("(module (table $name 100 9999 funcref))").unwrap();
321+
let err = check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap_err();
322+
assert!(err
323+
.to_string()
324+
.contains("Wasm contract's first table section has a too large max limit"));
325+
326+
// One table (unbound)
327+
let wasm = wat::parse_str("(module (table $name 100 funcref))").unwrap();
328+
let err = check_wasm_tables(&deserialize_wasm(&wasm).unwrap()).unwrap_err();
329+
assert!(err
330+
.to_string()
331+
.contains("Wasm contract must not have unbound table section"));
332+
}
333+
255334
#[test]
256335
fn check_wasm_memories_ok() {
257336
let wasm = wat::parse_str("(module (memory 1))").unwrap();

0 commit comments

Comments
 (0)