Skip to content

Parser#parse throws all kinds of exceptions for malformed input #98

Description

@Marcono1234

Version

1.0.1

Description

Out of curiosity I used the JVM fuzzing library Jazzer on Parser#parse to see how it handles malformed input.

As part of this I encountered the following exceptions (other than WasmEngineException):

  • IllegalArgumentException
  • IndexOutOfBoundsException
  • ArrayIndexOutOfBoundsException
  • BufferUnderflowException
  • ArithmeticException
  • NullPointerException
  • NegativeArraySizeException
  • OutOfMemoryError
  • AssertionError

I assume it would be good to fix the AssertionError (i.e., here throw an exception instead of using an assert).
OutOfMemoryError occurred for cases where the parser eagerly allocated user-defined amount of memory. That might be acceptable to still keep good performance?

This probably has no security impact though (not even the OutOfMemoryError)? If the user was parsing untrusted Wasm code, they most likely would execute it afterwards and that would be the main security risk then, and could suffer from denial-of-service attacks as well.

Do you think that current Parser behavior is acceptable to avoid adding checks in multiple places, for Wasm code which is most likely malformed anyway? Or should Parser only throw WasmEngineException?

Jazzer setup (click to expand)

Dependency:

<dependency>
  <groupId>com.code-intelligence</groupId>
  <artifactId>jazzer-junit</artifactId>
  <version>0.30.0</version>
  <scope>test</scope>
</dependency>

junit-platform.properties:

jazzer.instrumentation_includes=run.endive.**
jazzer.instrumentation_excludes=run.endive.$gen.**

# Workaround JUnit timeout causing spurious test failure, see https://github.com/CodeIntelligenceTesting/jazzer/issues/1060
junit.jupiter.execution.timeout.mode=disabled

Test class ParserTest:

import com.code_intelligence.jazzer.junit.FuzzTest;
import com.code_intelligence.jazzer.mutation.annotation.NotNull;
import com.code_intelligence.jazzer.mutation.annotation.ValuePool;
import org.junit.jupiter.api.Timeout;
import run.endive.runtime.Instance;
import run.endive.wasm.Parser;
import run.endive.wasm.WasmEngineException;
import run.endive.wasm.WasmModule;

import java.util.concurrent.TimeUnit;

public class ParserTest {
    // Corpus from https://github.com/bytecodealliance/endive/tree/75057984f851fd2b13fb04db42f66ab5600585ae/wasm-corpus/src/main/resources/compiled
    private static final String CORPUS_PATH = "src/test/resources/endive-wasm-corpus/**";

    @Timeout(value = 10, unit = TimeUnit.SECONDS)
    @FuzzTest
    void parse(byte[] bytes) {
        WasmModule module;
        try {
            module = Parser.parse(bytes);
        } catch (WasmEngineException e) {
            return;
        }

        try {
            Instance instance = Instance.builder(module).build();
        }  catch (WasmEngineException e) {
            return;
        }
    }

    @Timeout(value = 10, unit = TimeUnit.SECONDS)
    @FuzzTest
    // `ignored` parameter as workaround for https://github.com/CodeIntelligenceTesting/jazzer/issues/1022
    void parseWithCorpus(byte @ValuePool(files = CORPUS_PATH) @NotNull [] bytes, boolean ignored) {
        WasmModule module;
        try {
            module = Parser.parse(bytes);
        } catch (WasmEngineException e) {
            return;
        }

        try {
            Instance instance = Instance.builder(module).build();
        }  catch (WasmEngineException e) {
            return;
        }
    }
}

Then manually run the individual test methods (running multiple is not possible yet) for example in your IDE, and edit the IDE run configuration to set the environment variable JAZZER_FUZZ=1.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions