From 8695ca8257ec9f433c519d0f34e6868bf6f104f6 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Fri, 23 Oct 2026 19:17:40 +0800 Subject: [PATCH] fix(gpu): bound embedded section reads Fixes #6657 --- docs/gpu-code-objects.md | 4 +- src/gpu/GpuInventory.ts | 5 +- tests/acceptance/gpuCodeObjectsCli.test.ts | 79 ++++++++++++++++++++++ 3 files changed, 86 insertions(+), 2 deletions(-) diff --git a/docs/gpu-code-objects.md b/docs/gpu-code-objects.md index 0c2e14fac1..74226cf5ab 100644 --- a/docs/gpu-code-objects.md +++ b/docs/gpu-code-objects.md @@ -128,7 +128,9 @@ A location with `container_id: null` uses original file offsets. Other locations use that inventoried object's decoded byte coordinates. Follow its parent, encoded location and digest to trace decompressed metadata back to a native section and the original file. Decoded offsets are never presented as physical -file offsets. +file offsets. Reads from an ELF or PE GPU section stay within that declared +section extent, so neighboring native section bytes cannot complete a +truncated embedded bundle header. The CLI uses the same service and contracts: diff --git a/src/gpu/GpuInventory.ts b/src/gpu/GpuInventory.ts index 85d3beaf6b..eb5c8d2de3 100644 --- a/src/gpu/GpuInventory.ts +++ b/src/gpu/GpuInventory.ts @@ -197,7 +197,10 @@ export class GpuInventory { const borrowed: GpuSource = { size, containerId: source.containerId, - read: (start, length) => source.read(offset + start, length), + read: (start, length) => { + checkGpuRange(size, start, length); + return source.read(offset + start, length); + }, }; // A section is a coordinate space with an explicit physical backing, including PE short names. const row = gpuObject(source, { diff --git a/tests/acceptance/gpuCodeObjectsCli.test.ts b/tests/acceptance/gpuCodeObjectsCli.test.ts index 4c15e242a2..a0e3f02a9b 100644 --- a/tests/acceptance/gpuCodeObjectsCli.test.ts +++ b/tests/acceptance/gpuCodeObjectsCli.test.ts @@ -20,6 +20,7 @@ import { amdFixture, cubinFixture, hipFixture, + elfFixture, } from "../fixtures/gpu/codeObjects.js"; cliTest( @@ -64,6 +65,84 @@ cliTest( }, ); +cliTest( + "keeps embedded Clang bundle reads inside their native section", + async ({ cli }) => { + const root = await createTestTempDirectory("rea-gpu-section-range-"); + const magic = Buffer.from("__CLANG_OFFLOAD_BUNDLE__"), + truncated = magic, + complete = Buffer.concat([magic, Buffer.alloc(8)]); + const cases = [ + ["truncated-zero-neighbor", truncated, Buffer.alloc(8)], + [ + "truncated-one-neighbor", + truncated, + Buffer.from([1, ...Array(7).fill(0)]), + ], + ["complete", complete, Buffer.alloc(8)], + ] as const; + const reports = []; + for (const [name, bundle, adjacent] of cases) { + const bytes = elfFixture(62, [ + { name: ".hip_fatbin", bytes: bundle }, + { name: ".data", bytes: adjacent }, + ]), + path = join(root, name); + await writeFile(path, bytes); + const response = await cli.run({ + arguments: [ + "inspect-gpu-code-objects", + path, + "--analysis", + "kernels", + "--detail", + "complete", + "--json", + ], + }); + expect(response.exitCode, response.stderr).toBe(0); + const evidence = parseEvidence( + toolContract("inspect_gpu_code_objects").outputSchema.parse( + response.json, + ), + ); + const report = gpuCodeObjectsSchema.parse(evidence.normalized_result); + expect(report.artifact.sha256).toBe( + createHash("sha256").update(bytes).digest("hex"), + ); + reports.push(report); + } + const [zeroNeighbor, oneNeighbor, completeReport] = reports; + expect(zeroNeighbor!.coverage).toMatchObject({ + discovery: "partial", + payloads: "partial", + }); + expect(oneNeighbor!.coverage).toMatchObject({ + discovery: "partial", + payloads: "partial", + }); + expect(zeroNeighbor!.objects[0]).toMatchObject({ + kind: "native-section", + inspection: "malformed", + location: { size: 24, section_name: ".hip_fatbin" }, + }); + expect(oneNeighbor!.objects[0]).toMatchObject({ + kind: "native-section", + inspection: "malformed", + location: { size: 24, section_name: ".hip_fatbin" }, + }); + expect(completeReport!.coverage).toMatchObject({ + discovery: "complete", + payloads: "complete", + }); + expect(completeReport!.objects[0]).toMatchObject({ + kind: "native-section", + inspection: "complete", + location: { size: 32, section_name: ".hip_fatbin" }, + }); + }, +); + cliTest( "compares equivalent PTX targets and retains family-specific CUDA targets", async ({ cli }) => {