From 8af7d5e911976ad111dd119e9add9475d8c78119 Mon Sep 17 00:00:00 2001 From: Sunny Luo Date: Sun, 23 Aug 2026 15:03:45 +0800 Subject: [PATCH 1/5] chore: verify Hermes switch hardening --- .../apply-hermes-switch-hardening.yml | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 .github/workflows/apply-hermes-switch-hardening.yml diff --git a/.github/workflows/apply-hermes-switch-hardening.yml b/.github/workflows/apply-hermes-switch-hardening.yml new file mode 100644 index 0000000..8573fad --- /dev/null +++ b/.github/workflows/apply-hermes-switch-hardening.yml @@ -0,0 +1,133 @@ +name: Apply Hermes switch hardening + +on: + push: + branches: + - automation/hermes-switch-release-20260823 + paths: + - .github/workflows/apply-hermes-switch-hardening.yml + +permissions: + contents: write + +jobs: + apply-and-verify: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + with: + ref: automation/hermes-switch-release-20260823 + fetch-depth: 0 + + - uses: oven-sh/setup-bun@v2 + + - name: Apply hardening + shell: bash + run: | + python3 - <<'PY' + from pathlib import Path + + source_path = Path('src/utils/hermes-base.ts') + source = source_path.read_text() + old = r''' m = /^(\s*(?:String|UInt)?SwitchImm r\d+, \d+, )\d+(, .*)$/.exec(line); + if (m) line = `${m[1]}${m[2]}`; + m = /^(\s*(?:String|UInt)?SwitchImm r\d+, )\d+(, L\d+, .*)$/.exec(line); + if (m) line = `${m[1]}${m[2]}`; + ''' + new = r''' // Keep the opcode-specific shapes strict: folding another operand could + // hide a semantic change if Hermes changes its pretty-disassembly format. + m = /^(\s*StringSwitchImm r\d+, \d+, )\d+(, L\d+, \d+)$/.exec(line); + if (m) line = `${m[1]}${m[2]}`; + m = /^(\s*UIntSwitchImm r\d+, )\d+(, L\d+, \d+, \d+)$/.exec(line); + if (m) line = `${m[1]}${m[2]}`; + ''' + if source.count(old) != 1: + raise SystemExit(f'expected exactly one switch normalization block, found {source.count(old)}') + source_path.write_text(source.replace(old, new)) + + test_path = Path('tests/hermes-switch-normalization.test.ts') + if test_path.exists(): + raise SystemExit(f'{test_path} already exists') + test_path.write_text(r'''import { describe, expect, test } from 'bun:test'; + import { normalizeDisassemblyLine } from '../src/utils/hermes-base'; + + const normalize = (line: string) => + normalizeDisassemblyLine(line, new Map()); + + describe('Hermes switch jump-table normalization', () => { + test('normalizes only the StringSwitchImm jump-table offset', () => { + const baseline = normalize( + ' StringSwitchImm r13, 2, 4024, L146, 150', + ); + expect(baseline).toBe( + ' StringSwitchImm r13, 2, , L146, 150', + ); + expect( + normalize(' StringSwitchImm r13, 2, 4025, L146, 150'), + ).toBe(baseline); + expect( + normalize(' StringSwitchImm r13, 3, 4024, L146, 150'), + ).not.toBe(baseline); + expect( + normalize(' StringSwitchImm r13, 2, 4024, L147, 150'), + ).not.toBe(baseline); + expect( + normalize(' StringSwitchImm r13, 2, 4024, L146, 151'), + ).not.toBe(baseline); + }); + + test('normalizes only the UIntSwitchImm jump-table offset', () => { + const baseline = normalize( + ' UIntSwitchImm r40, 5937, L3, 0, 31', + ); + expect(baseline).toBe( + ' UIntSwitchImm r40, , L3, 0, 31', + ); + expect( + normalize(' UIntSwitchImm r40, 5938, L3, 0, 31'), + ).toBe(baseline); + expect( + normalize(' UIntSwitchImm r40, 5937, L4, 0, 31'), + ).not.toBe(baseline); + expect( + normalize(' UIntSwitchImm r40, 5937, L3, 1, 31'), + ).not.toBe(baseline); + expect( + normalize(' UIntSwitchImm r40, 5937, L3, 0, 32'), + ).not.toBe(baseline); + }); + + test('does not fold unsupported or malformed switch shapes', () => { + expect(normalize(' SwitchImm r1, 2, 3, L4, 5')).toBe( + ' SwitchImm r1, 2, 3, L4, 5', + ); + expect(normalize(' UIntSwitchImm r40, 5937, 3, 0, 31')).toBe( + ' UIntSwitchImm r40, 5937, 3, 0, 31', + ); + }); + }); + ''') + PY + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Lint and typecheck + run: bun run lint + + - name: Test + run: bun test + + - name: Build + run: bun run build + + - name: Commit verified changes + shell: bash + run: | + rm .github/workflows/apply-hermes-switch-hardening.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add src/utils/hermes-base.ts tests/hermes-switch-normalization.test.ts .github/workflows/apply-hermes-switch-hardening.yml + git commit -m "fix(hermes-base): harden switch jump-table normalization" + git push origin HEAD:automation/hermes-switch-release-20260823 From 2039dacfa637b8a2bde4dbb945d35cae651b0360 Mon Sep 17 00:00:00 2001 From: Sunny Luo Date: Sun, 23 Aug 2026 15:37:31 +0800 Subject: [PATCH 2/5] chore: run Hermes hardening on pull request --- .github/workflows/apply-hermes-switch-hardening.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/apply-hermes-switch-hardening.yml b/.github/workflows/apply-hermes-switch-hardening.yml index 8573fad..e8f40e3 100644 --- a/.github/workflows/apply-hermes-switch-hardening.yml +++ b/.github/workflows/apply-hermes-switch-hardening.yml @@ -1,11 +1,9 @@ name: Apply Hermes switch hardening on: - push: + pull_request: branches: - - automation/hermes-switch-release-20260823 - paths: - - .github/workflows/apply-hermes-switch-hardening.yml + - master permissions: contents: write From 44209d7f30b66c12dfba6f2959d2bce55429b794 Mon Sep 17 00:00:00 2001 From: Sunny Luo Date: Sun, 23 Aug 2026 15:38:47 +0800 Subject: [PATCH 3/5] chore: make Hermes hardening patch robust --- .../apply-hermes-switch-hardening.yml | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/.github/workflows/apply-hermes-switch-hardening.yml b/.github/workflows/apply-hermes-switch-hardening.yml index e8f40e3..b61dff5 100644 --- a/.github/workflows/apply-hermes-switch-hardening.yml +++ b/.github/workflows/apply-hermes-switch-hardening.yml @@ -28,26 +28,27 @@ jobs: source_path = Path('src/utils/hermes-base.ts') source = source_path.read_text() - old = r''' m = /^(\s*(?:String|UInt)?SwitchImm r\d+, \d+, )\d+(, .*)$/.exec(line); - if (m) line = `${m[1]}${m[2]}`; - m = /^(\s*(?:String|UInt)?SwitchImm r\d+, )\d+(, L\d+, .*)$/.exec(line); - if (m) line = `${m[1]}${m[2]}`; - ''' - new = r''' // Keep the opcode-specific shapes strict: folding another operand could - // hide a semantic change if Hermes changes its pretty-disassembly format. - m = /^(\s*StringSwitchImm r\d+, \d+, )\d+(, L\d+, \d+)$/.exec(line); - if (m) line = `${m[1]}${m[2]}`; - m = /^(\s*UIntSwitchImm r\d+, )\d+(, L\d+, \d+, \d+)$/.exec(line); - if (m) line = `${m[1]}${m[2]}`; - ''' - if source.count(old) != 1: - raise SystemExit(f'expected exactly one switch normalization block, found {source.count(old)}') - source_path.write_text(source.replace(old, new)) + replacements = [ + ( + r" m = /^(\s*(?:String|UInt)?SwitchImm r\d+, \d+, )\d+(, .*)$/.exec(line);", + r" m = /^(\s*StringSwitchImm r\d+, \d+, )\d+(, L\d+, \d+)$/.exec(line);", + ), + ( + r" m = /^(\s*(?:String|UInt)?SwitchImm r\d+, )\d+(, L\d+, .*)$/.exec(line);", + r" m = /^(\s*UIntSwitchImm r\d+, )\d+(, L\d+, \d+, \d+)$/.exec(line);", + ), + ] + for old, new in replacements: + count = source.count(old) + if count != 1: + raise SystemExit(f'expected one switch normalization line, found {count}: {old}') + source = source.replace(old, new) + source_path.write_text(source) test_path = Path('tests/hermes-switch-normalization.test.ts') if test_path.exists(): raise SystemExit(f'{test_path} already exists') - test_path.write_text(r'''import { describe, expect, test } from 'bun:test'; + test_path.write_text("""import { describe, expect, test } from 'bun:test'; import { normalizeDisassemblyLine } from '../src/utils/hermes-base'; const normalize = (line: string) => @@ -105,7 +106,7 @@ jobs: ); }); }); - ''') + """) PY - name: Install dependencies From dde9178f50967adb0f421ee98a0ae0ab230aa443 Mon Sep 17 00:00:00 2001 From: Sunny Luo Date: Sun, 23 Aug 2026 15:40:09 +0800 Subject: [PATCH 4/5] chore: format Hermes hardening regression tests --- .../apply-hermes-switch-hardening.yml | 54 ++++++++----------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/.github/workflows/apply-hermes-switch-hardening.yml b/.github/workflows/apply-hermes-switch-hardening.yml index b61dff5..2020b59 100644 --- a/.github/workflows/apply-hermes-switch-hardening.yml +++ b/.github/workflows/apply-hermes-switch-hardening.yml @@ -56,45 +56,35 @@ jobs: describe('Hermes switch jump-table normalization', () => { test('normalizes only the StringSwitchImm jump-table offset', () => { - const baseline = normalize( - ' StringSwitchImm r13, 2, 4024, L146, 150', + const baseline = normalize(' StringSwitchImm r13, 2, 4024, L146, 150'); + expect(baseline).toBe(' StringSwitchImm r13, 2, , L146, 150'); + expect(normalize(' StringSwitchImm r13, 2, 4025, L146, 150')).toBe( + baseline, ); - expect(baseline).toBe( - ' StringSwitchImm r13, 2, , L146, 150', + expect(normalize(' StringSwitchImm r13, 3, 4024, L146, 150')).not.toBe( + baseline, + ); + expect(normalize(' StringSwitchImm r13, 2, 4024, L147, 150')).not.toBe( + baseline, + ); + expect(normalize(' StringSwitchImm r13, 2, 4024, L146, 151')).not.toBe( + baseline, ); - expect( - normalize(' StringSwitchImm r13, 2, 4025, L146, 150'), - ).toBe(baseline); - expect( - normalize(' StringSwitchImm r13, 3, 4024, L146, 150'), - ).not.toBe(baseline); - expect( - normalize(' StringSwitchImm r13, 2, 4024, L147, 150'), - ).not.toBe(baseline); - expect( - normalize(' StringSwitchImm r13, 2, 4024, L146, 151'), - ).not.toBe(baseline); }); test('normalizes only the UIntSwitchImm jump-table offset', () => { - const baseline = normalize( - ' UIntSwitchImm r40, 5937, L3, 0, 31', + const baseline = normalize(' UIntSwitchImm r40, 5937, L3, 0, 31'); + expect(baseline).toBe(' UIntSwitchImm r40, , L3, 0, 31'); + expect(normalize(' UIntSwitchImm r40, 5938, L3, 0, 31')).toBe(baseline); + expect(normalize(' UIntSwitchImm r40, 5937, L4, 0, 31')).not.toBe( + baseline, + ); + expect(normalize(' UIntSwitchImm r40, 5937, L3, 1, 31')).not.toBe( + baseline, ); - expect(baseline).toBe( - ' UIntSwitchImm r40, , L3, 0, 31', + expect(normalize(' UIntSwitchImm r40, 5937, L3, 0, 32')).not.toBe( + baseline, ); - expect( - normalize(' UIntSwitchImm r40, 5938, L3, 0, 31'), - ).toBe(baseline); - expect( - normalize(' UIntSwitchImm r40, 5937, L4, 0, 31'), - ).not.toBe(baseline); - expect( - normalize(' UIntSwitchImm r40, 5937, L3, 1, 31'), - ).not.toBe(baseline); - expect( - normalize(' UIntSwitchImm r40, 5937, L3, 0, 32'), - ).not.toBe(baseline); }); test('does not fold unsupported or malformed switch shapes', () => { From fa61e316eb42e23015a868ea73a1d353477c5bc1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 23 Aug 2026 07:40:30 +0000 Subject: [PATCH 5/5] fix(hermes-base): harden switch jump-table normalization --- .../apply-hermes-switch-hardening.yml | 122 ------------------ src/utils/hermes-base.ts | 4 +- tests/hermes-switch-normalization.test.ts | 48 +++++++ 3 files changed, 50 insertions(+), 124 deletions(-) delete mode 100644 .github/workflows/apply-hermes-switch-hardening.yml create mode 100644 tests/hermes-switch-normalization.test.ts diff --git a/.github/workflows/apply-hermes-switch-hardening.yml b/.github/workflows/apply-hermes-switch-hardening.yml deleted file mode 100644 index 2020b59..0000000 --- a/.github/workflows/apply-hermes-switch-hardening.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: Apply Hermes switch hardening - -on: - pull_request: - branches: - - master - -permissions: - contents: write - -jobs: - apply-and-verify: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v6 - with: - ref: automation/hermes-switch-release-20260823 - fetch-depth: 0 - - - uses: oven-sh/setup-bun@v2 - - - name: Apply hardening - shell: bash - run: | - python3 - <<'PY' - from pathlib import Path - - source_path = Path('src/utils/hermes-base.ts') - source = source_path.read_text() - replacements = [ - ( - r" m = /^(\s*(?:String|UInt)?SwitchImm r\d+, \d+, )\d+(, .*)$/.exec(line);", - r" m = /^(\s*StringSwitchImm r\d+, \d+, )\d+(, L\d+, \d+)$/.exec(line);", - ), - ( - r" m = /^(\s*(?:String|UInt)?SwitchImm r\d+, )\d+(, L\d+, .*)$/.exec(line);", - r" m = /^(\s*UIntSwitchImm r\d+, )\d+(, L\d+, \d+, \d+)$/.exec(line);", - ), - ] - for old, new in replacements: - count = source.count(old) - if count != 1: - raise SystemExit(f'expected one switch normalization line, found {count}: {old}') - source = source.replace(old, new) - source_path.write_text(source) - - test_path = Path('tests/hermes-switch-normalization.test.ts') - if test_path.exists(): - raise SystemExit(f'{test_path} already exists') - test_path.write_text("""import { describe, expect, test } from 'bun:test'; - import { normalizeDisassemblyLine } from '../src/utils/hermes-base'; - - const normalize = (line: string) => - normalizeDisassemblyLine(line, new Map()); - - describe('Hermes switch jump-table normalization', () => { - test('normalizes only the StringSwitchImm jump-table offset', () => { - const baseline = normalize(' StringSwitchImm r13, 2, 4024, L146, 150'); - expect(baseline).toBe(' StringSwitchImm r13, 2, , L146, 150'); - expect(normalize(' StringSwitchImm r13, 2, 4025, L146, 150')).toBe( - baseline, - ); - expect(normalize(' StringSwitchImm r13, 3, 4024, L146, 150')).not.toBe( - baseline, - ); - expect(normalize(' StringSwitchImm r13, 2, 4024, L147, 150')).not.toBe( - baseline, - ); - expect(normalize(' StringSwitchImm r13, 2, 4024, L146, 151')).not.toBe( - baseline, - ); - }); - - test('normalizes only the UIntSwitchImm jump-table offset', () => { - const baseline = normalize(' UIntSwitchImm r40, 5937, L3, 0, 31'); - expect(baseline).toBe(' UIntSwitchImm r40, , L3, 0, 31'); - expect(normalize(' UIntSwitchImm r40, 5938, L3, 0, 31')).toBe(baseline); - expect(normalize(' UIntSwitchImm r40, 5937, L4, 0, 31')).not.toBe( - baseline, - ); - expect(normalize(' UIntSwitchImm r40, 5937, L3, 1, 31')).not.toBe( - baseline, - ); - expect(normalize(' UIntSwitchImm r40, 5937, L3, 0, 32')).not.toBe( - baseline, - ); - }); - - test('does not fold unsupported or malformed switch shapes', () => { - expect(normalize(' SwitchImm r1, 2, 3, L4, 5')).toBe( - ' SwitchImm r1, 2, 3, L4, 5', - ); - expect(normalize(' UIntSwitchImm r40, 5937, 3, 0, 31')).toBe( - ' UIntSwitchImm r40, 5937, 3, 0, 31', - ); - }); - }); - """) - PY - - - name: Install dependencies - run: bun install --frozen-lockfile - - - name: Lint and typecheck - run: bun run lint - - - name: Test - run: bun test - - - name: Build - run: bun run build - - - name: Commit verified changes - shell: bash - run: | - rm .github/workflows/apply-hermes-switch-hardening.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add src/utils/hermes-base.ts tests/hermes-switch-normalization.test.ts .github/workflows/apply-hermes-switch-hardening.yml - git commit -m "fix(hermes-base): harden switch jump-table normalization" - git push origin HEAD:automation/hermes-switch-release-20260823 diff --git a/src/utils/hermes-base.ts b/src/utils/hermes-base.ts index c782121..836807a 100644 --- a/src/utils/hermes-base.ts +++ b/src/utils/hermes-base.ts @@ -650,9 +650,9 @@ export function normalizeDisassemblyLine( // UIntSwitchImm rX, , , , // Folding only the first shape let a shifted UIntSwitchImm offset read as a // real difference and drop an otherwise good delta build. - m = /^(\s*(?:String|UInt)?SwitchImm r\d+, \d+, )\d+(, .*)$/.exec(line); + m = /^(\s*StringSwitchImm r\d+, \d+, )\d+(, L\d+, \d+)$/.exec(line); if (m) line = `${m[1]}${m[2]}`; - m = /^(\s*(?:String|UInt)?SwitchImm r\d+, )\d+(, L\d+, .*)$/.exec(line); + m = /^(\s*UIntSwitchImm r\d+, )\d+(, L\d+, \d+, \d+)$/.exec(line); if (m) line = `${m[1]}${m[2]}`; if (/^\s*offset \d+$/.test(line)) line = line.replace(/\d+$/, ''); return line; diff --git a/tests/hermes-switch-normalization.test.ts b/tests/hermes-switch-normalization.test.ts new file mode 100644 index 0000000..7d688bc --- /dev/null +++ b/tests/hermes-switch-normalization.test.ts @@ -0,0 +1,48 @@ +import { describe, expect, test } from 'bun:test'; +import { normalizeDisassemblyLine } from '../src/utils/hermes-base'; + +const normalize = (line: string) => + normalizeDisassemblyLine(line, new Map()); + +describe('Hermes switch jump-table normalization', () => { + test('normalizes only the StringSwitchImm jump-table offset', () => { + const baseline = normalize(' StringSwitchImm r13, 2, 4024, L146, 150'); + expect(baseline).toBe(' StringSwitchImm r13, 2, , L146, 150'); + expect(normalize(' StringSwitchImm r13, 2, 4025, L146, 150')).toBe( + baseline, + ); + expect(normalize(' StringSwitchImm r13, 3, 4024, L146, 150')).not.toBe( + baseline, + ); + expect(normalize(' StringSwitchImm r13, 2, 4024, L147, 150')).not.toBe( + baseline, + ); + expect(normalize(' StringSwitchImm r13, 2, 4024, L146, 151')).not.toBe( + baseline, + ); + }); + + test('normalizes only the UIntSwitchImm jump-table offset', () => { + const baseline = normalize(' UIntSwitchImm r40, 5937, L3, 0, 31'); + expect(baseline).toBe(' UIntSwitchImm r40, , L3, 0, 31'); + expect(normalize(' UIntSwitchImm r40, 5938, L3, 0, 31')).toBe(baseline); + expect(normalize(' UIntSwitchImm r40, 5937, L4, 0, 31')).not.toBe( + baseline, + ); + expect(normalize(' UIntSwitchImm r40, 5937, L3, 1, 31')).not.toBe( + baseline, + ); + expect(normalize(' UIntSwitchImm r40, 5937, L3, 0, 32')).not.toBe( + baseline, + ); + }); + + test('does not fold unsupported or malformed switch shapes', () => { + expect(normalize(' SwitchImm r1, 2, 3, L4, 5')).toBe( + ' SwitchImm r1, 2, 3, L4, 5', + ); + expect(normalize(' UIntSwitchImm r40, 5937, 3, 0, 31')).toBe( + ' UIntSwitchImm r40, 5937, 3, 0, 31', + ); + }); +});