Skip to content

fix(smoke): de-flake 10c by corrupting the signature instead of removing it - #692

Merged
DeusData merged 3 commits into
mainfrom
fix/smoke-10c-deflake
Jun 29, 2026
Merged

fix(smoke): de-flake 10c by corrupting the signature instead of removing it#692
DeusData merged 3 commits into
mainfrom
fix/smoke-10c-deflake

Conversation

@DeusData

@DeusData DeusData commented Jun 29, 2026

Copy link
Copy Markdown
Owner

De-flakes smoke test 10c (Phase 10 binary security E2E).

Root cause: the test's premise — "a tampered/unsigned arm64 binary is SIGKILLed (exit 137)" — is empirically false on current macOS CI runners for an ad-hoc-signed CLI binary. The cbm binary has no CS_KILL/hardened-runtime flag, so the kernel does not kill a bad page at runtime. Observed across four dry-runs:

Tamper method Run Result
codesign --remove-signature then run 28350650225, 28354735368 exit 0 (macOS 11+ ad-hoc re-signs on exec)
corrupt signature blob then run 28360363173 exit 0 (re-signed/ignored)
tamper code then run 28365724001 exit 132 (SIGILL — executes the garbage, not killed)

So no runtime exit code is a deterministic guard here, and "tamper → crash" is near-tautological (zeroed code crashes regardless of signing).

Fix: assert the real, deterministic integrity invariant — codesign --verify rejects a tampered copy (the CodeDirectory page hashes no longer match the modified code), while the untampered binary verifies cleanly (10a above). It's a pure userspace hash check; no tampered code is ever executed. Tampering is done on a separate copy ($SECURITY_BIN.tampered), so the original is untouched and the 10e re-sign step stays valid. x86_64 unchanged.

This is neither a skip nor a weakening — it replaces a false runtime premise with the actual security property the test exists to verify (the signature detects tampering). Unrelated to the seal fix (#677). Verified via a full dry-run on this branch.

Refs: garrytan/gstack#997, nodejs/node#40827

DeusData added 3 commits June 29, 2026 10:55
…ing it

Smoke test 10c (Phase 10 binary security E2E) verifies that an arm64 binary
with an invalid code signature is SIGKILLed (exit 137). It did
`codesign --remove-signature` then ran the binary, expecting the kernel to
kill it. But since macOS 11, a binary with NO LC_CODE_SIGNATURE is ad-hoc
re-signed on exec by newer macOS and RUNS (exit 0) -- so the test went flaky
and then consistently red on updated CI runner images (dry-runs 28350650225
and 28354735368 both failed only here; every other job, including the full
cross-platform test matrix, passed).

Corrupt the signature blob in place instead, leaving the LC_CODE_SIGNATURE
load command intact: AMFI then sees "signed but invalid" and rejects the
binary before any user code runs (deterministic 137). Only the signature
blob is garbled (not the code), so the later 10e re-sign step stays valid --
it replaces the blob and the code is untouched. x86_64 keeps
remove-signature (code signing is not enforced there).

Refs: github.com/garrytan/gstack#997, github.com/nodejs/node#40827

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Refines the previous 10c change on this branch. Garbling only the signature
blob still ran (exit=0 on dry-run 28360363173): since macOS 11 a binary with
a missing/invalid signature is ad-hoc re-signed on exec by newer macOS and
RUNS, so neither remove-signature nor a corrupt blob triggers the kill.

The reliable "tampered binary is SIGKILLed (137)" trigger is tampering the
SIGNED CODE while leaving the valid signature attached: the kernel validates
each executed page against the intact CodeDirectory hash, finds the mismatch,
and kills the process before user code runs. Zero the entry-point
instructions (LC_MAIN entryoff, extracted dynamically) plus a span of early
__text on a SEPARATE copy, leaving the Mach-O header + load commands intact so
it still parses. The original binary is untouched, so the later 10e re-sign
step stays valid. x86_64 keeps remove-signature (code signing is not enforced
there).

Refs: github.com/garrytan/gstack#997, github.com/nodejs/node#40827

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The "tampered arm64 binary is SIGKILLed (137)" premise is empirically false on
current macOS CI runners for an ad-hoc-signed CLI binary -- the binary has no
CS_KILL/hardened-runtime flag, so a tampered code page is not killed: it
executes the garbage and crashes with SIGILL (exit 132, run 28365724001), not
137. (remove-signature and corrupt-blob both ad-hoc re-sign on exec and run to
exit 0.) So no runtime exit code is a deterministic guard here, and
"tamper -> crash" is near-tautological (zeroed code crashes regardless of
signing).

Assert the real, deterministic integrity invariant instead: `codesign
--verify` REJECTS a tampered copy (the CodeDirectory page hashes no longer
match the modified code), while the untampered binary verifies cleanly (10a).
It is a pure userspace hash check -- no tampered code is executed. The copy is
separate, so the original binary stays intact for the 10e re-sign test.

Refs: github.com/garrytan/gstack#997, github.com/nodejs/node#40827

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
@DeusData
DeusData merged commit 7824e50 into main Jun 29, 2026
52 checks passed
@DeusData
DeusData deleted the fix/smoke-10c-deflake branch June 29, 2026 14:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant