32-bit x86 SEH dispatch infinite loop: FS:[0] returns 0x428 instead of valid handler (Wine/Proton on AArch64)
- Dominant language
- C++
- Stars
- 8k
- Forks
- 351
- Avg merge
- 12h 31m
- Merged PRs (30d)
- 102
Description
Bug Report: 32-bit x86 SEH Dispatch Infinite Loop via Corrupted FS:[0] Read
Repository: https://github.com/FEX-Emu/FEX
Component: 32-bit x86 emulation / FS segment register handling
Severity: Critical (prevents 32-bit Windows games from running)
Environment
Field | Value
-- | --
FEX Build Date | May 22 2026 (# SMP May 22 2026 13:57:52 from strings)
FEX Binary | /usr/bin/FEX, /usr/bin/FEXInterpreter
Host Architecture | AArch64 (Snapdragon 8 Elite / SM8750)
OS | RockNix nightly 20260524, BUILD_BRANCH=next
Kernel | AArch64 Linux
binfmt_misc | FEX-x86 and FEX-x86_64 both registered and enabled
Proton Version | GE-Proton9-27 (Wine-staging 9.0) via Steam
SteamLinuxRuntime | SteamLinuxRuntime_sniper
Application | Lineage 2 Gracia Epilogue client (l2.exe) — PE32 executable, Intel 80386, 32-bit
Summary
When running a 32-bit (x86) Windows application through Wine (GE-Proton9-27 / Wine-staging 9.0) on an AArch64 host using FEX as the x86 translation layer, FS:[0] consistently returns 0x428 instead of the correct SEH (Structured Exception Handling) chain pointer stored in the Thread Information Block (TIB).
This causes Wine's exception dispatcher to call an invalid handler address (0x428), which itself triggers another access violation, creating an infinite recursive exception dispatch loop that runs for millions of iterations until the thread stack overflows.
Steps to Reproduce
- AArch64 host with FEX binfmt registered for x86 and x86_64
- GE-Proton9-27 installed via Steam (Wine binaries are x86/x86_64 ELFs, intercepted by FEX binfmt)
- Launch any 32-bit Windows PE32 application through Proton that uses SEH (Structured Exception Handling) during initialization — specifically UE2-based games like Lineage 2 Gracia Epilogue
- Observe the crash
Launch options used:
PROTON_LOG=1 FEX_TSOENABLED=1 DXVK_ASYNC=1 WINEDLLOVERRIDES="dsetup=n" %command%
Expected Behavior
When a 32-bit Windows application raises an exception, Wine should:
- Read the SEH chain head from
FS:[0](Thread Information Block offset 0) - Walk the chain to find an appropriate handler
- Call the handler, which returns and resumes execution
- This is normal UE2 engine behavior — it intentionally uses SEH for hardware capability detection
Actual Behavior
Wine's seh:dispatch_exception reads FS:[0] and receives 0x428 — an invalid address that is not a valid code pointer. This produces:
0128:trace:seh:dispatch_exception code=c0000005 flags=0 addr=216A1D68 ip=216a1d68
0128:trace:seh:dispatch_exception info[0]=00000001
0128:trace:seh:dispatch_exception info[1]=00430000
0128:warn:seh:dispatch_exception EXCEPTION_ACCESS_VIOLATION exception (code=c0000005) raised
0128:trace:seh:dispatch_exception eax=216a7829 ebx=9ab0040d ecx=ffffaf74 edx=30257aca esi=0000f219 edi=23eefd3f
0128:trace:seh:dispatch_exception ebp=15ab0014 esp=00430004 cs=0030 ss=0000 ds=0000 es=0000 fs=0063 gs=006b flags=00000206
0128:trace:seh:call_stack_handlers calling handler at 23EEFD3F code=c0000005 flags=0
0128:trace:seh:dispatch_exception code=c0000005 flags=0 addr=23EEFD3F ip=23eefd3f
0128:warn:seh:dispatch_exception EXCEPTION_ACCESS_VIOLATION exception (code=c0000005) raised
0128:trace:seh:call_stack_handlers calling handler at 7BF9F0A0 code=c0000005 flags=0
0128:trace:seh:call_stack_handlers handler at 7BF9F0A0 returned 2
0128:trace:seh:call_stack_handlers calling handler at 00000428 code=c0000005 flags=10
0128:trace:seh:dispatch_exception code=c0000005 flags=0 addr=00000428 ip=00000428
The loop then repeats infinitely with info[1]=00000428 on every iteration:
0128:trace:seh:dispatch_exception info[1]=00000428
0128:trace:seh:dispatch_exception info[1]=00000428
0128:trace:seh:dispatch_exception info[1]=00000428
... (millions of iterations) ...
0128:trace:seh:dispatch_exception stack overflow 1472 bytes addr 0x7bfa94b3 stack 0x330a40 (0x330000-0x331000-0x2330000)
The log reached over 406 million lines before stack exhaustion. Testing with 32MB of stack reserve (SizeOfStackReserve=0x02000000 in the PE header) confirmed the loop always exhausts all available stack — the recursion depth scales with stack size, not with actual game initialization depth.
Root Cause Analysis
In 32-bit Windows x86, FS is a segment register pointing to the Thread Information Block (TIB). FS:[0] holds the head of the SEH handler chain on the stack.
FEX appears to be incorrectly resolving FS:[0] reads in 32-bit x86 code running inside Wine on AArch64. Instead of returning the valid SEH chain pointer, it returns 0x428 (decimal 1064) — a small integer that is clearly not a code address.
Evidence:
ecx=00000428appears as the SEH handler address in the first cascading exceptioninfo[1]=00000428repeats across millions of exception dispatch iterations- The same crash pattern occurs across Proton 8.0 and GE-Proton9-27
- The crash is fully reproducible and deterministic
FEX_TSOENABLED=1partially mitigated timing but did not resolve the issue
Wine binary architecture (GE-Proton9-27):
wine: ELF 32-bit LSB pie executable, Intel 80386 ← intercepted by FEX-x86 binfmt
wine-preloader: ELF 32-bit LSB executable, Intel 80386 ← intercepted by FEX-x86 binfmt
wine64: ELF 64-bit LSB pie executable, x86-64 ← intercepted by FEX-x86_64 binfmt
wineserver: ELF 64-bit LSB pie executable, x86-64 ← intercepted by FEX-x86_64 binfmt
All Wine binaries are x86/x86_64 ELFs running through FEX binfmt on the AArch64 host.
Additional Notes
prctl(PR_SET_SECCOMP, ...): Invalid argumentis logged by Wine, indicating seccomp-based syscall interception is unavailable. This may be related.- The application (Lineage 2 / UE2) uses SEH intentionally for hardware detection — this is not a bug in the application.
FEX_TSOENABLED=1extended the time before crash (from ~3900s to ~8300s in Wine log timestamps) but did not prevent it.- The issue is present with both Proton 8.0 and GE-Proton9-27.
- The
~/.fex-emu/user config directory being present (even empty) caused FEX to stop using the system RootFS config, breaking all x86 emulation. Removing the directory restored function. This may indicate a config discovery issue worth addressing separately.
Workarounds Attempted (None Successful)
FEX_TSOENABLED=1— partial mitigation onlyFEX_PARANOIDTSO=1— broke FEX entirely (caused immediate crash of all x86 processes)- Increasing
SizeOfStackReservein PE header to 32MB — confirmed the loop scales with stack size, not game code depth PROTON_USE_WINED3D=1— no effect on SEH behaviorWINEARCH=win32— no effect
Contact / Context
Discovered while attempting to run Lineage 2 Gracia Epilogue (private server) on an Odin 3 handheld (Snapdragon 8 Elite) running RockNix. The application fully initializes (all DLLs load, D3D9 initializes, UE2 Core.dll and Engine.dll load) before hitting this crash, suggesting the underlying emulation is otherwise functional for this workload.
Contributor guide
Assessment
This issue has not been assessed yet.