From f40bd0c4fb20450828fb6ac952af99ead660c7ff Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 23 Oct 2026 21:13:51 +0800 Subject: [PATCH] fix(il2cpp): deduplicate aliased registration fields Fixes #6715 --- src/il2cpp/Il2cppBinaryRegistration.ts | 7 ++++++- tests/boundary/il2cpp/il2cppBinaryFile.test.ts | 10 ++++++++++ tests/fixtures/il2cpp/binary.ts | 6 ++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/il2cpp/Il2cppBinaryRegistration.ts b/src/il2cpp/Il2cppBinaryRegistration.ts index 258d031a2d..1ae50113c3 100644 --- a/src/il2cpp/Il2cppBinaryRegistration.ts +++ b/src/il2cpp/Il2cppBinaryRegistration.ts @@ -209,6 +209,7 @@ export function* findCodeRegistration( const expected = new Set(modules.map((module) => module.address)); const total = BigInt(modules.length); const matches: { field: bigint; array: bigint }[] = []; + const matchedFields = new Set(); for (const mapping of image.container.mappings) { const start = Number((8n - (mapping.address % 8n)) % 8n); for (let at = start; at + 16 <= mapping.size; at += 8) { @@ -225,12 +226,16 @@ export function* findCodeRegistration( seen.add(pointer); } if (seen.size === modules.length) { + const field = mapping.address + BigInt(at); + // Exact load aliases can visit one virtual registration field twice. + if (matchedFields.has(field)) continue; + matchedFields.add(field); if (matches.length !== 0) throw new Il2cppBinaryFormatFailure( "code-registration-not-found", "multiple CodeRegistration module tables match", ); - matches.push({ field: mapping.address + BigInt(at), array }); + matches.push({ field, array }); } } } diff --git a/tests/boundary/il2cpp/il2cppBinaryFile.test.ts b/tests/boundary/il2cpp/il2cppBinaryFile.test.ts index 4830402d80..f0488b2cf4 100644 --- a/tests/boundary/il2cpp/il2cppBinaryFile.test.ts +++ b/tests/boundary/il2cpp/il2cppBinaryFile.test.ts @@ -114,6 +114,16 @@ it.each([ }, ); +it("still rejects distinct CodeRegistration module table fields", () => { + expect( + failure( + createElfFixture( + createIl2cppBinaryFixture({ duplicateCodeRegistration: true }), + ), + ), + ).toEqual({ reason: "code-registration-not-found", kind: "malformed" }); +}); + it("rejects reused MethodDef tokens within an image but preserves cross-image reuse and folded addresses", () => { const duplicateTokens = Uint32Array.from(facts.methodTokens); duplicateTokens[1] = duplicateTokens[0]!; diff --git a/tests/fixtures/il2cpp/binary.ts b/tests/fixtures/il2cpp/binary.ts index 24c07eefcd..2eafd51117 100644 --- a/tests/fixtures/il2cpp/binary.ts +++ b/tests/fixtures/il2cpp/binary.ts @@ -25,6 +25,8 @@ export interface Il2cppBinaryFixtureOptions { readonly chainedFixups?: boolean; /** Omit the CodeRegistration module count and array pointer. */ readonly codeRegistration?: boolean; + /** Write a second distinct CodeRegistration module count and array pair. */ + readonly duplicateCodeRegistration?: boolean; /** Write a RIP-relative `lea rax, [module array]`, as an inlined build loads it. */ readonly codeReference?: boolean; /** Omit the MetadataRegistration type table. */ @@ -125,6 +127,10 @@ export const createIl2cppBinaryFixture = ( if (options.codeRegistration !== false) { bytes.writeBigUInt64LE(2n, REGISTRATION); bytes.writeBigUInt64LE(address(MODULE_ARRAY), REGISTRATION + 8); + if (options.duplicateCodeRegistration === true) { + bytes.writeBigUInt64LE(2n, REGISTRATION + 0x80); + bytes.writeBigUInt64LE(address(MODULE_ARRAY), REGISTRATION + 0x88); + } } // Il2CppType records: 8-byte data, then attrs:16 and type:8 bits. const types: readonly [kind: number, data: bigint][] = [