Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

Description

@FaithfulAudio

Summary

Both PE-target ARM64 assembly implementations of ontop_fcontext
(src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
remainder of the resumed context's execution.

Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
report). Confirmed not present in either of the two structurally analogous,
already-correct siblings on the same target:

  • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
  • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
    symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
    consistent).

This is a structural, input-independent skew — every invocation of the ontop path on
Windows ARM64 hits it, not a data-dependent corruption.

Where it bites: /GS and SEH-based unwind, downstream

ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
ontop-function it jumps to eventually returns via the target context's own restored
LR, back into that target's original suspended call site — a site that expects sp
restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
instead, for the remaining lifetime of the resumed frame.

Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
stores a stack cookie in a frame which gets resumed via this path will have its
__security_check_cookie read the wrong stack slot on that frame's own exit, and fail
fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
SEH/C++ exception. A related failure mode hits boost::context::fiber's
forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
.pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
frame that's silently 16 bytes off from that expectation can make the unwinder
misidentify frame boundaries mid-walk.

We hit this downstream, three vendoring hops removed from this repo: hermes-windows
(Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
crash in a production React Native app under load (root-caused from 8 crash dumps across
2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
the companion downstream fix for hermes-windows (link added below once posted).

Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

History: where this was introduced

The asymmetry was introduced in
#201 "Windows arm64 fcontext support"
(merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
across a fiber switch — a good and necessary change. It correctly rewired every other
offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
the same commit got. The diff (against
src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

 ontop_fcontext proc BOOST_CONTEXT_EXPORT
; prepare stack for GP + FPU
- sub sp, sp, #0xb0+ sub sp, sp, #0xd0
...
; save LR as PC
- str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
...
+ ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
...
; skip pc
; restore stack from GP + FPU
- add sp, sp, #0xb0+ add sp, sp, #0xc0

A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
shortfall forward verbatim into the second file. Neither commit touched
jump_fcontext's analogous epilog, which is why it stayed correct throughout.

Proposed fix

Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
register logic changes.

--- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
; skip pc
; restore stack from GP + FPU
- add sp, sp, #0xc0+ add sp, sp, #0xd0
; jump to ontop-function
ret x2
--- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
// skip pc
// restore stack from GP + FPU
- add sp, sp, #0xc0+ add sp, sp, #0xd0
// jump to ontop-function
ret x2

(Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
at line 71, epilog at line 133.)

Validation status

  • Root cause: confirmed by direct inspection of develop HEAD (both files, both
    currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
    hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
    input-independent: it does not depend on data, timing, or heap state, only on whether
    the ontop path is taken.
  • Fix: validated by symmetry against the two already-correct sibling implementations in
    this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
    independently against a downstream engine that carries an identical fix — see the
    linked hermes-windows PR for that fix's own validation status (empirical repro or
    build+inspection, whichever applies at time of reading).
  • We have not run this repo's own test suite (libs/context/test) against the fix —
    we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
    want a PR rather than (or in addition to) this issue, we're happy to open one with the
    diff above; it's a minimal, mechanical, two-line change and should be straightforward
    to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
       blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
      }
      } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
      })();
      (function(){
      try {
      var __m = "github.com";
      var __re = new RegExp('^' + "github\\.com" + '
      
      Skip to content

      Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

      Description

      @FaithfulAudio

      Summary

      Both PE-target ARM64 assembly implementations of ontop_fcontext
      (src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
      src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
      prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
      only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
      bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
      remainder of the resumed context's execution.

      Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
      report). Confirmed not present in either of the two structurally analogous,
      already-correct siblings on the same target:

      • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
      • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
        symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
        consistent).

      This is a structural, input-independent skew — every invocation of the ontop path on
      Windows ARM64 hits it, not a data-dependent corruption.

      Where it bites: /GS and SEH-based unwind, downstream

      ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
      ontop-function it jumps to eventually returns via the target context's own restored
      LR, back into that target's original suspended call site — a site that expects sp
      restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
      leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
      instead, for the remaining lifetime of the resumed frame.

      Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
      stores a stack cookie in a frame which gets resumed via this path will have its
      __security_check_cookie read the wrong stack slot on that frame's own exit, and fail
      fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
      SEH/C++ exception. A related failure mode hits boost::context::fiber's
      forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
      marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
      .pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
      frame that's silently 16 bytes off from that expectation can make the unwinder
      misidentify frame boundaries mid-walk.

      We hit this downstream, three vendoring hops removed from this repo: hermes-windows
      (Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
      vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
      ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
      crash in a production React Native app under load (root-caused from 8 crash dumps across
      2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
      structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
      the companion downstream fix for hermes-windows (link added below once posted).

      Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
      Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
      ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

      History: where this was introduced

      The asymmetry was introduced in
      #201 "Windows arm64 fcontext support"
      (merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
      ("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
      0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
      TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
      across a fiber switch — a good and necessary change. It correctly rewired every other
      offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
      at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
      add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
      the same commit got. The diff (against
      src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

       ontop_fcontext proc BOOST_CONTEXT_EXPORT
      ; prepare stack for GP + FPU
      - sub sp, sp, #0xb0+ sub sp, sp, #0xd0
      ...
      ; save LR as PC
      - str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
      ...
      + ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
      ...
      ; skip pc
      ; restore stack from GP + FPU
      - add sp, sp, #0xb0+ add sp, sp, #0xc0

      A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
      2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
      syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
      shortfall forward verbatim into the second file. Neither commit touched
      jump_fcontext's analogous epilog, which is why it stayed correct throughout.

      Proposed fix

      Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
      jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
      register logic changes.

      --- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
      ; skip pc
      ; restore stack from GP + FPU
      - add sp, sp, #0xc0+ add sp, sp, #0xd0
      ; jump to ontop-function
      ret x2
      --- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
      // skip pc
      // restore stack from GP + FPU
      - add sp, sp, #0xc0+ add sp, sp, #0xd0
      // jump to ontop-function
      ret x2

      (Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
      prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
      at line 71, epilog at line 133.)

      Validation status

      • Root cause: confirmed by direct inspection of develop HEAD (both files, both
        currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
        hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
        input-independent: it does not depend on data, timing, or heap state, only on whether
        the ontop path is taken.
      • Fix: validated by symmetry against the two already-correct sibling implementations in
        this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
        independently against a downstream engine that carries an identical fix — see the
        linked hermes-windows PR for that fix's own validation status (empirical repro or
        build+inspection, whichever applies at time of reading).
      • We have not run this repo's own test suite (libs/context/test) against the fix —
        we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
        want a PR rather than (or in addition to) this issue, we're happy to open one with the
        diff above; it's a minimal, mechanical, two-line change and should be straightforward
        to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        No labels
        No labels

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

          Description

          @FaithfulAudio

          Summary

          Both PE-target ARM64 assembly implementations of ontop_fcontext
          (src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
          src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
          prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
          only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
          bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
          remainder of the resumed context's execution.

          Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
          report). Confirmed not present in either of the two structurally analogous,
          already-correct siblings on the same target:

          • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
          • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
            symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
            consistent).

          This is a structural, input-independent skew — every invocation of the ontop path on
          Windows ARM64 hits it, not a data-dependent corruption.

          Where it bites: /GS and SEH-based unwind, downstream

          ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
          ontop-function it jumps to eventually returns via the target context's own restored
          LR, back into that target's original suspended call site — a site that expects sp
          restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
          leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
          instead, for the remaining lifetime of the resumed frame.

          Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
          stores a stack cookie in a frame which gets resumed via this path will have its
          __security_check_cookie read the wrong stack slot on that frame's own exit, and fail
          fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
          SEH/C++ exception. A related failure mode hits boost::context::fiber's
          forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
          marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
          .pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
          frame that's silently 16 bytes off from that expectation can make the unwinder
          misidentify frame boundaries mid-walk.

          We hit this downstream, three vendoring hops removed from this repo: hermes-windows
          (Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
          vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
          ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
          crash in a production React Native app under load (root-caused from 8 crash dumps across
          2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
          structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
          the companion downstream fix for hermes-windows (link added below once posted).

          Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
          Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
          ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

          History: where this was introduced

          The asymmetry was introduced in
          #201 "Windows arm64 fcontext support"
          (merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
          ("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
          0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
          TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
          across a fiber switch — a good and necessary change. It correctly rewired every other
          offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
          at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
          add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
          the same commit got. The diff (against
          src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

           ontop_fcontext proc BOOST_CONTEXT_EXPORT
          ; prepare stack for GP + FPU
          - sub sp, sp, #0xb0+ sub sp, sp, #0xd0
          ...
          ; save LR as PC
          - str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
          ...
          + ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
          ...
          ; skip pc
          ; restore stack from GP + FPU
          - add sp, sp, #0xb0+ add sp, sp, #0xc0

          A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
          2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
          syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
          shortfall forward verbatim into the second file. Neither commit touched
          jump_fcontext's analogous epilog, which is why it stayed correct throughout.

          Proposed fix

          Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
          jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
          register logic changes.

          --- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
          ; skip pc
          ; restore stack from GP + FPU
          - add sp, sp, #0xc0+ add sp, sp, #0xd0
          ; jump to ontop-function
          ret x2
          --- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
          // skip pc
          // restore stack from GP + FPU
          - add sp, sp, #0xc0+ add sp, sp, #0xd0
          // jump to ontop-function
          ret x2

          (Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
          prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
          at line 71, epilog at line 133.)

          Validation status

          • Root cause: confirmed by direct inspection of develop HEAD (both files, both
            currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
            hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
            input-independent: it does not depend on data, timing, or heap state, only on whether
            the ontop path is taken.
          • Fix: validated by symmetry against the two already-correct sibling implementations in
            this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
            independently against a downstream engine that carries an identical fix — see the
            linked hermes-windows PR for that fix's own validation status (empirical repro or
            build+inspection, whichever applies at time of reading).
          • We have not run this repo's own test suite (libs/context/test) against the fix —
            we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
            want a PR rather than (or in addition to) this issue, we're happy to open one with the
            diff above; it's a minimal, mechanical, two-line change and should be straightforward
            to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            No labels
            No labels

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

              Description

              @FaithfulAudio

              Summary

              Both PE-target ARM64 assembly implementations of ontop_fcontext
              (src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
              src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
              prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
              only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
              bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
              remainder of the resumed context's execution.

              Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
              report). Confirmed not present in either of the two structurally analogous,
              already-correct siblings on the same target:

              • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
              • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
                symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
                consistent).

              This is a structural, input-independent skew — every invocation of the ontop path on
              Windows ARM64 hits it, not a data-dependent corruption.

              Where it bites: /GS and SEH-based unwind, downstream

              ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
              ontop-function it jumps to eventually returns via the target context's own restored
              LR, back into that target's original suspended call site — a site that expects sp
              restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
              leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
              instead, for the remaining lifetime of the resumed frame.

              Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
              stores a stack cookie in a frame which gets resumed via this path will have its
              __security_check_cookie read the wrong stack slot on that frame's own exit, and fail
              fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
              SEH/C++ exception. A related failure mode hits boost::context::fiber's
              forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
              marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
              .pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
              frame that's silently 16 bytes off from that expectation can make the unwinder
              misidentify frame boundaries mid-walk.

              We hit this downstream, three vendoring hops removed from this repo: hermes-windows
              (Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
              vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
              ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
              crash in a production React Native app under load (root-caused from 8 crash dumps across
              2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
              structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
              the companion downstream fix for hermes-windows (link added below once posted).

              Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
              Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
              ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

              History: where this was introduced

              The asymmetry was introduced in
              #201 "Windows arm64 fcontext support"
              (merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
              ("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
              0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
              TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
              across a fiber switch — a good and necessary change. It correctly rewired every other
              offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
              at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
              add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
              the same commit got. The diff (against
              src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

               ontop_fcontext proc BOOST_CONTEXT_EXPORT
              ; prepare stack for GP + FPU
              - sub sp, sp, #0xb0+ sub sp, sp, #0xd0
              ...
              ; save LR as PC
              - str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
              ...
              + ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
              ...
              ; skip pc
              ; restore stack from GP + FPU
              - add sp, sp, #0xb0+ add sp, sp, #0xc0

              A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
              2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
              syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
              shortfall forward verbatim into the second file. Neither commit touched
              jump_fcontext's analogous epilog, which is why it stayed correct throughout.

              Proposed fix

              Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
              jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
              register logic changes.

              --- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
              ; skip pc
              ; restore stack from GP + FPU
              - add sp, sp, #0xc0+ add sp, sp, #0xd0
              ; jump to ontop-function
              ret x2
              --- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
              // skip pc
              // restore stack from GP + FPU
              - add sp, sp, #0xc0+ add sp, sp, #0xd0
              // jump to ontop-function
              ret x2

              (Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
              prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
              at line 71, epilog at line 133.)

              Validation status

              • Root cause: confirmed by direct inspection of develop HEAD (both files, both
                currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
                hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
                input-independent: it does not depend on data, timing, or heap state, only on whether
                the ontop path is taken.
              • Fix: validated by symmetry against the two already-correct sibling implementations in
                this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
                independently against a downstream engine that carries an identical fix — see the
                linked hermes-windows PR for that fix's own validation status (empirical repro or
                build+inspection, whichever applies at time of reading).
              • We have not run this repo's own test suite (libs/context/test) against the fix —
                we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
                want a PR rather than (or in addition to) this issue, we're happy to open one with the
                diff above; it's a minimal, mechanical, two-line change and should be straightforward
                to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                No labels
                No labels

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions

                  , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
                  Skip to content

                  Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

                  Description

                  @FaithfulAudio

                  Summary

                  Both PE-target ARM64 assembly implementations of ontop_fcontext
                  (src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
                  src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
                  prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
                  only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
                  bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
                  remainder of the resumed context's execution.

                  Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
                  report). Confirmed not present in either of the two structurally analogous,
                  already-correct siblings on the same target:

                  • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
                  • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
                    symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
                    consistent).

                  This is a structural, input-independent skew — every invocation of the ontop path on
                  Windows ARM64 hits it, not a data-dependent corruption.

                  Where it bites: /GS and SEH-based unwind, downstream

                  ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
                  ontop-function it jumps to eventually returns via the target context's own restored
                  LR, back into that target's original suspended call site — a site that expects sp
                  restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
                  leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
                  instead, for the remaining lifetime of the resumed frame.

                  Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
                  stores a stack cookie in a frame which gets resumed via this path will have its
                  __security_check_cookie read the wrong stack slot on that frame's own exit, and fail
                  fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
                  SEH/C++ exception. A related failure mode hits boost::context::fiber's
                  forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
                  marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
                  .pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
                  frame that's silently 16 bytes off from that expectation can make the unwinder
                  misidentify frame boundaries mid-walk.

                  We hit this downstream, three vendoring hops removed from this repo: hermes-windows
                  (Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
                  vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
                  ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
                  crash in a production React Native app under load (root-caused from 8 crash dumps across
                  2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
                  structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
                  the companion downstream fix for hermes-windows (link added below once posted).

                  Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
                  Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
                  ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

                  History: where this was introduced

                  The asymmetry was introduced in
                  #201 "Windows arm64 fcontext support"
                  (merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
                  ("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
                  0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
                  TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
                  across a fiber switch — a good and necessary change. It correctly rewired every other
                  offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
                  at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
                  add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
                  the same commit got. The diff (against
                  src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

                   ontop_fcontext proc BOOST_CONTEXT_EXPORT
                  ; prepare stack for GP + FPU
                  - sub sp, sp, #0xb0+ sub sp, sp, #0xd0
                  ...
                  ; save LR as PC
                  - str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
                  ...
                  + ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
                  ...
                  ; skip pc
                  ; restore stack from GP + FPU
                  - add sp, sp, #0xb0+ add sp, sp, #0xc0

                  A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
                  2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
                  syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
                  shortfall forward verbatim into the second file. Neither commit touched
                  jump_fcontext's analogous epilog, which is why it stayed correct throughout.

                  Proposed fix

                  Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
                  jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
                  register logic changes.

                  --- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
                  ; skip pc
                  ; restore stack from GP + FPU
                  - add sp, sp, #0xc0+ add sp, sp, #0xd0
                  ; jump to ontop-function
                  ret x2
                  --- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
                  // skip pc
                  // restore stack from GP + FPU
                  - add sp, sp, #0xc0+ add sp, sp, #0xd0
                  // jump to ontop-function
                  ret x2

                  (Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
                  prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
                  at line 71, epilog at line 133.)

                  Validation status

                  • Root cause: confirmed by direct inspection of develop HEAD (both files, both
                    currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
                    hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
                    input-independent: it does not depend on data, timing, or heap state, only on whether
                    the ontop path is taken.
                  • Fix: validated by symmetry against the two already-correct sibling implementations in
                    this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
                    independently against a downstream engine that carries an identical fix — see the
                    linked hermes-windows PR for that fix's own validation status (empirical repro or
                    build+inspection, whichever applies at time of reading).
                  • We have not run this repo's own test suite (libs/context/test) against the fix —
                    we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
                    want a PR rather than (or in addition to) this issue, we're happy to open one with the
                    diff above; it's a minimal, mechanical, two-line change and should be straightforward
                    to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

                  Metadata

                  Metadata

                  Assignees

                  No one assigned

                    Labels

                    No labels
                    No labels

                    Type

                    No type

                    Projects

                    No projects

                      Milestone

                      No milestone

                      Relationships

                      None yet

                      Development

                      No branches or pull requests

                      Issue actions

                      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                      Skip to content

                      Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

                      Description

                      @FaithfulAudio

                      Summary

                      Both PE-target ARM64 assembly implementations of ontop_fcontext
                      (src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
                      src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
                      prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
                      only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
                      bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
                      remainder of the resumed context's execution.

                      Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
                      report). Confirmed not present in either of the two structurally analogous,
                      already-correct siblings on the same target:

                      • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
                      • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
                        symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
                        consistent).

                      This is a structural, input-independent skew — every invocation of the ontop path on
                      Windows ARM64 hits it, not a data-dependent corruption.

                      Where it bites: /GS and SEH-based unwind, downstream

                      ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
                      ontop-function it jumps to eventually returns via the target context's own restored
                      LR, back into that target's original suspended call site — a site that expects sp
                      restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
                      leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
                      instead, for the remaining lifetime of the resumed frame.

                      Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
                      stores a stack cookie in a frame which gets resumed via this path will have its
                      __security_check_cookie read the wrong stack slot on that frame's own exit, and fail
                      fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
                      SEH/C++ exception. A related failure mode hits boost::context::fiber's
                      forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
                      marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
                      .pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
                      frame that's silently 16 bytes off from that expectation can make the unwinder
                      misidentify frame boundaries mid-walk.

                      We hit this downstream, three vendoring hops removed from this repo: hermes-windows
                      (Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
                      vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
                      ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
                      crash in a production React Native app under load (root-caused from 8 crash dumps across
                      2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
                      structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
                      the companion downstream fix for hermes-windows (link added below once posted).

                      Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
                      Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
                      ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

                      History: where this was introduced

                      The asymmetry was introduced in
                      #201 "Windows arm64 fcontext support"
                      (merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
                      ("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
                      0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
                      TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
                      across a fiber switch — a good and necessary change. It correctly rewired every other
                      offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
                      at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
                      add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
                      the same commit got. The diff (against
                      src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

                       ontop_fcontext proc BOOST_CONTEXT_EXPORT
                      ; prepare stack for GP + FPU
                      - sub sp, sp, #0xb0+ sub sp, sp, #0xd0
                      ...
                      ; save LR as PC
                      - str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
                      ...
                      + ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
                      ...
                      ; skip pc
                      ; restore stack from GP + FPU
                      - add sp, sp, #0xb0+ add sp, sp, #0xc0

                      A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
                      2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
                      syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
                      shortfall forward verbatim into the second file. Neither commit touched
                      jump_fcontext's analogous epilog, which is why it stayed correct throughout.

                      Proposed fix

                      Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
                      jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
                      register logic changes.

                      --- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
                      ; skip pc
                      ; restore stack from GP + FPU
                      - add sp, sp, #0xc0+ add sp, sp, #0xd0
                      ; jump to ontop-function
                      ret x2
                      --- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
                      // skip pc
                      // restore stack from GP + FPU
                      - add sp, sp, #0xc0+ add sp, sp, #0xd0
                      // jump to ontop-function
                      ret x2

                      (Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
                      prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
                      at line 71, epilog at line 133.)

                      Validation status

                      • Root cause: confirmed by direct inspection of develop HEAD (both files, both
                        currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
                        hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
                        input-independent: it does not depend on data, timing, or heap state, only on whether
                        the ontop path is taken.
                      • Fix: validated by symmetry against the two already-correct sibling implementations in
                        this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
                        independently against a downstream engine that carries an identical fix — see the
                        linked hermes-windows PR for that fix's own validation status (empirical repro or
                        build+inspection, whichever applies at time of reading).
                      • We have not run this repo's own test suite (libs/context/test) against the fix —
                        we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
                        want a PR rather than (or in addition to) this issue, we're happy to open one with the
                        diff above; it's a minimal, mechanical, two-line change and should be straightforward
                        to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

                      Metadata

                      Metadata

                      Assignees

                      No one assigned

                        Labels

                        No labels
                        No labels

                        Type

                        No type

                        Projects

                        No projects

                          Milestone

                          No milestone

                          Relationships

                          None yet

                          Development

                          No branches or pull requests

                          Issue actions

                          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
                          Skip to content

                          Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

                          Description

                          @FaithfulAudio

                          Summary

                          Both PE-target ARM64 assembly implementations of ontop_fcontext
                          (src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
                          src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
                          prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
                          only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
                          bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
                          remainder of the resumed context's execution.

                          Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
                          report). Confirmed not present in either of the two structurally analogous,
                          already-correct siblings on the same target:

                          • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
                          • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
                            symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
                            consistent).

                          This is a structural, input-independent skew — every invocation of the ontop path on
                          Windows ARM64 hits it, not a data-dependent corruption.

                          Where it bites: /GS and SEH-based unwind, downstream

                          ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
                          ontop-function it jumps to eventually returns via the target context's own restored
                          LR, back into that target's original suspended call site — a site that expects sp
                          restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
                          leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
                          instead, for the remaining lifetime of the resumed frame.

                          Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
                          stores a stack cookie in a frame which gets resumed via this path will have its
                          __security_check_cookie read the wrong stack slot on that frame's own exit, and fail
                          fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
                          SEH/C++ exception. A related failure mode hits boost::context::fiber's
                          forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
                          marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
                          .pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
                          frame that's silently 16 bytes off from that expectation can make the unwinder
                          misidentify frame boundaries mid-walk.

                          We hit this downstream, three vendoring hops removed from this repo: hermes-windows
                          (Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
                          vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
                          ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
                          crash in a production React Native app under load (root-caused from 8 crash dumps across
                          2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
                          structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
                          the companion downstream fix for hermes-windows (link added below once posted).

                          Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
                          Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
                          ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

                          History: where this was introduced

                          The asymmetry was introduced in
                          #201 "Windows arm64 fcontext support"
                          (merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
                          ("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
                          0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
                          TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
                          across a fiber switch — a good and necessary change. It correctly rewired every other
                          offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
                          at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
                          add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
                          the same commit got. The diff (against
                          src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

                           ontop_fcontext proc BOOST_CONTEXT_EXPORT
                          ; prepare stack for GP + FPU
                          - sub sp, sp, #0xb0+ sub sp, sp, #0xd0
                          ...
                          ; save LR as PC
                          - str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
                          ...
                          + ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
                          ...
                          ; skip pc
                          ; restore stack from GP + FPU
                          - add sp, sp, #0xb0+ add sp, sp, #0xc0

                          A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
                          2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
                          syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
                          shortfall forward verbatim into the second file. Neither commit touched
                          jump_fcontext's analogous epilog, which is why it stayed correct throughout.

                          Proposed fix

                          Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
                          jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
                          register logic changes.

                          --- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
                          ; skip pc
                          ; restore stack from GP + FPU
                          - add sp, sp, #0xc0+ add sp, sp, #0xd0
                          ; jump to ontop-function
                          ret x2
                          --- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
                          // skip pc
                          // restore stack from GP + FPU
                          - add sp, sp, #0xc0+ add sp, sp, #0xd0
                          // jump to ontop-function
                          ret x2

                          (Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
                          prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
                          at line 71, epilog at line 133.)

                          Validation status

                          • Root cause: confirmed by direct inspection of develop HEAD (both files, both
                            currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
                            hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
                            input-independent: it does not depend on data, timing, or heap state, only on whether
                            the ontop path is taken.
                          • Fix: validated by symmetry against the two already-correct sibling implementations in
                            this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
                            independently against a downstream engine that carries an identical fix — see the
                            linked hermes-windows PR for that fix's own validation status (empirical repro or
                            build+inspection, whichever applies at time of reading).
                          • We have not run this repo's own test suite (libs/context/test) against the fix —
                            we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
                            want a PR rather than (or in addition to) this issue, we're happy to open one with the
                            diff above; it's a minimal, mechanical, two-line change and should be straightforward
                            to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

                          Metadata

                          Metadata

                          Assignees

                          No one assigned

                            Labels

                            No labels
                            No labels

                            Type

                            No type

                            Projects

                            No projects

                              Milestone

                              No milestone

                              Relationships

                              None yet

                              Development

                              No branches or pull requests

                              Issue actions

                              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                              Skip to content

                              Windows ARM64 (PE) ontop_fcontext: epilog deallocates 0xc0 of the 0xd0 bytes the prolog allocates — 16-byte SP skew, breaks /GS and SEH-based unwind on resume #336

                              Description

                              @FaithfulAudio

                              Summary

                              Both PE-target ARM64 assembly implementations of ontop_fcontext
                              (src/asm/ontop_arm64_aapcs_pe_armasm.asm, MSVC ARMASM64 syntax, and
                              src/asm/ontop_arm64_aapcs_pe_armclang.S, Clang/GAS syntax) have an asymmetric
                              prolog/epilog: the prolog reserves 0xd0 (208) bytes of stack, but the epilog releases
                              only 0xc0 (192) before tail-jumping into the ontop-function. The missing 0x10 (16)
                              bytes are never reclaimed, so sp sits 16 bytes below where it should be for the
                              remainder of the resumed context's execution.

                              Confirmed present, byte-for-byte, on develop @ d2142b6925… (current HEAD as of this
                              report). Confirmed not present in either of the two structurally analogous,
                              already-correct siblings on the same target:

                              • src/asm/jump_arm64_aapcs_pe_armasm.asm — symmetric, 0xd0/0xd0.
                              • src/asm/ontop_arm64_aapcs_elf_gas.S (the ELF/GAS target's ontop_fcontext) —
                                symmetric, 0xb0/0xb0 (smaller frame: no Windows TEB/TIB save/restore, but internally
                                consistent).

                              This is a structural, input-independent skew — every invocation of the ontop path on
                              Windows ARM64 hits it, not a data-dependent corruption.

                              Where it bites: /GS and SEH-based unwind, downstream

                              ontop_fcontext is reached via a tail-jump (ret x2, not a call), and the
                              ontop-function it jumps to eventually returns via the target context's own restored
                              LR, back into that target's original suspended call site — a site that expects sp
                              restored to its pre-suspend value, exactly as the symmetric jump_fcontext epilog would
                              leave it. The asymmetric ontop_fcontext epilog leaves sp 16 bytes below that value
                              instead, for the remaining lifetime of the resumed frame.

                              Any caller built with /GS (MSVC, on by default) or -fstack-protector-all (Clang) that
                              stores a stack cookie in a frame which gets resumed via this path will have its
                              __security_check_cookie read the wrong stack slot on that frame's own exit, and fail
                              fast with STATUS_STACK_BUFFER_OVERRUN (0xc0000409) — a __fastfail, not a catchable
                              SEH/C++ exception. A related failure mode hits boost::context::fiber's
                              forced_unwind-driven teardown (which also uses the ontop path to inject an unwind
                              marker into the target context): Windows' _CxxFrameHandler3 walks frames using static
                              .pdata/.xdata unwind metadata that encodes each frame's expected SP delta, and a
                              frame that's silently 16 bytes off from that expectation can make the unwinder
                              misidentify frame boundaries mid-walk.

                              We hit this downstream, three vendoring hops removed from this repo: hermes-windows
                              (Microsoft's Windows fork of Meta's Hermes JS engine, used by react-native-windows)
                              vendors boost_1_86_0 wholesale, including this exact file pair, unmodified. On Windows
                              ARM64, running Hermes' interpreter on a custom fiber stack, this produces a deterministic
                              crash in a production React Native app under load (root-caused from 8 crash dumps across
                              2 engine builds and 4 ASLR bases — all module-relative-identical, confirming the
                              structural nature of the bug). Full writeup and a downstream fix for hermes-windows:
                              the companion downstream fix for hermes-windows (link added below once posted).

                              Anyone else running boost::context::fiber (or anything built on it — Boost.Fiber,
                              Boost.Coroutine2) on Windows ARM64 under /GS or -fstack-protector-all, exercising the
                              ontop/resume_with/fiber-teardown path under load, is exposed to the same defect.

                              History: where this was introduced

                              The asymmetry was introduced in
                              #201 "Windows arm64 fcontext support"
                              (merged 2022-07-05), specifically in commit abf8e04e23cf05a499594e674d1c90db39117662
                              ("Spport Windows arm64 cpp exception", 2022-06-26). That commit extended the frame from
                              0xb0 to 0xd0 to save/restore four TEB fields (TeStackBase, TeStackLimit,
                              TeDeallocationStack, TeFiberData) so C++ exception unwinding sees a consistent TIB
                              across a fiber switch — a good and necessary change. It correctly rewired every other
                              offset in the function (the PC-save slot moved 0xa00xc0, the new TIB fields landed
                              at 0xa0/0xb0, the prolog's sub moved 0xb00xd0) but only bumped the epilog's
                              add from 0xb0 to 0xc0 — a 0x10 step, not the 0x20 step every other offset in
                              the same commit got. The diff (against
                              src/asm/ontop_arm64_aapcs_pe_armasm.asm) makes the off-by-one-constant plain:

                               ontop_fcontext proc BOOST_CONTEXT_EXPORT
                              ; prepare stack for GP + FPU
                              - sub sp, sp, #0xb0+ sub sp, sp, #0xd0
                              ...
                              ; save LR as PC
                              - str x30, [sp, #0xa0]+ str x30, [sp, #0xc0]++ ; save current stack base and limit+ ldp x5, x6, [x18, #0x08]+ stp x5, x6, [sp, #0xa0]+ ; save current fiber data and deallocation stack+ ldr x5, [x18, #0x1478]+ ldr x6, [x18, #0x20]+ stp x5, x6, [sp, #0xb0]
                              ...
                              + ; restore stack base and limit+ ldp x5, x6, [sp, #0xa0]+ stp x5, x6, [x18, #0x08]+ ; restore fiber data and deallocation stack+ ldp x5, x6, [sp, #0xb0]+ str x5, [x18, #0x1478]+ str x6, [x18, #0x20]
                              ...
                              ; skip pc
                              ; restore stack from GP + FPU
                              - add sp, sp, #0xb0+ add sp, sp, #0xc0

                              A second commit, e878e8edb2b2… ("Convert ARM64 armasm to armclang for Windows clang",
                              2024-06-19), mechanically transcribed this file's ARMASM64 syntax into the Clang/GAS
                              syntax now at src/asm/ontop_arm64_aapcs_pe_armclang.S, carrying the same 0x10
                              shortfall forward verbatim into the second file. Neither commit touched
                              jump_fcontext's analogous epilog, which is why it stayed correct throughout.

                              Proposed fix

                              Make the epilog symmetric with the prolog (add sp, sp, #0xd0) in both files, matching
                              jump_fcontext on the same target and ontop_fcontext on the ELF target. No other
                              register logic changes.

                              --- a/src/asm/ontop_arm64_aapcs_pe_armasm.asm+++ b/src/asm/ontop_arm64_aapcs_pe_armasm.asm@@ -122,7 +122,7 @@ ontop_fcontext proc BOOST_CONTEXT_EXPORT
                              ; skip pc
                              ; restore stack from GP + FPU
                              - add sp, sp, #0xc0+ add sp, sp, #0xd0
                              ; jump to ontop-function
                              ret x2
                              --- a/src/asm/ontop_arm64_aapcs_pe_armclang.S+++ b/src/asm/ontop_arm64_aapcs_pe_armclang.S@@ -128,7 +128,7 @@ ontop_fcontext:
                              // skip pc
                              // restore stack from GP + FPU
                              - add sp, sp, #0xc0+ add sp, sp, #0xd0
                              // jump to ontop-function
                              ret x2

                              (Exact line numbers as of develop @ d2142b6925…: src/asm/ontop_arm64_aapcs_pe_armasm.asm
                              prolog at line 65, epilog at line 127; src/asm/ontop_arm64_aapcs_pe_armclang.S prolog
                              at line 71, epilog at line 133.)

                              Validation status

                              • Root cause: confirmed by direct inspection of develop HEAD (both files, both
                                currently 0xd0/0xc0) and by disassembly of a downstream production crash (8 dumps,
                                hermes-windows on Windows ARM64 — see linked report above). The skew is structural and
                                input-independent: it does not depend on data, timing, or heap state, only on whether
                                the ontop path is taken.
                              • Fix: validated by symmetry against the two already-correct sibling implementations in
                                this same file family (jump_fcontext PE variant, ontop_fcontext ELF variant), and
                                independently against a downstream engine that carries an identical fix — see the
                                linked hermes-windows PR for that fix's own validation status (empirical repro or
                                build+inspection, whichever applies at time of reading).
                              • We have not run this repo's own test suite (libs/context/test) against the fix —
                                we don't have a boost ARM64-on-Windows test harness in our environment. If maintainers
                                want a PR rather than (or in addition to) this issue, we're happy to open one with the
                                diff above; it's a minimal, mechanical, two-line change and should be straightforward
                                to verify against existing Boost.Context/Boost.Fiber ARM64 CI if one exists.

                              Metadata

                              Metadata

                              Assignees

                              No one assigned

                                Labels

                                No labels
                                No labels

                                Type

                                No type

                                Projects

                                No projects

                                  Milestone

                                  No milestone

                                  Relationships

                                  None yet

                                  Development

                                  No branches or pull requests

                                  Issue actions