fix(go): validate string extents before budget admission (#10231)

This commit is contained in:
Kaoru0822 authored and GitHub committed 2026-11-12 20:40:05 -08:00
1 parent 913b029c96
commit feca30c72c
4 files changed
+76 -12

No files matched your search

+15 -10
View File
@@ -360,6 +360,11 @@ const readInlineExtent = (
);
length |= BigInt(byte & 127) << BigInt(index * 7);
if ((byte & 128) === 0) {
if (length > BigInt(remaining - index - 1))
throw new BinaryFormatFailure(
"malformed",
"Go build-info string range is truncated",
);
if (length > BigInt(maximumBytes))
throw new BinaryResourceFailure(
"build-info",
@@ -367,11 +372,6 @@ const readInlineExtent = (
`Go build-info string length exceeds max_build_info_bytes=${String(maximumBytes)}`,
);
const size = Number(length);
if (size > remaining - index - 1)
throw new BinaryFormatFailure(
"malformed",
"Go build-info string range is truncated",
);
const start = offset + index + 1;
return {
location: { offset: start, bytes: size },
@@ -401,11 +401,10 @@ const readPointerString = (
const header = mappedFileOffset(mappings, address, width * 2, zeroFills);
const valueAddress = reader.word(header, pointerBits);
const length = reader.word(header + width, pointerBits);
if (length > BigInt(maximumBytes))
throw new BinaryResourceFailure(
"build-info",
maximumBytes,
`Go build-info pointer string length exceeds max_build_info_bytes=${String(maximumBytes)}`,
if (length > BigInt(reader.bytes.length))
throw new BinaryFormatFailure(
"malformed",
"Go build-info pointer string length exceeds the file extent",
);
const size = Number(length);
// Zero-length Go strings may carry a zero data pointer.
@@ -413,6 +412,12 @@ const readPointerString = (
size === 0 && valueAddress === 0n
? header
: mappedFileOffset(mappings, valueAddress, size, zeroFills);
if (length > BigInt(maximumBytes))
throw new BinaryResourceFailure(
"build-info",
maximumBytes,
`Go build-info pointer string length exceeds max_build_info_bytes=${String(maximumBytes)}`,
);
return {
bytes: reader.range(offset, size, "Go build-info pointer string"),
location: { offset, bytes: size },
+17
View File
@@ -88,6 +88,13 @@ cliTest(
const root = await createTestTempDirectory("rea-go-inline-budget-");
const { client, call } = await connectLocalToolsMcp();
const maximum = 1024 * 1024;
const impossibleInline = createGoBinaryFixture();
impossibleInline.bytes.set(
[0x81, 0x80, 0x40],
impossibleInline.headerOffset + 32,
);
const impossiblePointer = createGoBinaryFixture({ encoding: "pointer" });
impossiblePointer.bytes.writeBigUInt64LE(BigInt(maximum + 1), 1544);
const cases = [
{
name: "exact",
@@ -104,6 +111,16 @@ cliTest(
fixture: createGoInlineAggregateFixture(512),
outcome: "malformed",
},
{
name: "impossible-inline",
fixture: impossibleInline,
outcome: "malformed",
},
{
name: "impossible-pointer",
fixture: impossiblePointer,
outcome: "malformed",
},
] as const;
for (const item of cases) {
+42
View File
@@ -285,6 +285,48 @@ it("refuses mapped pointer lengths outside the file instead of rounding 64-bit a
expect(() => readGoBinaryImage(length.bytes)).toThrowError(/limit|length/i);
});
it.each(["inline", "pointer"] as const)(
"classifies impossible %s string extents before the decoding allowance",
(encoding) => {
for (const length of [1048577n, 0xffffffffffffffffn]) {
const fixture = createGoBinaryFixture({ encoding });
if (encoding === "pointer") fixture.bytes.writeBigUInt64LE(length, 1544);
else {
let value = length;
let at = fixture.headerOffset + 32;
do {
const byte = Number(value & 127n);
value >>= 7n;
fixture.bytes[at++] = byte | (value === 0n ? 0 : 128);
} while (value !== 0n);
}
expect(() => readGoBinaryImage(fixture.bytes)).toThrowError(
expect.objectContaining({ kind: "malformed" }),
);
}
},
);
it("validates pointer mapping before a smaller caller allowance", () => {
const fixture = createGoBinaryFixture({ encoding: "pointer" });
fixture.bytes.writeBigUInt64LE(0x10fffn, 1536);
expect(() =>
readGoBinaryImage(fixture.bytes, { budgets: { max_build_info_bytes: 1 } }),
).toThrowError(expect.objectContaining({ kind: "malformed" }));
});
it.each(["inline", "pointer"] as const)(
"preserves resource refusal for physically valid %s strings",
(encoding) => {
const fixture = createGoBinaryFixture({ encoding });
expect(() =>
readGoBinaryImage(fixture.bytes, {
budgets: { max_build_info_bytes: 1 },
}),
).toThrowError(BinaryResourceFailure);
},
);
it.each(["file bytes", "zero-fill"])(
"refuses a header-backed PE module descriptor contradicted by %s",
(alias) => {
+2 -2
View File
@@ -12,6 +12,7 @@ import { projectAnalysisError } from "../../../src/domain/analysisErrorProjectio
import {
createGoBinaryDiagnosticFixtures,
createGoBinaryFixture,
createGoInlineAggregateFixture,
createGoOverlappingElfFixture,
} from "../../fixtures/go/image.js";
import { createGoRodataStringFixture } from "../../fixtures/go/rodataStringImage.js";
@@ -55,8 +56,7 @@ it.each([32, 64] as const)(
);
it("preserves the actual build-info byte guard as a resource failure", async () => {
const fixture = createGoBinaryFixture();
fixture.bytes.set([0x81, 0x80, 0x40], fixture.headerOffset + 32);
const fixture = createGoInlineAggregateFixture(1024 * 1024 + 2);
const root = await createTestTempDirectory("rea-go-resource-");
const path = join(root, "oversized-metadata.elf");
await writeFile(path, fixture.bytes);