Skip to content

Commit 80dc262

Browse files
fitzgenelliottt
andauthored
wasmparser: Fix validation of the return_call family of instructions (#1585)
We need to additionally check that the callee's results are an exact match of the caller's results. We were incorrectly allowing return calls that would push more values on the operand stack than would be returned. That is fine with a `call; return` sequence, where extra values on the stack are allowed to dangle, but not okay with a `return_call`. With a `return_call` it doesn't make sense because the callee might need a return pointer to put all its results into, but the caller can't supply one since its frame is going away, nor can the caller forward a return pointer that it received to the callee, since it might not return enough values to require a return pointer. This commit fixes the validation to match the spec and disallow `return_call`s that would leave dangling values on the operand stack. cc bytecodealliance/wasmtime#8704 Co-authored-by: Trevor Elliott <[email protected]>
1 parent 0d97aa7 commit 80dc262

File tree

3 files changed

+124
-10
lines changed

3 files changed

+124
-10
lines changed

crates/wasmparser/src/validator/operators.rs

Lines changed: 61 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -796,18 +796,22 @@ where
796796
}
797797
}
798798

799-
/// Validates a `call` instruction, ensuring that the function index is
800-
/// in-bounds and the right types are on the stack to call the function.
801-
fn check_call(&mut self, function_index: u32) -> Result<()> {
802-
let ty = match self.resources.type_of_function(function_index) {
803-
Some(i) => i,
799+
fn type_of_function(&self, function_index: u32) -> Result<&'resources FuncType> {
800+
match self.resources.type_of_function(function_index) {
801+
Some(f) => Ok(f),
804802
None => {
805803
bail!(
806804
self.offset,
807805
"unknown function {function_index}: function index out of bounds",
808-
);
806+
)
809807
}
810-
};
808+
}
809+
}
810+
811+
/// Validates a `call` instruction, ensuring that the function index is
812+
/// in-bounds and the right types are on the stack to call the function.
813+
fn check_call(&mut self, function_index: u32) -> Result<()> {
814+
let ty = self.type_of_function(function_index)?;
811815
self.check_call_ty(ty)
812816
}
813817

@@ -864,6 +868,49 @@ where
864868
Ok(())
865869
}
866870

871+
/// Check that the function at the given index has the same result types as
872+
/// the current function's results.
873+
fn check_func_same_results(&self, function_index: u32) -> Result<()> {
874+
let ty = self.type_of_function(function_index)?;
875+
self.check_func_type_same_results(ty)
876+
}
877+
878+
/// Check that the type at the given index has the same result types as the
879+
/// current function's results.
880+
fn check_func_type_index_same_results(&self, type_index: u32) -> Result<()> {
881+
let ty = self.func_type_at(type_index)?;
882+
self.check_func_type_same_results(ty)
883+
}
884+
885+
/// Check that the given type has the same result types as the current
886+
/// function's results.
887+
fn check_func_type_same_results(&self, callee_ty: &FuncType) -> Result<()> {
888+
let caller_rets = self.results(self.control[0].block_type)?;
889+
if callee_ty.results().len() != caller_rets.len()
890+
|| !caller_rets
891+
.zip(callee_ty.results())
892+
.all(|(caller_ty, callee_ty)| self.resources.is_subtype(*callee_ty, caller_ty))
893+
{
894+
let caller_rets = self
895+
.results(self.control[0].block_type)?
896+
.map(|ty| format!("{ty}"))
897+
.collect::<Vec<_>>()
898+
.join(" ");
899+
let callee_rets = callee_ty
900+
.results()
901+
.iter()
902+
.map(|ty| format!("{ty}"))
903+
.collect::<Vec<_>>()
904+
.join(" ");
905+
bail!(
906+
self.offset,
907+
"type mismatch: current function requires result type \
908+
[{caller_rets}] but callee returns [{callee_rets}]"
909+
);
910+
}
911+
Ok(())
912+
}
913+
867914
/// Checks the validity of a common comparison operator.
868915
fn check_cmp_op(&mut self, ty: ValType) -> Result<()> {
869916
self.pop_operand(Some(ty))?;
@@ -1510,6 +1557,7 @@ where
15101557
fn visit_return_call(&mut self, function_index: u32) -> Self::Output {
15111558
self.check_call(function_index)?;
15121559
self.check_return()?;
1560+
self.check_func_same_results(function_index)?;
15131561
Ok(())
15141562
}
15151563
fn visit_call_ref(&mut self, type_index: u32) -> Self::Output {
@@ -1532,7 +1580,9 @@ where
15321580
}
15331581
fn visit_return_call_ref(&mut self, type_index: u32) -> Self::Output {
15341582
self.visit_call_ref(type_index)?;
1535-
self.check_return()
1583+
self.check_return()?;
1584+
self.check_func_type_index_same_results(type_index)?;
1585+
Ok(())
15361586
}
15371587
fn visit_call_indirect(
15381588
&mut self,
@@ -1549,9 +1599,10 @@ where
15491599
self.check_call_indirect(index, table_index)?;
15501600
Ok(())
15511601
}
1552-
fn visit_return_call_indirect(&mut self, index: u32, table_index: u32) -> Self::Output {
1553-
self.check_call_indirect(index, table_index)?;
1602+
fn visit_return_call_indirect(&mut self, type_index: u32, table_index: u32) -> Self::Output {
1603+
self.check_call_indirect(type_index, table_index)?;
15541604
self.check_return()?;
1605+
self.check_func_type_index_same_results(type_index)?;
15551606
Ok(())
15561607
}
15571608
fn visit_drop(&mut self) -> Self::Output {
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
;; Test that various return calls must exactly match the callee's returns, not
2+
;; simply leave the operand stack in a state where a `call; return` would
3+
;; otherwise be valid, but with some dangling stack values. Those dangling stack
4+
;; values are valid for regular calls, but not for return calls.
5+
6+
(assert_invalid
7+
(module
8+
(func $f (result i32 i32) unreachable)
9+
(func (result i32)
10+
return_call $f
11+
)
12+
)
13+
"type mismatch: current function requires result type [i32] but callee returns [i32 i32]"
14+
)
15+
16+
(assert_invalid
17+
(module
18+
(type $ty (func (result i32 i32)))
19+
(import "env" "table" (table $table 0 funcref))
20+
(func (param i32) (result i32)
21+
local.get 0
22+
return_call_indirect $table (type $ty)
23+
)
24+
)
25+
"type mismatch: current function requires result type [i32] but callee returns [i32 i32]"
26+
)
27+
28+
(assert_invalid
29+
(module
30+
(type $ty (func (result i32 i32)))
31+
(func (param funcref) (result i32)
32+
local.get 0
33+
return_call_ref $ty
34+
)
35+
)
36+
"type mismatch: current function requires result type [i32] but callee returns [i32 i32]"
37+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"source_filename": "tests/local/function-references/return-call.wast",
3+
"commands": [
4+
{
5+
"type": "assert_invalid",
6+
"line": 7,
7+
"filename": "return-call.0.wasm",
8+
"text": "type mismatch: current function requires result type [i32] but callee returns [i32 i32]",
9+
"module_type": "binary"
10+
},
11+
{
12+
"type": "assert_invalid",
13+
"line": 17,
14+
"filename": "return-call.1.wasm",
15+
"text": "type mismatch: current function requires result type [i32] but callee returns [i32 i32]",
16+
"module_type": "binary"
17+
},
18+
{
19+
"type": "assert_invalid",
20+
"line": 29,
21+
"filename": "return-call.2.wasm",
22+
"text": "type mismatch: current function requires result type [i32] but callee returns [i32 i32]",
23+
"module_type": "binary"
24+
}
25+
]
26+
}

0 commit comments

Comments
 (0)