From da79ea0baa1abdfcbf0547c56727f3b141e8465f Mon Sep 17 00:00:00 2001 From: Jiabin Yin <54463072+Nolkee@users.noreply.github.com> Date: Thu, 5 Nov 2026 18:33:36 +0800 Subject: [PATCH] fix(il2cpp): reject module image references outside retained inventory (#8956) --- .../analysisView/il2cppMetadataView.test.ts | 28 ++++++++ src/domain/il2cpp/il2cppMetadata.ts | 8 +++ tests/acceptance/il2cppMetadataCli.test.ts | 67 +++++++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/src/domain/analysisView/il2cppMetadataView.test.ts b/src/domain/analysisView/il2cppMetadataView.test.ts index 23e27840fc..8b5a0c59d7 100644 --- a/src/domain/analysisView/il2cppMetadataView.test.ts +++ b/src/domain/analysisView/il2cppMetadataView.test.ts @@ -482,6 +482,34 @@ const linkedReport = (): Il2cppMetadata => ({ parameter_types: [{ type: 0, name: "int" }], }); +it.each([-1, 1, 99])( + "rejects imported code-generation module image reference %i", + (image) => { + const value = linkedReport(); + value.binary!.code_gen_modules[0]!.image = image; + const parsed = il2cppMetadataParentSchema.safeParse(value); + expect(parsed.success).toBe(false); + if (!parsed.success) + expect(parsed.error.issues).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + path: ["binary", "code_gen_modules", 0, "image"], + }), + ]), + ); + for (const selection of [{}, { image_indices: [image] }]) { + const view = projectIl2cppMetadataView(parent(value, false), { + kind: "il2cpp-metadata", + collection: "code_gen_modules", + ...selection, + offset: 0, + limit: 64, + }); + expect(view.ok).toBe(false); + } + }, +); + it("selects every folded method by numeric address with stable indices and both source identities", () => { const source = parent(freezeJsonSnapshot(linkedReport()), true); const view = projectIl2cppMetadataView(source, { diff --git a/src/domain/il2cpp/il2cppMetadata.ts b/src/domain/il2cpp/il2cppMetadata.ts index 2599df5f9a..e8e88df6a0 100644 --- a/src/domain/il2cpp/il2cppMetadata.ts +++ b/src/domain/il2cpp/il2cppMetadata.ts @@ -444,6 +444,14 @@ const validateMetadataFacts = ( "Type reference is outside the linked type table or names an unavailable type.", }); }); + report.binary?.code_gen_modules.forEach((row, index) => { + if (row.image >= report.images.length) + context.addIssue({ + code: "custom", + path: ["binary", "code_gen_modules", index, "image"], + message: "Code-generation module image is outside the retained images.", + }); + }); report.method_addresses?.forEach((row, index) => { if (codeRowContradicts(row, report.binary?.image_base)) context.addIssue({ diff --git a/tests/acceptance/il2cppMetadataCli.test.ts b/tests/acceptance/il2cppMetadataCli.test.ts index 0327902d1f..89c0530704 100644 --- a/tests/acceptance/il2cppMetadataCli.test.ts +++ b/tests/acceptance/il2cppMetadataCli.test.ts @@ -56,6 +56,73 @@ const parseMetadataSummary = (value: unknown) => parseEvidence(value).normalized_result, ); +cliTest( + "rejects out-of-range module image references through CLI and MCP", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-il2cpp-module-image-"); + const path = join(root, "global-metadata.dat"); + const binaryPath = join(root, "GameAssembly.dylib"); + await writeFile( + path, + createLinkedIl2cppMetadataFixture({ version: 39 }).bytes, + ); + await writeFile(binaryPath, createIl2cppBinaryFixture()); + const produced = await runFileCli(cli, { + arguments: [ + "inspect-il2cpp-metadata", + path, + "--binary", + binaryPath, + "--detail", + "complete", + "--json", + ], + }); + expect(produced.exitCode, produced.stderr).toBe(0); + const original = parseCliMetadata(produced.json); + expect(original.binary!.code_gen_modules.length).toBeGreaterThan(0); + const { call } = await connectLocalToolsMcp(); + for (const image of [ + 0, + original.images.length - 1, + -1, + original.images.length, + 99, + ]) { + const report = structuredClone(original); + report.binary!.code_gen_modules[0]!.image = image; + const evidence = createEvidence( + { path, format: "file", sha256: report.artifact.sha256 }, + { id: "il2cpp-metadata", name: "IL2CPP", version: "1" }, + { + operation: "inspect_il2cpp_metadata", + parameters: { path }, + result: report, + }, + ); + const request = { + source: { kind: "inline", evidence }, + view: { kind: "il2cpp-metadata", collection: "code_gen_modules" }, + }; + const query = join(root, `image-${image}.json`); + await writeFile(query, JSON.stringify(request)); + const result = await runFileCli(cli, { + arguments: ["inspect-analysis-view", query, "--json"], + }); + const mcp = await call("inspect_analysis_view", request); + if (image >= 0 && image < original.images.length) { + expect(result.exitCode, result.stderr).toBe(0); + expect(mcp.isError, JSON.stringify(mcp)).not.toBe(true); + } else { + expect(result.exitCode, `image ${image}`).toBe(1); + expect(result.json).toMatchObject({ code: "invalid_request" }); + expect(parseMcpToolError(mcp).error.code).toBe("invalid_request"); + } + } + }, + 30000, +); + cliTest( "rejects impossible imported metadata owners through CLI and MCP", async ({ cli }) => {