Wasm: don't zero-pad the caller's return buffer in the interp-to-R2R thunk - #132257
Conversation
…thunk WasmInterpreterToR2RThunkNode zero-padded pRet out to an aligned size after calling the R2R body. pRet is the interpreter's own return slot only when the interpreter made the call; it is the compiled caller's return buffer when we arrive via WasmR2RToInterpreterThunkNode, and CallDescrData::pRetBuffArg when we arrive via CallDescrWorkerInternal. In those cases the buffer is exactly as large as the struct, so the padding wrote past its end. For a 12-byte struct that is four bytes into the next frame slot. In JsonDocument.Parse it zeroed the reference field of a live ReadOnlySpan and left its length intact, so the first Parse threw NullReferenceException from Utf8JsonReader.ReadSingleSegment. Fixes dotnet#131640 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
@davidwrighton PTAL |
|
Azure Pipelines: Successfully started running 3 pipeline(s). 13 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
davidwrighton
left a comment
There was a problem hiding this comment.
I agree with Jan on the comment, but the rest of the fix looks correct to me.
…yAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs
There was a problem hiding this comment.
Pull request overview
This PR fixes a WebAssembly ReadyToRun thunk correctness issue where the interpreter-to-R2R thunk could zero-pad a caller-owned struct return buffer past its actual size, corrupting adjacent stack data (observed as a spurious NullReferenceException in JsonDocument.Parse under wasm R2R).
Changes:
- Remove post-call zero-padding of
pRetinWasmInterpreterToR2RThunkNodefor retbuf-based struct returns to avoid writing past the caller’s buffer. - Add a wasm R2R-focused JIT regression test that keeps byref-like values live across a struct-returning call, catching the historical overwrite pattern.
- Add per-test project settings to force crossgen2 on the browser leg and run the test in process-isolated mode.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs | Removes retbuf padding logic so the thunk never writes beyond the caller-provided return buffer. |
| src/tests/JIT/Regression/JitBlue/Runtime_131640/Runtime_131640.cs | Adds a regression test that detects stack corruption from overrunning retbuf padding in wasm R2R thunking scenarios. |
| src/tests/JIT/Regression/JitBlue/Runtime_131640/Runtime_131640.csproj | Forces crossgen2 on TargetOS=browser and uses process isolation so the repro runs under wasm R2R as intended. |
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/tests/JIT/Regression/JitBlue/Runtime_131640/Runtime_131640.csproj:8
- The csproj comment claims
AlwaysUseCrossGen2“only takes effect through the project's own run script, which requires process isolation”, but other wasm R2R regression tests in the same directory rely onAlwaysUseCrossGen2withoutRequiresProcessIsolation(e.g. Runtime_131285/Runtime_131373). This makes the comment misleading andRequiresProcessIsolationlooks unnecessary here. Consider droppingRequiresProcessIsolationand simplifying the comment to match the established pattern so future readers don’t infer a harness constraint that doesn’t exist.
<!-- The bug only reproduces when the caller is optimized R2R code, so force crossgen on
the browser leg. AlwaysUseCrossGen2 only takes effect through the project's own run
script, which requires process isolation. -->
<AlwaysUseCrossGen2 Condition="'$(TargetOS)' == 'browser'">true</AlwaysUseCrossGen2>
<RequiresProcessIsolation>true</RequiresProcessIsolation>
|
/ba-g build timeouts |
WasmInterpreterToR2RThunkNode zero-padded pRet out to an aligned size after calling the R2R body. pRet is the interpreter's own return slot only when the interpreter made the call; it is the compiled caller's return buffer when we arrive via WasmR2RToInterpreterThunkNode, and CallDescrData::pRetBuffArg when we arrive via CallDescrWorkerInternal. In those cases the buffer is exactly as large as the struct, so the padding wrote past its end.
For a 12-byte struct that is four bytes into the next frame slot. In JsonDocument.Parse it zeroed the reference field of a live ReadOnlySpan and left its length intact, so the first Parse threw NullReferenceException from Utf8JsonReader.ReadSingleSegment.
Fixes #131640