Repository navigation
Make WASM Function Signature Support ref concrete heap type #525
Description
Hello,
I recently attempted to add support for custom concrete heap types in WebAssembly function signatures but discovered that Fuzzilli currently does not support this, leading to a Use-After-Free (UAF) crash. Below is a test case that reproduces the issue:
func testWasmOperationWithWasmSignatureUAFBug() throws {
let runner = try GetJavaScriptExecutorOrSkipTest()
let liveTestConfig = Configuration(logLevel: .error, enableInspection: true)
let fuzzer = makeMockFuzzer(config: liveTestConfig, environment: JavaScriptEnvironment())
let b = fuzzer.makeBuilder()
let types = b.wasmDefineTypeGroup {
let structType = b.wasmDefineStructType(fields: [WasmStructTypeDescription.Field(type: .wasmi32, mutability: true)], indexTypes: [])
let structType2 = b.wasmDefineStructType(fields: [WasmStructTypeDescription.Field(type: ILType.wasmRef(.Index(), nullability: true), mutability: true)], indexTypes: [structType])
return [structType, structType2]
}
let structType2 = types[1]
let refStrcutType = b.type(of: structType2).wasmTypeDefinition!.getReferenceTypeTo(nullability: true)
b.buildWasmModule { m in
m.addWasmFunction(with: [] => [refStrcutType]) { f, _, _ in
let structInstance = f.wasmStructNewDefault(structType: structType2)
return [structInstance]
}
}
let prog = b.finalize()
let jsProg = fuzler.lifter.lift(prog)
let result = try runner.execute(jsProg)
}Running this test causes Fuzzilli to crash with a UAF error:
Fatal error: Attempted to read an unowned reference but object 0x55555764efe0 was already deallocated
Root Cause Analysis
The issue occurs because for values of type ref concrete heap type, the WasmReferenceType does not directly hold a strong reference. Instead, it holds an unowned weak reference via UnownedWasmTypeDescription, while the JSTyper is the actual owner of the WasmStructTypeDescription reference.
struct UnownedWasmTypeDescription : Hashable {
private unowned var description: WasmTypeDescription?
init(_ description: WasmTypeDescription? = nil) {
self.description = description
}
func get() -> WasmTypeDescription? {
return description
}
}The getReferenceTypeTo method creates an ILType object that also holds a weak reference to the WasmStructTypeDescription object. Subsequently, addWasmFunction() places this ILType into a WasmSignature object.
public func addWasmFunction(with signature: WasmSignature, _ body: (WasmFunction, Variable, [Variable]) -> [Variable]) -> Variable {
let instr = b.emit(BeginWasmFunction(signature: signature))
let results = body(currentWasmFunction, instr.innerOutput(0), Array(instr.innerOutputs(1...)))
return b.emit(EndWasmFunction(signature: signature), withInputs: results).output
}The WasmSignature is itself a property of various WasmOperation classes (like BeginWasmFunction).
final class BeginWasmFunction: WasmOperation {
override var opcode: Opcode { .beginWasmFunction(self) }
public let signature: WasmSignature
init(signature: WasmSignature) {
self.signature = signature
super.init(numInnerOutputs: 1 + signature.parameterTypes.count, attributes: [.isBlockStart], requiredContext: [.wasm], contextOpened: [.wasmFunction])
}
}Consequently, while the WasmStructTypeDescription object is logically owned by the JSTyper, the BeginWasmFunction operation also maintains a weak reference to it. When the ProgramBuilder finishes and the JSTyper is deallocated, the WasmStructTypeDescription object is also freed. However, during WasmLifting, the BeginWasmFunction operation attempts to access the ILType objects within its WasmSignature, triggering the UAF crash.
Summary: The UAF occurs because the WasmStructTypeDescription object is leaked into WasmOperation objects which have a longer lifecycle than the JSTyper.
This bug prevents function signatures from supporting concrete heap types and blocks support for additional instructions like ref.func.
Proposed Solution & Design Discussion
I believe the core issue is that WasmOperation objects directly reference ILType objects. This violates the intended FuzzIL design, where ILType should be the result of the JSTyper's analysis of the FuzzIL code, not a property stored within a WasmOperation. Therefore, WasmOperation should not hold references to ILType.
Proposed Improvement:
The solution is to support defining function signatures within a Rec Group, similar to how WasmStructTypeDescription and WasmArrayTypeDescription currently work. This would involve introducing a new WasmFuncSigDescription object to describe function signatures.
Benefits of this approach:
- Better aligns with the WASM GC Proposal, where
struct,array, andfuncshould be parallel types. Thefunctype shouldn't be treated as a special case. - Allows representing a WASM signature via a
Variable. - Paves the way for future support of instructions like
ref.funcandcall_ref. - Removes the need for
WasmSignaturefields inWasmOperationclasses. Instead, a new InputVariablecan be used to represent the function signature.
Example refactor for BeginWasmFunction:
final class BeginWasmFunction: WasmOperation {
override var opcode: Opcode { .beginWasmFunction(self) }
init() {
// The signature is now an input Variable, not a stored property
super.init(numInputs: 1, numInnerOutputs: 1 + signature.parameterTypes.count, attributes: [.isBlockStart], requiredContext: [.wasm], contextOpened: [.wasmFunction])
}
}The primary role of WasmSignature during lifting (generating the correct typeidx) can be fulfilled by querying the JSTyper for the type information associated with the input Variable, as all necessary type information is already embedded within the FuzzIL code.
Questions for the Maintainers
Before proceeding with the changes, I would like to understand your opinions on this matter.
- What are your thoughts on this proposed change?
- Are you already working on this issue?
- If I proceed with implementing this change, would you be open to accepting a Pull Request (PR) for it?
I look forward to your feedback and am willing to contribute the necessary implementation work.
Activity
Liedtke commentedon Sep 8, 2025
Hi,
Thanks for opening this issue and looking into this.
Let me start with answering your main questions:
BeginWasmFunction,WasmBeginBlock,WasmBeginIfand all the other blocks that have an input signature and similarly for the operations closing the block. On top of that, there are more instructions that would need to be updated, likeWasmDefineTag,WasmDefineGlobal, ... Instead, we'll have aWasmDefineSignatureTypeoperation to define signatures inside recursive type groups which would then be passed as an input into the listed instructions.A few more in-depth responses to your issue description:
This isn't a simple yes/no question. There are things that just fundamentally need some static type annotations, e.g. the
WasmDefineGlobalneeds to be able to annotate that the global should be ani32or anf64. If we didn't useILTypefor that, we'd need a separate enum or we'd need aWasmDefineGlobalI32etc., which wouldn't scale. The separate enum would be a "cleaner" solution but it would require extra conversions. So instead we have someILTypes flowing into wasm operations however, only for "built-in types", i.e. anything that is not "user"-defined / an index type in wasm-gc. Index-types (as you point out as well), require additional inputs, they depend on states in the IL program.Yes, we'll need a
WasmDefineSignatureTypeoperation and the corresponding extensions to theILTypemechanism. The main reason why this is inconsistent right now is thatarrayandstructtypes are simpler because they didn't interfere with the existing operations for functions and blocks and the functions and blocks simply weren't built with wasm-gc in mind (as the initial goal was to cover wasm MVP and add other proposals later on.)So unfortunately, Fuzzilli is still in an intermediate stage where a lot of wasm-gc support is missing and a lot of that is blocked on having the proper signature support. Similarly, we don't have inheritance of any wasm-gc-type-definitions and we are still missing a large amount of operations (e.g. casts). On top of that, there are also other proposals that depend on wasm-gc like string-builtins, custom descriptors, ..., that therefore are also not covered by Fuzzilli, yet.
I hope this answers most of the raised questions. As described, due to the scope and relevance for our progress on wasm-gc, currently this doesn't seem to be a great issue for an external contribution. If you're in general interested in contributing, I can see if I can (a) document a few more of the outstanding tasks in our issue-tracker and (b) see if we can make these issues publicly visible (right now most of the Fuzzilli project work tracking issues are marked as non-public.)
Footnotes
The main issue being input constraints: If the
BeginWasmFunctionsimply requires a signature type definition, we can't create this ad-hoc: When we are in the.wasmcontext, we can't emit a recursive type group definition (which we embed into the.javascriptcontext for better sharing between modules.) I'm currently leaning towards having a signature type definition statement in the.wasmcontext that is then not emedded into a recursive type group. But we'll have the same issue then in the.wasmfunctioncontext, so we'd prefer having that operation available in both of these contexts which also isn't a supported feature for operations as of now as far as I'm aware of. ↩If you run Fuzzilli with default weights (or increased weights only for e.g.
WasmStructSetGenerator, it gets obvious: We will almost never emit astruct.setsuccessfully because it is very unlikely to have both a struct and a matching value to store, the struct being the more difficult part as it requires to have (1) a typegroup, (2) aWasmDefineStructTypeinside and (3) an operation that instantiates a struct (right now onlyWasmStructNewDefault). As said, this is hopefully going to be addressed soon but it's still WIP. ↩