Skip to content

Commit 49a9c26

Browse files
committed
fix(smoke): tamper signed code, not the signature, to de-flake 10c
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>
1 parent b27dc64 commit 49a9c26

1 file changed

Lines changed: 22 additions & 17 deletions

File tree

scripts/smoke-test.sh

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,26 +1208,31 @@ if [ "$(uname -s)" = "Darwin" ]; then
12081208
BIN_ARCH=$(file "$SECURITY_BIN" | grep -o 'arm64\|x86_64' | head -1)
12091209

12101210
if [ "$BIN_ARCH" = "arm64" ]; then
1211-
# arm64: a binary whose code signature is INVALID must SIGKILL (exit 137 = 128+9).
1212-
# We CORRUPT the signature blob in place instead of `codesign --remove-signature`: since macOS
1213-
# 11, a binary with NO LC_CODE_SIGNATURE is ad-hoc re-signed on exec by newer macOS and RUNS
1214-
# (exit 0) — that made this test flaky, then consistently red on updated CI runner images.
1215-
# Leaving the LC_CODE_SIGNATURE load command intact but garbling its blob makes AMFI see
1216-
# "signed but invalid" and reject it before any user code runs (deterministic). Corrupting only
1217-
# the signature blob (not the code) keeps the later 10e re-sign valid — it replaces the blob and
1218-
# the code is untouched. Refs: github.com/garrytan/gstack#997, nodejs/node#40827.
1219-
SIG_OFF=$(otool -l "$SECURITY_BIN" 2>/dev/null | awk '/LC_CODE_SIGNATURE/{f=1} f&&/dataoff/{print $2; exit}')
1220-
if [ -n "$SIG_OFF" ]; then
1221-
head -c 1024 /dev/urandom | dd of="$SECURITY_BIN" bs=1 seek="$((SIG_OFF + 8))" count=1024 conv=notrunc 2>/dev/null
1222-
else
1223-
codesign --remove-signature "$SECURITY_BIN" 2>/dev/null || true
1224-
fi
1211+
# arm64: a SIGNED binary whose CODE has been tampered (CodeDirectory hash mismatch) must be
1212+
# SIGKILLed (exit 137 = 128+9) by AMFI before user code runs. Neither `codesign
1213+
# --remove-signature` nor garbling the signature blob triggers this — since macOS 11 a binary
1214+
# with a missing/invalid signature is ad-hoc re-signed on exec by newer macOS and RUNS (exit 0),
1215+
# which made this test flaky then consistently red on updated CI runner images. The reliable
1216+
# trigger is tampering the signed CODE while leaving the valid signature attached: the kernel
1217+
# validates the executed page against the (intact) CodeDirectory hash, finds the mismatch, and
1218+
# kills the process. Done on a SEPARATE copy so the original $SECURITY_BIN stays intact for the
1219+
# 10e re-sign test. Refs: github.com/garrytan/gstack#997, github.com/nodejs/node#40827.
1220+
TAMPER_BIN="${SECURITY_BIN}.tampered"
1221+
cp "$SECURITY_BIN" "$TAMPER_BIN"
1222+
# Zero the entry-point instructions (LC_MAIN entryoff = start of __text) plus a span of early
1223+
# __text, leaving the Mach-O header + load commands (offset 0..entryoff) intact so the binary
1224+
# still parses. The tampered code pages no longer match their CodeDirectory hashes -> SIGKILL.
1225+
ENTRY_OFF=$(otool -l "$SECURITY_BIN" 2>/dev/null | awk '/LC_MAIN/{f=1} f&&/entryoff/{print $2; exit}')
1226+
ENTRY_OFF=${ENTRY_OFF:-2184}
1227+
dd if=/dev/zero of="$TAMPER_BIN" bs=1 seek="$ENTRY_OFF" count=4096 conv=notrunc 2>/dev/null
1228+
dd if=/dev/zero of="$TAMPER_BIN" bs=4096 seek=4 count=512 conv=notrunc 2>/dev/null
12251229
UNSIGNED_EXIT=0
1226-
"$SECURITY_BIN" --version > /dev/null 2>&1 || UNSIGNED_EXIT=$?
1230+
"$TAMPER_BIN" --version > /dev/null 2>&1 || UNSIGNED_EXIT=$?
1231+
rm -f "$TAMPER_BIN"
12271232
if [ "$UNSIGNED_EXIT" -eq 137 ] || [ "$UNSIGNED_EXIT" -eq 9 ]; then
1228-
echo "OK 10c: invalid-signature arm64 binary killed (exit $UNSIGNED_EXIT)"
1233+
echo "OK 10c: tampered-code arm64 binary killed (exit $UNSIGNED_EXIT)"
12291234
else
1230-
echo "FAIL 10c: invalid-signature arm64 exit=$UNSIGNED_EXIT (expected 137)"
1235+
echo "FAIL 10c: tampered-code arm64 exit=$UNSIGNED_EXIT (expected 137)"
12311236
exit 1
12321237
fi
12331238
else

0 commit comments

Comments
 (0)