Skip to content

Commit 24c1388

Browse files
authored
Call-indirect caching: protect against out-of-bounds table index during prescan. (bytecodealliance#8541)
* Call-indirect caching: protect against out-of-bounds table index during prescan. Call-indirect caching requires a "prescan" of a module's code section during compilation in order to know which tables are possibly written, and to count call-indirect callsites so that each separate function compilation can enable caching if possible and use the right slot range. This prescan is not integrated with the usual validation logic (nor should it be, probably, for performance), so it's possible that an out-of-bounds table index or other illegal instruction could be present. We previously indexed into an internal data structure (`table_plans`) with this index, allowing for a compilation panic on certain invalid modules before validation would have caught it. This PR fixes that with a one-off check at the single point that we interpret a parameter (the table index) from an instruction during the prescan. * Add test case.
1 parent 3f98505 commit 24c1388

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

crates/environ/src/compile/module_environ.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use crate::{
1111
};
1212
use anyhow::{bail, Result};
1313
use cranelift_entity::packed_option::ReservedValue;
14+
use cranelift_entity::EntityRef;
1415
use std::borrow::Cow;
1516
use std::collections::HashMap;
1617
use std::mem;
@@ -737,7 +738,14 @@ and for re-adding support for interface types you can see this issue:
737738
| Operator::TableCopy {
738739
dst_table: table, ..
739740
} => {
740-
self.flag_written_table(TableIndex::from_u32(table));
741+
// We haven't yet validated the body during
742+
// this pre-scan, so we need to check that
743+
// `dst_table` is in bounds. Ignore if not:
744+
// we'll catch the error later.
745+
let table = TableIndex::from_u32(table);
746+
if table.index() < self.result.module.table_plans.len() {
747+
self.flag_written_table(table);
748+
}
741749
}
742750
// Count the `call_indirect` sites so we can
743751
// assign them unique slots.

tests/all/module.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,31 @@ fn call_indirect_caching_and_memory64() -> Result<()> {
269269
)?;
270270
Ok(())
271271
}
272+
273+
#[test]
274+
fn call_indirect_caching_out_of_bounds_table_index() -> Result<()> {
275+
let mut config = Config::new();
276+
config.cache_call_indirects(true);
277+
let engine = Engine::new(&config)?;
278+
// Test an out-of-bounds table index: this is exposed to the prescan
279+
// that call-indirect caching must perform during compilation, so we
280+
// need to make sure the error is properly handled by the validation
281+
// that comes later.
282+
let err = Module::new(
283+
&engine,
284+
"(module
285+
(func (param i32)
286+
ref.null func
287+
local.get 0
288+
table.set 32 ;; out-of-bounds table index
289+
)
290+
)",
291+
)
292+
.unwrap_err();
293+
let err = format!("{err:?}");
294+
assert!(
295+
err.contains("table index out of bounds"),
296+
"bad error: {err}"
297+
);
298+
Ok(())
299+
}

0 commit comments

Comments
 (0)