mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 01:41:21 +00:00
fix(pyinstaller): account for one-byte name expansion (#10058)
This commit is contained in:
1 parent
9cd1eadfb7
commit
4de8020745
2 files changed
+82
-41
No files matched your search
@@ -94,44 +94,83 @@ it.each(["PYZ\0", "BAD\0"])(
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects a PYZ table whose back-references repeat one long row", async () => {
|
||||
// One namespace row with a 16 KiB name, flagged for reference, then 999
|
||||
// back-references to it. The table is about 21 KiB but would expand to
|
||||
// 16 MiB of entry names.
|
||||
const name = Buffer.alloc(16 * 1024, 0x61);
|
||||
const refs = 1_000;
|
||||
const row = Buffer.concat([
|
||||
Buffer.from([0x28 | 0x80]),
|
||||
int32(2),
|
||||
Buffer.from([0x61]),
|
||||
int32(name.length),
|
||||
name,
|
||||
Buffer.from([0x29, 3, 0x69]),
|
||||
int32(3),
|
||||
Buffer.from([0x69]),
|
||||
int32(0),
|
||||
Buffer.from([0x69]),
|
||||
int32(0),
|
||||
]);
|
||||
const reference = Buffer.concat([Buffer.from([0x72]), int32(0)]);
|
||||
const table = Buffer.concat([
|
||||
Buffer.from([0x5b]),
|
||||
int32(refs),
|
||||
row,
|
||||
...Array.from({ length: refs - 1 }, () => reference),
|
||||
]);
|
||||
const header = Buffer.alloc(12);
|
||||
header.write("PYZ\0");
|
||||
PYTHON_39_MAGIC.copy(header, 4);
|
||||
header.writeUInt32BE(12, 8);
|
||||
const image = Buffer.concat([header, table]);
|
||||
expect(image.length).toBeLessThan(32 * 1024);
|
||||
it.each([0x61, 0xe9])(
|
||||
"rejects a PYZ table whose back-references repeat one long row (byte %i)",
|
||||
async (byte) => {
|
||||
// One namespace row with a 16 KiB name, flagged for reference, then 999
|
||||
// back-references to it. The table is about 21 KiB but would expand to
|
||||
// 16 or 32 MiB of UTF-8 entry names.
|
||||
const name = Buffer.alloc(16 * 1024, byte);
|
||||
const refs = 1_000;
|
||||
const row = Buffer.concat([
|
||||
Buffer.from([0x28 | 0x80]),
|
||||
int32(2),
|
||||
Buffer.from([0x61]),
|
||||
int32(name.length),
|
||||
name,
|
||||
Buffer.from([0x29, 3, 0x69]),
|
||||
int32(3),
|
||||
Buffer.from([0x69]),
|
||||
int32(0),
|
||||
Buffer.from([0x69]),
|
||||
int32(0),
|
||||
]);
|
||||
const reference = Buffer.concat([Buffer.from([0x72]), int32(0)]);
|
||||
const table = Buffer.concat([
|
||||
Buffer.from([0x5b]),
|
||||
int32(refs),
|
||||
row,
|
||||
...Array.from({ length: refs - 1 }, () => reference),
|
||||
]);
|
||||
const header = Buffer.alloc(12);
|
||||
header.write("PYZ\0");
|
||||
PYTHON_39_MAGIC.copy(header, 4);
|
||||
header.writeUInt32BE(12, 8);
|
||||
const image = Buffer.concat([header, table]);
|
||||
expect(image.length).toBeLessThan(32 * 1024);
|
||||
|
||||
const parsed = await readImage(image);
|
||||
expect(parsed).toMatchObject({ ok: false, error: { reason: "limit" } });
|
||||
if (parsed.ok) return;
|
||||
expect(parsed.error.message).toContain("expand past");
|
||||
});
|
||||
const parsed = await readImage(image);
|
||||
expect(parsed).toMatchObject({ ok: false, error: { reason: "limit" } });
|
||||
if (parsed.ok) return;
|
||||
expect(parsed.error.message).toContain("expand past");
|
||||
},
|
||||
);
|
||||
|
||||
it.each([0x61, 0x41, 0x7a, 0x5a, 0x75])(
|
||||
"preserves namespace text from one-byte and UTF-8 marshal encodings (tag %i)",
|
||||
async (tag) => {
|
||||
const name = "é".repeat(64);
|
||||
const encoded = Buffer.from(name, tag === 0x75 ? "utf8" : "latin1");
|
||||
const table = Buffer.concat([
|
||||
Buffer.from([0x5b]),
|
||||
int32(1),
|
||||
Buffer.from([0x29, 2, tag]),
|
||||
tag === 0x7a || tag === 0x5a
|
||||
? Buffer.from([encoded.length])
|
||||
: int32(encoded.length),
|
||||
encoded,
|
||||
Buffer.from([0x29, 3, 0x69]),
|
||||
int32(3),
|
||||
Buffer.from([0x69]),
|
||||
int32(0),
|
||||
Buffer.from([0x69]),
|
||||
int32(0),
|
||||
]);
|
||||
const image = pyzArchive({
|
||||
magic: PYTHON_39_MAGIC,
|
||||
modules: [],
|
||||
toc: table,
|
||||
});
|
||||
expect(await readImage(image)).toMatchObject({
|
||||
ok: true,
|
||||
value: {
|
||||
kind: "entries",
|
||||
sha256: createHash("sha256").update(image).digest("hex"),
|
||||
entries: [{ name, role: "namespace", typeCode: 3, length: 0 }],
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const int32 = (value: number): Buffer => {
|
||||
const bytes = Buffer.alloc(4);
|
||||
|
||||
@@ -180,8 +180,9 @@ const parsePyzParts = (
|
||||
|
||||
/**
|
||||
* Marshal back-references let one stored row appear many times, so a small
|
||||
* table can name far more bytes than it holds. Each genuine entry stores its
|
||||
* own name inline, so the expanded names can never exceed the table itself.
|
||||
* table can name far more bytes than it holds. Inline one-byte Unicode names
|
||||
* can double in UTF-8; UTF-8 marshal names do not expand. Allow that decoding
|
||||
* expansion while bounding the total names repeated by back-references.
|
||||
*/
|
||||
const pyzEntries = (
|
||||
value: MarshalValue,
|
||||
@@ -193,16 +194,17 @@ const pyzEntries = (
|
||||
pyInstallerFailure("format", "PYZ table of contents is not a list"),
|
||||
);
|
||||
const entries: PyzEntry[] = [];
|
||||
const nameByteBudget = tableBytes * 2;
|
||||
let nameBytes = 0;
|
||||
for (const item of value) {
|
||||
const entry = onePyzEntry(item, payloadEnd, entries.length);
|
||||
if (!entry.ok) return entry;
|
||||
nameBytes += Buffer.byteLength(entry.value.name, "utf8");
|
||||
if (nameBytes > tableBytes)
|
||||
if (nameBytes > nameByteBudget)
|
||||
return err(
|
||||
pyInstallerFailure(
|
||||
"limit",
|
||||
`PYZ table entry names expand past the ${String(tableBytes)}-byte table that stores them`,
|
||||
`PYZ table entry names expand past the ${String(nameByteBudget)}-byte decoded-name budget`,
|
||||
),
|
||||
);
|
||||
entries.push(entry.value);
|
||||
|
||||
Reference in new issue
Block a user