-
Notifications
You must be signed in to change notification settings - Fork 338
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
, andfunc
should be parallel types. Thefunc
type 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.func
andcall_ref
. - Removes the need for
WasmSignature
fields inWasmOperation
classes. Instead, a new InputVariable
can 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.