Skip to content

[Interpreter] ResumeAfterCatchException escapes ThrowControlForThread when compiled code raises a thread abort again and an interpreted frame catches it #135532

Description

@pcshrosbree

Description

With DOTNET_InterpMode=1 (user code interpreted, CoreLib compiled), a ControlledExecution action that cancels itself crashes the process:

  • linux-x64 and osx-arm64 (Checked): terminate called after throwing an instance of 'ResumeAfterCatchException'
  • windows-x64 (Checked): an assert in exception handling

This is the case of ControlledExecutionTests.CancelItselfOutsideOfTryCatchFinally, which has been disabled since #122308. It is separate from #135463 (fixed by #135520): it still fails with that fix, and #135520's test does not cover it.

var cts = new CancellationTokenSource();
ControlledExecution.Run(() => cts.Cancel(), cts.Token); // expected: OperationCanceledException

Analysis

CancellationTokenSource.Cancel (compiled) catches the ThreadAbortException, and at the end of that catch EECodeManager::ResumeAfterCatch raises it again through ThrowControlForThread. The abort is then caught by an interpreted frame further up (ControlledExecution.Run translates it, or the user's catch). Resuming in an interpreted frame unwinds the native frames with a C++ ResumeAfterCatchException. Other places that start a dispatch on top of managed frames catch it and rethrow it past those frames with INSTALL_RESUME_AFTER_CATCH_HANDLER_WITH_CONTEXT (IL_Throw, HandleManagedFault), but ThrowControlForThread does not, so the exception runs into the managed frames the thread was redirected from.

Adding that handler fixes linux-x64 and osx-arm64. On windows-x64 with shadow stacks it then asserts *(ULONG64*)targetSSP == pContext->Rip in ExecuteFunctionBelowContext. RethrowResumeAfterCatchExceptionSkipManagedFrames searches for a "precise" SSP on the assumption that the slot below the SSP holds a return address in the context's frame. The SSP that RedirectForThrowControl records belongs to a frame that was interrupted, not stopped at a call, so that slot is stale and the search moves one slot too far. The same mismatch is behind #135530.

I have a fix that adds the handler and tells it that the context was interrupted, so it uses the SSP as it is. With it and #135520, the case above passes under DOTNET_InterpMode 1, 2 and 3 on linux-x64, osx-arm64 and windows-x64 (Checked, CET enabled on Windows). I'll open it as a draft PR stacked on #135520.

Note

This issue was prepared with AI assistance (Anthropic Claude).

Activity

  1. dotnet-policy-service commented on Oct 9, 2026

    @dotnet-policy-service
    Contributor

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

  2. dotnet-policy-service commented on Oct 9, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @BrzVlad, @janvorli
    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

    Type

    No type

    Projects

    • Status
      No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions