fix(artifacts): reject contradictory checksum representations

This commit is contained in:
morluto committed 2026-10-08 19:13:39 +08:00
1 parent 3664a4a57e
commit d9a39e5b48
3 files changed
+78

No files matched your search

+1
View File
@@ -335,6 +335,7 @@ export class CpioArtifactReader implements ArtifactReader {
"format",
"cpio trailer declares member data",
);
await drain(this.#verified(source, header, raw));
await source.finish();
yield* this.#unresolvedLinks();
return;
@@ -3,6 +3,7 @@ import { tmpdir } from "node:os";
import { join } from "node:path";
import { buffer } from "node:stream/consumers";
import { Readable } from "node:stream";
import { gunzipSync, gzipSync } from "node:zlib";
import { expect, it } from "vitest";
import { ArtifactDecodedBudget } from "./ArtifactDecodedBudget.js";
import { CpioArtifactReader } from "./CpioArtifactReader.js";
@@ -233,3 +234,64 @@ it("does not expose a partial inventory when a previous TOC load failed", async
},
);
});
it("rejects a TOC checksum when the fixed header declares none", async () => {
const bytes = Buffer.from(
xarArchive([{ name: "a", data: Buffer.from("ok") }]),
);
bytes.writeUInt32BE(0, 24);
await withReader(bytes, async (reader) => {
await expect(
(async () => {
for await (const entry of reader.entries()) void entry;
})(),
).rejects.toMatchObject({
reason: "format",
message: expect.stringContaining("declares no TOC checksum"),
});
});
});
it.each(["", "nothex", "a".repeat(63)])(
"rejects malformed checksum text %j before applying integrity policy",
async (digest) => {
const bytes = rewriteXarToc(
xarArchive([{ name: "a", data: Buffer.from("ok") }]),
(xml) =>
xml.replace(
/<extracted-checksum style="sha1">[^<]*<\/extracted-checksum>/u,
`<extracted-checksum style="sha256">${digest}</extracted-checksum>`,
),
);
await withReader(bytes, async (reader) => {
await expect(
(async () => {
for await (const entry of reader.entries()) void entry;
})(),
).rejects.toMatchObject({
reason: "format",
message: "xar extracted-checksum has malformed sha256 digest text",
});
});
},
);
it("rejects a CRC trailer whose declared checksum is not empty-data zero", async () => {
const raw = gunzipSync(gzipCpio([], "crc"));
raw.write("00000001", 102, "ascii");
const reader = new CpioArtifactReader(async () =>
Readable.from([gzipSync(raw)]),
);
try {
await expect(
(async () => {
for await (const entry of reader.entries()) void entry;
})(),
).rejects.toMatchObject({
reason: "integrity",
message: "cpio CRC disagrees with content: TRAILER!!!",
});
} finally {
await reader.close();
}
});
+15
View File
@@ -101,6 +101,16 @@ const parseChecksum = (
const value = (element.textContent ?? "").trim().toLowerCase();
if (CHECKSUM_ALGORITHMS.get(style) === undefined)
return { unsupported: style || "(missing style)", value };
const expected = DIGEST_BYTES.get(style);
if (
expected === undefined ||
value.length !== expected * 2 ||
!/^[0-9a-f]+$/u.test(value)
)
throw new ArtifactReaderFailure(
"format",
`xar ${tag} has malformed ${style} digest text`,
);
return { algorithm: style, value };
};
@@ -529,6 +539,11 @@ export class XarArtifactReader implements ArtifactReader {
`xar header declares unsupported TOC checksum algorithm ${headerAlg}`,
);
const checksum = childElement(toc, "checksum");
if (expectedStyle === null && checksum !== undefined)
throw new ArtifactReaderFailure(
"format",
"xar header declares no TOC checksum but the TOC supplies one",
);
if (checksum === undefined) {
if (headerAlg !== 0)
throw new ArtifactReaderFailure(