From 8babb8e3fb96e7e15933590164680768029e2806 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Mon, 9 Nov 2026 19:14:41 +0800 Subject: [PATCH] fix(gpu): validate bundle count against directory extent --- src/gpu/ClangOffloadBundle.ts | 10 +++++++-- tests/boundary/gpu/codeObjects.test.ts | 28 ++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/gpu/ClangOffloadBundle.ts b/src/gpu/ClangOffloadBundle.ts index 87f4ada2f9b..05777d7c14a 100644 --- a/src/gpu/ClangOffloadBundle.ts +++ b/src/gpu/ClangOffloadBundle.ts @@ -28,10 +28,16 @@ export const readClangBundleDirectory = async ( declarations: { object: GpuObject; amdTarget: AmdGpuTargetIdentity | null }[]; end: number; }> => { - const header = await source.read(base, 32), - count = gpuCount(header.readBigUInt64LE(24)); + const header = await source.read(base, 32); if (!header.subarray(0, 24).equals(CLANG_BUNDLE_MAGIC)) rejectGpu("Invalid Clang offload bundle magic"); + const declaredCount = header.readBigUInt64LE(24); + // Every entry needs three 64-bit words before its variable-length ID. + // Compare before numeric conversion so even a uint64 maximum stays a + // malformed extent rather than a capacity or host-precision refusal. + if (declaredCount > BigInt(Math.floor((source.size - base - 32) / 24))) + rejectGpu("Clang offload bundle directory is truncated"); + const count = gpuCount(declaredCount); if (count > 65536) throw new BinaryResourceFailure( "structure", diff --git a/tests/boundary/gpu/codeObjects.test.ts b/tests/boundary/gpu/codeObjects.test.ts index a3d326ca3e3..a57858211b6 100644 --- a/tests/boundary/gpu/codeObjects.test.ts +++ b/tests/boundary/gpu/codeObjects.test.ts @@ -1224,3 +1224,31 @@ it("refuses escaped PTX report expansion while preserving bounded interrupted fa expect(partial.objects).toHaveLength(1); expect(partial.artifact.sha256).toBeNull(); }); + +it("distinguishes impossible Clang bundle counts from a framed over-capacity directory", async () => { + const empty = Buffer.alloc(32); + Buffer.from("__CLANG_OFFLOAD_BUNDLE__").copy(empty); + const emptyResult = await inspect(empty, { analysis: "inventory" }); + if (!emptyResult.ok) throw emptyResult.error; + expect(emptyResult.value.objects).toEqual([]); + const truncated = hipFixture().subarray(0, 32); + for (const count of [65537n, 0xffffffffffffffffn]) { + const bytes = Buffer.from(truncated); + bytes.writeBigUInt64LE(count, 24); + const result = await inspect(bytes, { analysis: "inventory" }); + if (result.ok) throw new Error("Expected malformed directory"); + expect(result.error._tag).toBe("BinaryTargetError"); + } + const framed = Buffer.alloc(32 + 65537 * 25); + Buffer.from("__CLANG_OFFLOAD_BUNDLE__").copy(framed); + framed.writeBigUInt64LE(65537n, 24); + for (let index = 0; index < 65537; index++) { + const row = 32 + index * 25; + framed.writeBigUInt64LE(BigInt(framed.length), row); + framed.writeBigUInt64LE(1n, row + 16); + framed[row + 24] = 0x78; + } + const result = await inspect(framed, { analysis: "inventory" }); + if (result.ok) throw new Error("Expected directory capacity failure"); + expect(result.error._tag).toBe("AnalysisResourceConstraintError"); +});