You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Checked runtime asserts in CallCatchFunclet on Windows x64 with shadow stacks when a thread abort raised at the end of a catch is caught in the same method #135530
On windows-x64 with CET shadow stacks enabled, a Checked runtime asserts in CallCatchFunclet (exceptionhandling.cpp) when a ThreadAbortException that the runtime raises again at the end of a catch handler is caught by another handler of the same method:
This is JIT-compiled code; the interpreter is not involved. linux-x64 and osx-arm64 run the same program correctly.
Reproduction
#pragma warning disable SYSLIB0046usingSystem;usingSystem.Runtime;usingSystem.Runtime.CompilerServices;usingSystem.Threading;usingSystem.Threading.Tasks;staticclassProgram{staticvolatilebools_ready;staticvolatileints_counter;[MethodImpl(MethodImplOptions.NoInlining)]staticvoidRunInfiniteLoop(){while(true){if((++s_counter&0xfffff)==0)Thread.Sleep(0);}}[MethodImpl(MethodImplOptions.NoInlining)]staticvoidNop(){}[MethodImpl(MethodImplOptions.NoInlining)]staticvoidNestedCatch(){try{Nop();try{s_ready=true;RunInfiniteLoop();}catch(ThreadAbortException){Console.WriteLine("inner catch");}// the abort is raised again here, inside the outer tryNop();}catch(ThreadAbortException){Console.WriteLine("outer catch");}}staticintMain(){varcts=newCancellationTokenSource();Task.Run(()=>{while(!s_ready)Thread.Sleep(10);cts.Cancel();});try{ControlledExecution.Run(NestedCatch,cts.Token);}catch(OperationCanceledException){Console.WriteLine("OperationCanceledException");}return100;}}
Expected (and what linux-x64 and osx-arm64 print): inner catch, outer catch, OperationCanceledException. windows-x64 Checked (CET enabled; corerun.exe is CET compatible): the assert above, on main at 6ff62b1. The same happens when the catch is followed by a try block that catches the abort.
Analysis
EECodeManager::ResumeAfterCatch sends the thread to THROW_CONTROL_FOR_THREAD_FUNCTION, and RedirectForThrowControl records the SSP at the point where the thread was redirected in the FaultingExceptionFrame (ThrowControlForThread, pfef->SetSSP(ssp)). The frame being resumed was interrupted there, not stopped at a call, so no shadow stack slot holds its ControlPC. That makes the third clause of the assert false whenever the catch handler is in that same frame. The SSP itself is the right one to resume with: it points at the frame's return address.
With the assert also accepting a frame that has faulted ((pHandlerIP != NULL) && exInfo->m_frameIter.m_crawl.HasFaulted()), the program above and the "try block at the resume point" case print the expected output on windows-x64 Checked with CET, with no shadow stack violation. Release builds do not have the assert.
Note
This issue was prepared with AI assistance (Anthropic Claude).
Activity
dotnet-policy-service commentedon Oct 9, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @VSadov
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Oct 9, 2026
Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.
Accept a faulted frame in the shadow stack assert of CallCatchFunclet (…