Skip to content

Make WASM Function Signature Support ref concrete heap type #525

@chennbnbnb

Description

@chennbnbnb

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:

  1. Better aligns with the WASM GC Proposal, where struct, array, and func should be parallel types. The func type shouldn't be treated as a special case.
  2. Allows representing a WASM signature via a Variable.
  3. Paves the way for future support of instructions like ref.func and call_ref.
  4. Removes the need for WasmSignature fields in WasmOperation classes. Instead, a new Input Variable 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.

  1. What are your thoughts on this proposed change?
  2. Are you already working on this issue?
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions