Skip to content

[wasi] FileStream_ctor_sfh_fa_buffer.InconsistentFileAccessThrows corrupts memory (interpreter and ReadyToRun) #134957

Description

@lewing

Description

With CoreCLR WASI ReadyToRun, System.IO.FileSystem.DisabledFileLocking.Tests crashes during a garbage collection while running System.IO.Tests.FileStream_ctor_sfh_fa_buffer.InconsistentFileAccessThrows. The GC's stack scan reports a slot through the wasm32 GC info that does not hold a valid object reference, and marking it reads outside linear memory:

memory fault at wasm address 0x800560a0 in linear memory of size 0x8000000
wasm trap: out of bounds memory access
    0: corerun!WKS::gc_heap::mark_object_simple(unsigned char**)
    1: corerun!WKS::GCHeap::Promote(Object**, ScanContext*, unsigned int)
    2: corerun!PromoteCarefully(...)
    3: corerun!GcEnumObject(void*, Object**, unsigned int)
    4: corerun!TGcInfoDecoder<Wasm32GcInfoEncoding>::ReportUntrackedSlots(GcSlotDecoder<Wasm32GcInfoEncoding>&, REGDISPLAY*, ...)
    5: corerun!TGcInfoDecoder<Wasm32GcInfoEncoding>::EnumerateLiveSlots(REGDISPLAY*, bool, unsigned int, ...)
    6: corerun!EECodeManager::EnumGcRefs(REGDISPLAY*, EECodeInfo*, unsigned int, ...)
    7: corerun!GcStackCrawlCallBack(CrawlFrame*, void*)
    ...
   14: corerun!WKS::gc_heap::mark_phase(int)
   ...
   19: corerun!WKS::gc_heap::allocate_uoh_object(unsigned long, unsigned int, int, long long&)

The collection is triggered by a large object allocation. The untracked slot comes from a ReadyToRun-compiled frame on the stack. The wasmtime backtrace is truncated at 20 frames, so the method with the bad GC info is not identified yet. Rerun with a deeper backtrace or under a debugger to find the frame, then compare its GC info with the slots the compiled code actually initializes.

This may be the same root cause as #134947, an out-of-bounds access that also appears only with ReadyToRun.

Seen in trimmed ReadyToRun runs of the CoreCLR WASI library tests for #134813.

Note

This issue was drafted with the help of GitHub Copilot.

