fix(domain): reject malformed base64 before byte-range checks

This commit is contained in:
morluto committed 2026-10-22 17:16:25 +08:00
1 parent 21f9af9d93
commit 31dc035041
7 files changed
+76 -90

No files matched your search

@@ -178,59 +178,65 @@ it("cancels before invocation and after an acquired observation", async () => {
).toMatchObject({ ok: false, error: { _tag: "AnalysisCancelledError" } });
});
it.each(["different-path", "invalid-digest", "outside-range"])(
"rejects provider evidence at the next boundary: %s",
async (problem) => {
const fixture = recordedCrashFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
if (problem === "invalid-digest") fixture.artifact.sha256 = "unobserved";
if (problem === "outside-range")
fixture.segments.push({
index: 0,
type: "PT_LOAD",
offset: "0x200",
header_location: { offset: "0x0", bytes: "0x38" },
file_backing: "file",
file_size: "0x1",
memory_size: "0x1",
virtual_address: "0x20000000000001",
physical_address: "0x0",
alignment: "0x1",
flags: "0x5",
});
const service = new RecordedCrashService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: RECORDED_CRASH_TEST_PROVIDER,
inspect: () => Promise.resolve(ok(fixture)),
it.each([
"different-path",
"invalid-digest",
"outside-range",
"owner-base64",
"descriptor-base64",
])("rejects provider evidence at the next boundary: %s", async (problem) => {
const fixture = recordedCrashFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
if (problem === "invalid-digest") fixture.artifact.sha256 = "unobserved";
if (problem === "owner-base64") fixture.notes[0]!.owner_bytes_base64 = "A";
if (problem === "descriptor-base64")
fixture.notes[0]!.descriptor_bytes_base64 = "A";
if (problem === "outside-range")
fixture.segments.push({
index: 0,
type: "PT_LOAD",
offset: "0x200",
header_location: { offset: "0x0", bytes: "0x38" },
file_backing: "file",
file_size: "0x1",
memory_size: "0x1",
virtual_address: "0x20000000000001",
physical_address: "0x0",
alignment: "0x1",
flags: "0x5",
});
expect(
await service.inspect({ path: "/artifacts/source-owned.core" }),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
capturedOutput: fixture.diagnostics,
},
});
const controller = new AbortController();
const lateInvalid = await new RecordedCrashService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: RECORDED_CRASH_TEST_PROVIDER,
inspect: () => {
controller.abort();
return Promise.resolve(ok(fixture));
},
}).inspect(
{ path: "/artifacts/source-owned.core" },
{ signal: controller.signal },
);
expect(lateInvalid).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
},
);
const service = new RecordedCrashService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: RECORDED_CRASH_TEST_PROVIDER,
inspect: () => Promise.resolve(ok(fixture)),
});
expect(
await service.inspect({ path: "/artifacts/source-owned.core" }),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
capturedOutput: fixture.diagnostics,
},
});
const controller = new AbortController();
const lateInvalid = await new RecordedCrashService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: RECORDED_CRASH_TEST_PROVIDER,
inspect: () => {
controller.abort();
return Promise.resolve(ok(fixture));
},
}).inspect(
{ path: "/artifacts/source-owned.core" },
{ signal: controller.signal },
);
expect(lateInvalid).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
});
+1 -6
View File
@@ -158,12 +158,7 @@ export const goBinarySchema = z
["module_bytes_base64", info.module_location, "module"],
] as const) {
const encoded = info[field];
const padding = encoded.endsWith("==")
? 2
: encoded.endsWith("=")
? 1
: 0;
if ((encoded.length / 4) * 3 - padding !== range.bytes)
if (Buffer.byteLength(encoded, "base64") !== range.bytes)
context.addIssue({
code: "custom",
path: ["build_info", field],
+5 -11
View File
@@ -426,17 +426,11 @@ const validatePackedRelativeTables = (
tableIndex,
"location",
]);
const encodedBytes =
(table.encoded_bytes_base64.length * 3) / 4 -
(table.encoded_bytes_base64.endsWith("==")
? 2
: table.encoded_bytes_base64.endsWith("=")
? 1
: 0);
if (
!Number.isInteger(encodedBytes) ||
BigInt(encodedBytes) !== BigInt(table.location.bytes)
)
const encodedBytes = Buffer.byteLength(
table.encoded_bytes_base64,
"base64",
);
if (BigInt(encodedBytes) !== BigInt(table.location.bytes))
context.addIssue({
code: "custom",
path: ["packed_relative_relocations", tableIndex],
+2 -7
View File
@@ -317,15 +317,10 @@ export const recordedCrashSchema = recordedCrashObjectSchema.superRefine(
fail("Invalid original note identity or segment reference.");
continue;
}
const decodedLength = (raw: string) =>
BigInt(
(raw.length / 4) * 3 -
(raw.endsWith("==") ? 2 : raw.endsWith("=") ? 1 : 0),
);
if (
decodedLength(note.owner_bytes_base64) !==
BigInt(Buffer.byteLength(note.owner_bytes_base64, "base64")) !==
BigInt(note.owner_location.bytes) ||
decodedLength(note.descriptor_bytes_base64) !==
BigInt(Buffer.byteLength(note.descriptor_bytes_base64, "base64")) !==
BigInt(note.descriptor_location.bytes)
)
fail("Raw note lengths differ from their source ranges.");
@@ -32,13 +32,7 @@ const paddingMatches = (
segment: Segment | undefined,
): boolean => {
const bytes = BigInt(item.location.bytes);
const decodedLength =
(item.bytes_base64.length / 4) * 3 -
(item.bytes_base64.endsWith("==")
? 2
: item.bytes_base64.endsWith("=")
? 1
: 0);
const decodedLength = Buffer.byteLength(item.bytes_base64, "base64");
return (
bytes > 0n &&
bytes < 12n &&
+1 -2
View File
@@ -3,8 +3,7 @@ import { isPaddedBase64 } from "./stringPatterns.js";
/** Validate padded syntax without imposing canonical zero pad bits. */
export const paddedBase64ByteLength = (value: string): number | undefined => {
if (!isPaddedBase64(value)) return undefined;
const padding = value.endsWith("==") ? 2 : value.endsWith("=") ? 1 : 0;
return (value.length / 4) * 3 - padding;
return Buffer.byteLength(value, "base64");
};
export const decodePaddedBase64 = (value: string): Buffer | undefined =>
+5 -2
View File
@@ -32,10 +32,13 @@ const paddedBase64SchemaRules = [
{ not: { pattern: INVALID_BASE64_PADDING_PATTERN.source } },
];
/** Canonical padded base64 with zero pad bits, including the empty encoding. */
/** Canonical padded base64 with zero pad bits; invalid bytes never enter enclosing range checks. */
export const canonicalBase64Schema = z
.string()
.refine(isCanonicalBase64, "Expected canonical padded base64")
.refine(isCanonicalBase64, {
message: "Expected canonical padded base64",
abort: true,
})
.meta({
allOf: [
...paddedBase64SchemaRules,