mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 08:47:00 +00:00
fix(artifacts): recover malformed PYZ sizes without losing siblings
Fixes #6120
This commit is contained in:
1 parent
56685c6ec6
commit
f8f4a5c3b8
3 files changed
+124
-2
No files matched your search
@@ -60,3 +60,26 @@ it("rejects a marshal type outside the subset", () => {
|
||||
if (parsed.ok) return;
|
||||
expect(parsed.error.reason).toBe("format");
|
||||
});
|
||||
|
||||
it.each([
|
||||
["string", "73ffffffff"],
|
||||
["list", "5bffffffff"],
|
||||
["tuple", "28ffffffff"],
|
||||
])("classifies a negative marshal %s size as malformed", (_kind, hex) => {
|
||||
const parsed = readMarshalSubset(bytes(hex));
|
||||
expect(parsed).toMatchObject({
|
||||
ok: false,
|
||||
error: { reason: "format" },
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["string", "7301000100"],
|
||||
["list", "5ba1860100"],
|
||||
])("keeps positive marshal %s budget refusals as limits", (_kind, hex) => {
|
||||
const parsed = readMarshalSubset(bytes(hex));
|
||||
expect(parsed).toMatchObject({
|
||||
ok: false,
|
||||
error: { reason: "limit" },
|
||||
});
|
||||
});
|
||||
@@ -103,7 +103,11 @@ const readSequence = (
|
||||
count: Result<number, PyInstallerFailure>,
|
||||
): Result<MarshalValue, PyInstallerFailure> => {
|
||||
if (!count.ok) return count;
|
||||
if (count.value < 0 || count.value > MAX_SEQUENCE)
|
||||
if (count.value < 0)
|
||||
return err(
|
||||
pyInstallerFailure("format", "PYZ marshal sequence has a negative count"),
|
||||
);
|
||||
if (count.value > MAX_SEQUENCE)
|
||||
return err(
|
||||
pyInstallerFailure(
|
||||
"limit",
|
||||
@@ -236,7 +240,11 @@ const readBytes = (
|
||||
cursor: MarshalCursor,
|
||||
length: number,
|
||||
): Result<Buffer, PyInstallerFailure> => {
|
||||
if (!Number.isSafeInteger(length) || length < 0 || length > MAX_STRING_BYTES)
|
||||
if (length < 0)
|
||||
return err(
|
||||
pyInstallerFailure("format", "PYZ marshal string has a negative length"),
|
||||
);
|
||||
if (!Number.isSafeInteger(length) || length > MAX_STRING_BYTES)
|
||||
return err(
|
||||
pyInstallerFailure(
|
||||
"limit",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { execFile } from "node:child_process";
|
||||
import { readFile, readdir, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
@@ -23,6 +24,7 @@ const onefile = async (
|
||||
magic = PYTHON_39_MAGIC,
|
||||
marker = false,
|
||||
compressPyz = false,
|
||||
pyzToc?: Buffer,
|
||||
): Promise<string> => {
|
||||
const directory = await createTestTempDirectory("rea-pyinstaller-cli-");
|
||||
const path = join(directory, "hello");
|
||||
@@ -62,6 +64,12 @@ const onefile = async (
|
||||
payload: Buffer.from("print('hi')"),
|
||||
compress: true,
|
||||
},
|
||||
{
|
||||
name: "healthy.txt",
|
||||
typeCode: "x",
|
||||
payload: Buffer.from("healthy sibling"),
|
||||
compress: false,
|
||||
},
|
||||
{
|
||||
name: "PYZ.pyz",
|
||||
typeCode: "z",
|
||||
@@ -70,6 +78,7 @@ const onefile = async (
|
||||
modules: [
|
||||
{ name: "app", typeCode: 0, body: Buffer.from("app-body") },
|
||||
],
|
||||
...(pyzToc === undefined ? {} : { toc: pyzToc }),
|
||||
}),
|
||||
compress: compressPyz,
|
||||
},
|
||||
@@ -201,3 +210,85 @@ it("reports unknown traversal through the CLI for an opaque PYZ", async () => {
|
||||
compression_flag: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
["negative string length", "5b0100000073ffffffff"],
|
||||
["negative list count", "5bffffffff"],
|
||||
["negative tuple count", "28ffffffff"],
|
||||
["truncated string", "5b010000007301000000"],
|
||||
["unsupported marshal type", "5b010000004e"],
|
||||
])(
|
||||
"keeps healthy CArchive siblings when the PYZ table has %s",
|
||||
async (_description, tocHex) => {
|
||||
const path = await onefile(
|
||||
PYTHON_39_MAGIC,
|
||||
false,
|
||||
false,
|
||||
Buffer.from(tocHex, "hex"),
|
||||
);
|
||||
const result = await execute(process.execPath, [
|
||||
cli,
|
||||
"inspect-artifact",
|
||||
path,
|
||||
"--json",
|
||||
]);
|
||||
const inspection = artifactInspectionResultSchema.parse(
|
||||
parseEvidence(JSON.parse(result.stdout)).normalized_result,
|
||||
);
|
||||
const inventory = inspection.substeps[0]?.evidence.normalized_result;
|
||||
expect(inventory?.coverage.status).toBe("unknown");
|
||||
expect(inventory?.occurrences.map((item) => item.logical_path)).toEqual(
|
||||
expect.arrayContaining(["hello", "healthy.txt", "PYZ.pyz"]),
|
||||
);
|
||||
const healthySibling = inventory?.occurrences.find(
|
||||
(item) => item.logical_path === "healthy.txt",
|
||||
);
|
||||
expect(healthySibling).toMatchObject({
|
||||
hash_status: "verified",
|
||||
artifact_id: expect.any(String),
|
||||
});
|
||||
expect(
|
||||
inventory?.nodes.find(
|
||||
(node) => node.artifact_id === healthySibling?.artifact_id,
|
||||
),
|
||||
).toMatchObject({
|
||||
sha256: createHash("sha256")
|
||||
.update(Buffer.from("healthy sibling"))
|
||||
.digest("hex"),
|
||||
size: Buffer.byteLength("healthy sibling"),
|
||||
});
|
||||
expect(
|
||||
inventory?.occurrences.find((item) => item.logical_path === "PYZ.pyz"),
|
||||
).toMatchObject({
|
||||
format_metadata: { pyz_status: "opaque" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps positive over-budget PYZ sizes as resource refusals", async () => {
|
||||
const path = await onefile(
|
||||
PYTHON_39_MAGIC,
|
||||
false,
|
||||
false,
|
||||
Buffer.from("7301000100", "hex"),
|
||||
);
|
||||
let failure: { code: unknown; stdout: string } | undefined;
|
||||
try {
|
||||
await execute(process.execPath, [cli, "inspect-artifact", path, "--json"]);
|
||||
} catch (error: unknown) {
|
||||
if (
|
||||
typeof error === "object" &&
|
||||
error !== null &&
|
||||
"code" in error &&
|
||||
"stdout" in error &&
|
||||
typeof error.stdout === "string"
|
||||
)
|
||||
failure = { code: error.code, stdout: error.stdout };
|
||||
else throw error;
|
||||
}
|
||||
expect(failure?.code).toBe(1);
|
||||
expect(JSON.parse(failure?.stdout ?? "null")).toMatchObject({
|
||||
code: "truncated",
|
||||
details: { reason: "limit" },
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user