Activity

  1. added this to the 12.0.0 milestone on Sep 30, 2026
  2. dotnet-policy-service commented on Sep 30, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
    See info in area-owners.md if you want to be subscribed.

  3. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    and removed on Sep 30, 2026
  4. dotnet-policy-service commented on Sep 30, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  5. lewing commented on Sep 30, 2026

    @lewing
    MemberAuthor

    Update: this still reproduces on current main (a1cdbbb), which includes the recent wasm GC reporting fixes (#134779). It is not specific to ReadyToRun.

    Configuration (System.IO.FileSystem.DisabledFileLocking.Tests) Result
    Interpreter wasm trap: out of bounds memory access in corerun!pread ← SystemNative_PRead ← InvokeUnmanagedCalli ← InterpExecMethod. With XunitShowProgress, the last started test is FileStream_ctor_sfh_fa_buffer.InconsistentFileAccessThrows.
    Trimmed ReadyToRun The xunit runner dies with ArgumentNullException (Parameter 'source') in TestClassRunner.RunAsync, then an unreachable trap in SfiNextWorker during exception dispatch.
    Trimmed ReadyToRun, this test skipped 1103 run, 0 failed

    The test reads through a write-only handle and writes through a read-only handle and expects UnauthorizedAccessException. The interpreter failure is in pread for the failing read, which suggests the failed read/write path on WASI hands native code a bad buffer pointer or length. The original R2R GC-time fault address 0x800560a0 (in 128 MB of linear memory) looks like the same kind of invalid pointer. GC slot reporting is probably a symptom rather than the cause. A root-cause investigation is in progress.

    On #134813 the quarantine now covers all WASI configurations.

    Note

    This comment was drafted with the help of GitHub Copilot.

  6. changed the title [-][wasi][R2R] GC stack scan reports an invalid untracked slot from ReadyToRun code[/-] [+][wasi] FileStream_ctor_sfh_fa_buffer.InconsistentFileAccessThrows corrupts memory (interpreter and ReadyToRun)[/+] on Sep 30, 2026
  7. lewing commented on Sep 30, 2026

    @lewing
    MemberAuthor

    Root cause

    This isn't a GC-reporting or R2R bug. It's a wasi-libc bug in the wasip2 pread shipped with wasi-sdk 33, which this repo builds with.

    pread consumes the descriptor.read result before checking whether the call failed (pread.c at wasi-sdk-33):

    bool ok = filesystem_method_descriptor_read(file_handle, nbyte, offset, &contents, &error_code);
    bytes_read = contents.f0.len;                  // uninitialized on failure
    memcpy(buf, contents.f0.ptr, bytes_read);      // garbage src pointer and length
    wasip2_list_u8_free(&contents.f0);             // free() of a garbage pointer
    if (!ok) { ... return -1; }

    On failure the binding never writes contents, so pread copies a stale-stack-sized chunk from a stale-stack pointer into the caller's buffer, then frees a stale pointer. Depending on what's on the stack, that traps immediately (the interpreter's out of bounds memory access in corerun!pread) or silently corrupts the heap or managed objects. The corruption case explains the ReadyToRun symptoms: a GC-time memory fault at a garbage address, or ArgumentNullException in the xunit runner followed by an unreachable trap during exception dispatch.

    InconsistentFileAccessThrows triggers it by opening the file FileAccess.Write, wrapping the handle as FileAccess.Read, and calling ReadByte(). That goes RandomAccess.ReadAtOffset → SystemNative_PRead → pread on a write-only fd, descriptor.read fails with bad-descriptor, and the bad copy and free follow. Browser passes because emscripten's pread doesn't have this bug.

    Upstream fixed it in WebAssembly/wasi-libc#862 (issue WebAssembly/wasi-libc#861), first released in wasi-sdk 34.

    Evidence

    • A 20-line C program built with the repo's wasi-sdk 33 (--target=wasm32-wasip2) that calls pread on an O_WRONLY fd traps with the same out of bounds memory access in pread.
    • On current main (137a6701515), interpreter, -class System.IO.Tests.FileStream_ctor_sfh_fa_buffer:
      • Unmodified: traps at corerun!pread <- SystemNative_PRead.
      • With a guard in SystemNative_PRead: Tests run: 10 Passed: 10 Failed: 0.
    • With the guard, the full System.IO.FileSystem.DisabledFileLocking.Tests suite runs to completion (1517 tests, no trap). All three InconsistentFileAccessThrows variants pass. The remaining failures are unrelated WASI gaps: PNSE from APM/threading and pipe conformance tests.

    Fix

    • Workaround, draft PR [wasi] Avoid wasi-libc pread memory corruption on write-only descriptors #134988: on TARGET_WASI, SystemNative_PRead and the PReadV fallback reject fds that aren't open for reading (fcntl(F_GETFL)) with EBADF before calling pread. EBADF is what the fixed libc returns, and it maps to the UnauthorizedAccessException the test expects.
    • Real fix: move to wasi-sdk 34. That also covers other descriptor.read failures, such as I/O errors, that the guard doesn't. The workaround has a TODO-WASI pointing here so it can be removed after the bump.

    I moved the label from area-CodeGen-coreclr to area-System.IO, since the runtime-side change is in System.Native's pal_io.c.

    Note

    This comment was generated with GitHub Copilot.

  8. added and removed
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    on Sep 30, 2026
  9. dotnet-policy-service commented on Sep 30, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-system-io
    See info in area-owners.md if you want to be subscribed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    arch-wasmWebAssembly architecturearea-System.IOos-wasiRelated to WASI variant of arch-wasm

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions