Skip to content

Commit 9d94003

Browse files
Keep instantiations alive on failure to prevent use-after-free when a partially-instantiated module writes a function reference to an imported table (#8111)
Fixes #8108. In linking0.wast, a module writes a function reference to an imported memory but fails to instantiate completely due to an out of bounds memory access. In this case, the function reference is expected to stay alive even though instantiation didn't complete, but our code currently drops the last reference in this case which causes a segfault. Change the code to keep the reference alive even when instantiation fails. This is pessimistic since some modules may fail to instantiate without writing any references to imported tables, but it avoids dangling pointers in those cases that do write references.
1 parent 4f52bff commit 9d94003

File tree

2 files changed

+43
-9
lines changed

2 files changed

+43
-9
lines changed

src/tools/wasm-shell.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -158,12 +158,19 @@ struct Shell {
158158
}
159159

160160
Result<> instantiate(Module& wasm, Name instanceName) {
161-
std::shared_ptr<ShellExternalInterface> interface;
162-
std::shared_ptr<ModuleRunner> instance;
161+
auto interface = std::make_shared<ShellExternalInterface>(linkedInstances);
162+
auto instance =
163+
std::make_shared<ModuleRunner>(wasm, interface.get(), linkedInstances);
164+
165+
lastInstance = instanceName;
166+
167+
// Even if instantiation fails, the module may have partially instantiated
168+
// and mutated an imported memory or table. Keep the references alive to
169+
// ensure that function references stay alive.
170+
interfaces[instanceName] = interface;
171+
instances[instanceName] = instance;
172+
163173
try {
164-
interface = std::make_shared<ShellExternalInterface>(linkedInstances);
165-
instance =
166-
std::make_shared<ModuleRunner>(wasm, interface.get(), linkedInstances);
167174

168175
// This is not an optimization: we want to execute anything, even relaxed
169176
// SIMD instructions.
@@ -173,10 +180,6 @@ struct Shell {
173180
return Err{"failed to instantiate module"};
174181
}
175182

176-
lastInstance = instanceName;
177-
178-
interfaces[instanceName] = std::move(interface);
179-
instances[instanceName] = std::move(instance);
180183
return Ok{};
181184
}
182185

test/spec/linking0.wast

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
;; Adapted from test/spec/testsuite/linking0.wast with a failing assertion removed.
2+
;; TODO: Remove this once test/spec/testsuite/linking0.wast passes.
3+
4+
(module $Mt
5+
(type (func (result i32)))
6+
(type (func))
7+
8+
(table (export "tab") 10 funcref)
9+
(elem (i32.const 2) $g $g $g $g)
10+
(func $g (result i32) (i32.const 4))
11+
(func (export "h") (result i32) (i32.const -4))
12+
13+
(func (export "call") (param i32) (result i32)
14+
(call_indirect (type 0) (local.get 0))
15+
)
16+
)
17+
(register "Mt" $Mt)
18+
19+
(assert_trap
20+
(module
21+
(table (import "Mt" "tab") 10 funcref)
22+
(func $f (result i32) (i32.const 0))
23+
(elem (i32.const 7) $f)
24+
(memory 0)
25+
(memory $m 1)
26+
(memory 0)
27+
(data $m (i32.const 0x10000) "d") ;; out of bounds
28+
)
29+
"out of bounds memory access"
30+
)
31+
(assert_return (invoke $Mt "call" (i32.const 7)) (i32.const 0))

0 commit comments

Comments
 (0)