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
On Windows x64, a WebAssembly guest trap that is hardware-signaled (unreachable, memory out-of-bounds, integer divide-by-zero) terminates the host process with 0xC0000409 (STATUS_STACK_BUFFER_OVERRUN, exit -1073740791) instead of surfacing as a catchable Wasmtime.TrapException.
Root cause (confirmed): Intel CET / hardware shadow stack enforcement. Since .NET 9 Preview 6, the .NET SDK marks the generated Windows apphost executable with /CETCOMPAT (documented breaking change). This enables the hardware shadow stack for the process. Wasmtime's Windows trap recovery performs a non-local thread-context restore to a landing pad that is neither present on the shadow stack nor registered in an EH continuation allow-list, so Windows terminates the process.
Cooperative metered traps (fuel / epoch) are not affected — they return through the normal call path and never perform a non-local context restore.
Note
This issue was originally filed as a .NET 10 regression with an SEH / security-cookie theory, and stated that disabling CET had no effect. All three of those claims were wrong. They are struck through in Retracted claims at the bottom rather than deleted, since earlier comments refer to them. Body corrected 2026-08-17.
Environment
OS
Windows 11, build 26200 (x64)
CPU
AMD Ryzen 7 7735HS (Zen 3+, shadow-stack capable)
SDK
.NET SDK 10.0.302
Runtimes
Microsoft.NETCore.App 8.0.30 and 10.0.10
Package
Wasmtime 44.0.0
System mitigation policy
UserShadowStack: NOTSET (OS defaults, no per-machine override)
Configurations
Reproduces in both Debug and Release, deterministically (5/5 runs)
Although the feature is branded Intel CET, this reproduces on an AMD CPU — it is not vendor-specific.
The original report listed ".NET SDK: .NET 10.0.10". That figure is the runtime version (Microsoft.NETCore.App 10.0.10), not the SDK version; .NET SDK versions use feature bands (10.0.3xx), and the SDK here is 10.0.302. Both are listed above under the correct names.
Affected .NET versions: 9.0 and later (anything whose apphost is /CETCOMPAT-marked). .NET 8 and earlier are unaffected.
Trap matrix
Each scenario runs in its own process so that a fail-fast in one cannot mask the others.
usingWasmtime;Console.WriteLine("Starting Wasmtime probe on Windows x64...");usingvarengine=newEngine();usingvarmodule=Module.FromText(engine,"unreachable_test",@"(module (func (export ""run"") unreachable ))");usingvarlinker=newLinker(engine);usingvarstore=newStore(engine);varinstance=linker.Instantiate(store,module);varrun=instance.GetFunction("run");if(runisnull){Console.Error.WriteLine("Failed to find exported function 'run'");return1;}try{Console.WriteLine("Invoking guest function that traps...");run.Invoke();Console.Error.WriteLine("FAIL: Expected TrapException was not thrown.");return1;}catch(TrapExceptionex){Console.WriteLine($"SUCCESS: Caught expected TrapException: {ex.Message}");return0;}
Steps:
Build and run the generated .exe on net8.0 → prints SUCCESS, exit 0.
Rebuild on net10.0 (or net9.0) and run the generated .exe → process terminates, exit -1073740791.
Run the generated apphost .exe, not dotnet run/dotnet app.dll. dotnet.exe is not CET-marked, so launching through it hides the bug entirely.
Root-cause evidence
Isolation controls. Each row changes exactly one variable against the net10.0 baseline:
Experiment
Result
Eliminates
net8.0 app on .NET 8 runtime
✅ exit 0
— (baseline)
net8.0 app forced onto the .NET 10.0.10 runtime (DOTNET_ROLL_FORWARD=LatestMajor, resolution confirmed via COREHOST_TRACE=1)
✅ exit 0
the runtime version
net10.0 app with DOTNET_LegacyExceptionHandling=1
❌ 0xC0000409
the new managed EH model added in .NET 9
net10.0 app with lib/net8.0/Wasmtime.Dotnet.dll swapped in
❌ 0xC0000409
the managed assembly / package asset selection
net10.0 app launched via dotnet.exe instead of its apphost
✅ exit 0
→ isolates the host executable
net10.0 apphost with the CET bit patched to 0
✅ exit 0
→ causation, single bit
net10.0 rebuilt with <CetCompat>false</CetCompat>
✅ exit 0
→ supported workaround
Two of these deserve emphasis:
The .NET 10 runtime is not implicated. The same runtime executes the same IL successfully when the process is launched by a non-CET host.
Package asset selection is a red herring. A net10.0 project resolves lib/net9.0/Wasmtime.Dotnet.dll while net8.0 resolves lib/net8.0/…; those files differ in content (identical length, same 44.0.0.0 assembly version). "The net9 managed build is at fault" is a natural first guess and it is wrong — forcing the net8.0 assembly into the net10.0 app still fail-fasts.
PE-level state.IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT (debug directory type 20) and the load-config EH continuation table:
Binary
CET compat
GuardFlags
EH continuation targets
apphost (net8.0 build)
absent
0x10417500
12
apphost (net10.0 build)
SET
0x10417500
12
dotnet.exe (10.0.10)
absent
—
—
coreclr.dll (10.0.10)
—
0x10417500
1017
wasmtime.dll (44.0.0, win-x64)
absent
0x00000100
0
The apphost carried /EHCONT in both .NET 8 and .NET 10. What .NET 9 changed is the CETCOMPAT bit that makes the constraint enforced — not the presence of continuation metadata. wasmtime.dll registers zero EH continuation targets and is not CFG-instrumented, so no address it continues to can be on an allow-list, by construction.
This restores correct TrapException behavior for every trap kind. Verify the flag actually landed in the emitted binary — an incremental build will silently reuse an already-generated CET-marked apphost and the property will appear to do nothing:
Trade-off: this disables ROP protection for the whole process, so it is a mitigation rather than a fix.
Alternatively, opt the specific executable out via Windows Security / exploit-protection policy (UserShadowStack).
Proposed upstream resolution
wasmtime-dotnet (or Wasmtime's Windows trap handling) needs to make its trap-recovery continuation target legal under CET. The correct mechanism depends on where that landing pad lives, which I have not established:
if it is static code inside wasmtime.dll (a setjmp-style recovery point) → building that library with /EHCONT (plus CFG) is sufficient;
if it is JIT-generated code → SetProcessDynamicEHContinuationTargets must be called at runtime as code regions are published.
At minimum, shipping a win-x64wasmtime.dll that is CET-aware would let .NET 9+ consumers keep shadow-stack protection enabled.
What I did not do: I did not attach a debugger to capture the faulting continuation address or the fail-fast subcode, so the specific RtlRestoreContext/NtContinue attribution is inference from the documented CET contract, not a direct observation. My measurements stop at "wasmtime.dll registers zero continuation targets." Happy to run that debugger session if it would help.
Retracted claims from the original report
Preserved for reference because earlier comments respond to them.
"This is a .NET 10 regression; .NET 8 works." → The trigger landed in .NET 9 Preview 6. .NET 8 does work, but .NET 9 is equally affected; I simply went 8 → 10 and never tested 9.
"Disabling CET (Hardware-enforced Stack Protection) does not alter the behavior." → False. Disabling CET fixes it completely, on every trap kind. My original observation was almost certainly an incremental build reusing the CET-marked apphost — the rebuild reported success in 0.39 s and emitted an exe whose CET bit was still set.
"Changes to SEH stack unwinding or security cookies in .NET 10 conflict with Wasmtime's native exception handler." → No evidence.DOTNET_LegacyExceptionHandling=1 does not help, which specifically rules out the .NET 9+ managed exception-handling rewrite.
Still standing from the original report: metered fuel/epoch traps behave correctly (re-verified above). The claim that Linux hosts (POSIX signals) are unaffected is carried over from the original report and was not re-tested in this round — though it is consistent with the mechanism, since CET shadow-stack enforcement is a Windows process mitigation.
Summary
On Windows x64, a WebAssembly guest trap that is hardware-signaled (
unreachable, memory out-of-bounds, integer divide-by-zero) terminates the host process with0xC0000409(STATUS_STACK_BUFFER_OVERRUN, exit-1073740791) instead of surfacing as a catchableWasmtime.TrapException.Root cause (confirmed): Intel CET / hardware shadow stack enforcement. Since .NET 9 Preview 6, the .NET SDK marks the generated Windows
apphostexecutable with/CETCOMPAT(documented breaking change). This enables the hardware shadow stack for the process. Wasmtime's Windows trap recovery performs a non-local thread-context restore to a landing pad that is neither present on the shadow stack nor registered in an EH continuation allow-list, so Windows terminates the process.Cooperative metered traps (fuel / epoch) are not affected — they return through the normal call path and never perform a non-local context restore.
Note
This issue was originally filed as a .NET 10 regression with an SEH / security-cookie theory, and stated that disabling CET had no effect. All three of those claims were wrong. They are struck through in Retracted claims at the bottom rather than deleted, since earlier comments refer to them. Body corrected 2026-08-17.
Environment
Microsoft.NETCore.App8.0.30 and 10.0.10Wasmtime44.0.0UserShadowStack: NOTSET(OS defaults, no per-machine override)Although the feature is branded Intel CET, this reproduces on an AMD CPU — it is not vendor-specific.
The original report listed ".NET SDK: .NET 10.0.10". That figure is the runtime version (
Microsoft.NETCore.App10.0.10), not the SDK version; .NET SDK versions use feature bands (10.0.3xx), and the SDK here is10.0.302. Both are listed above under the correct names.Affected .NET versions: 9.0 and later (anything whose apphost is
/CETCOMPAT-marked). .NET 8 and earlier are unaffected.Trap matrix
Each scenario runs in its own process so that a fail-fast in one cannot mask the others.
unreachableUnreachable0xC0000409UnreachableMemoryOutOfBounds0xC0000409MemoryOutOfBoundsIntegerDivisionByZero0xC0000409IntegerDivisionByZeroOutOfFuelOutOfFuelOutOfFuelInterruptInterruptInterruptMinimal reproduction
Program.cs:Steps:
.exeonnet8.0→ printsSUCCESS, exit0.net10.0(ornet9.0) and run the generated.exe→ process terminates, exit-1073740791.Root-cause evidence
Isolation controls. Each row changes exactly one variable against the
net10.0baseline:net8.0app on .NET 8 runtimenet8.0app forced onto the .NET 10.0.10 runtime (DOTNET_ROLL_FORWARD=LatestMajor, resolution confirmed viaCOREHOST_TRACE=1)net10.0app withDOTNET_LegacyExceptionHandling=10xC0000409net10.0app withlib/net8.0/Wasmtime.Dotnet.dllswapped in0xC0000409net10.0app launched viadotnet.exeinstead of its apphostnet10.0apphost with the CET bit patched to0net10.0rebuilt with<CetCompat>false</CetCompat>Two of these deserve emphasis:
net10.0project resolveslib/net9.0/Wasmtime.Dotnet.dllwhilenet8.0resolveslib/net8.0/…; those files differ in content (identical length, same44.0.0.0assembly version). "The net9 managed build is at fault" is a natural first guess and it is wrong — forcing thenet8.0assembly into thenet10.0app still fail-fasts.PE-level state.
IMAGE_DLLCHARACTERISTICS_EX_CET_COMPAT(debug directory type 20) and the load-config EH continuation table:GuardFlagsapphost(net8.0 build)0x10417500apphost(net10.0 build)0x10417500dotnet.exe(10.0.10)coreclr.dll(10.0.10)0x10417500wasmtime.dll(44.0.0, win-x64)0x00000100The apphost carried
/EHCONTin both .NET 8 and .NET 10. What .NET 9 changed is theCETCOMPATbit that makes the constraint enforced — not the presence of continuation metadata.wasmtime.dllregisters zero EH continuation targets and is not CFG-instrumented, so no address it continues to can be on an allow-list, by construction.Workaround
Opt the application out of CET:
This restores correct
TrapExceptionbehavior for every trap kind. Verify the flag actually landed in the emitted binary — an incremental build will silently reuse an already-generated CET-marked apphost and the property will appear to do nothing:Trade-off: this disables ROP protection for the whole process, so it is a mitigation rather than a fix.
Alternatively, opt the specific executable out via Windows Security / exploit-protection policy (
UserShadowStack).Proposed upstream resolution
wasmtime-dotnet(or Wasmtime's Windows trap handling) needs to make its trap-recovery continuation target legal under CET. The correct mechanism depends on where that landing pad lives, which I have not established:wasmtime.dll(asetjmp-style recovery point) → building that library with/EHCONT(plus CFG) is sufficient;SetProcessDynamicEHContinuationTargetsmust be called at runtime as code regions are published.At minimum, shipping a
win-x64wasmtime.dllthat is CET-aware would let .NET 9+ consumers keep shadow-stack protection enabled.What I did not do: I did not attach a debugger to capture the faulting continuation address or the fail-fast subcode, so the specific
RtlRestoreContext/NtContinueattribution is inference from the documented CET contract, not a direct observation. My measurements stop at "wasmtime.dllregisters zero continuation targets." Happy to run that debugger session if it would help.Retracted claims from the original report
Preserved for reference because earlier comments respond to them.
"This is a .NET 10 regression; .NET 8 works."→ The trigger landed in .NET 9 Preview 6. .NET 8 does work, but .NET 9 is equally affected; I simply went 8 → 10 and never tested 9."Disabling CET (Hardware-enforced Stack Protection) does not alter the behavior."→ False. Disabling CET fixes it completely, on every trap kind. My original observation was almost certainly an incremental build reusing the CET-marked apphost — the rebuild reported success in 0.39 s and emitted an exe whose CET bit was still set."Changes to SEH stack unwinding or security cookies in .NET 10 conflict with Wasmtime's native exception handler."→ No evidence.DOTNET_LegacyExceptionHandling=1does not help, which specifically rules out the .NET 9+ managed exception-handling rewrite.Still standing from the original report: metered fuel/epoch traps behave correctly (re-verified above). The claim that Linux hosts (POSIX signals) are unaffected is carried over from the original report and was not re-tested in this round — though it is consistent with the mechanism, since CET shadow-stack enforcement is a Windows process mitigation.