mirror of
https://github.com/morluto/rea
synced 2026-10-26 14:25:49 -04:00
fix(python): preserve historical signed code fields
Keep signed co_stacksize and co_flags for historical magic entries whose constructors accepted them. Preserve magic 3410 with a constructor-revision limitation; reject negative values from 3411 onward. Fixes #6487
This commit is contained in:
@@ -1520,9 +1520,12 @@ it("rejects code CPython treats as corrupt instead of complete coverage", () =>
|
||||
expect(() => parse(file("py311", 3531, { flags: 0x80000000 }))).toThrow(
|
||||
/co_flags is negative/,
|
||||
);
|
||||
expect(() => parse(file("py37", 3401, { flags: 0x80000000 }))).toThrow(
|
||||
expect(() => parse(file("py38", 3411, { flags: 0x80000000 }))).toThrow(
|
||||
/co_flags is negative/,
|
||||
);
|
||||
expect(() => parse(file("py38", 3411, { stacksize: 0xffffffff }))).toThrow(
|
||||
/co_stacksize is negative/,
|
||||
);
|
||||
|
||||
const valid = parse(
|
||||
file("py311", 3531, {
|
||||
@@ -1544,6 +1547,25 @@ it("rejects code CPython treats as corrupt instead of complete coverage", () =>
|
||||
const negativePy37Flags = parse(file("py37", 3394, { flags: 0x80000000 }));
|
||||
expect(root(negativePy37Flags).flags).toBe(-0x80000000);
|
||||
expect(pythonPycSchema.safeParse(negativePy37Flags).success).toBe(true);
|
||||
const negativePy37Stacksize = parse(
|
||||
file("py37", 3394, { stacksize: 0xffffffff }),
|
||||
);
|
||||
expect(root(negativePy37Stacksize).stacksize).toBe(-1);
|
||||
expect(pythonPycSchema.safeParse(negativePy37Stacksize).success).toBe(true);
|
||||
const negativePy38AlphaFlags = parse(
|
||||
file("py37", 3401, { flags: 0x80000000 }),
|
||||
);
|
||||
expect(root(negativePy38AlphaFlags).flags).toBe(-0x80000000);
|
||||
const ambiguousPy38Fields = parse(
|
||||
file("py38", 3410, { stacksize: 0xffffffff, flags: 0x80000000 }),
|
||||
);
|
||||
expect(root(ambiguousPy38Fields)).toMatchObject({
|
||||
stacksize: -1,
|
||||
flags: -0x80000000,
|
||||
});
|
||||
expect(ambiguousPy38Fields.limitations).toEqual(
|
||||
expect.arrayContaining([expect.stringContaining("Magic 3410 is shared")]),
|
||||
);
|
||||
const py37 = parse(
|
||||
file("py37", 3394, {
|
||||
argcount: 1,
|
||||
|
||||
@@ -235,7 +235,7 @@ export const parsePythonPyc = (
|
||||
marshal,
|
||||
mapped.code_layout,
|
||||
input,
|
||||
mapped.python_version !== "3.7",
|
||||
mapped.negative_code_fields === "rejected",
|
||||
);
|
||||
const root = reader.readObject(0);
|
||||
if (reader.offset !== marshal.length)
|
||||
@@ -268,6 +268,12 @@ export const parsePythonPyc = (
|
||||
? "source_hash_hex is the PEP 552 header field; the original .py is not read."
|
||||
: "source_mtime and source_size are header fields; they do not prove a matching .py is present.",
|
||||
"python_version is the pinned CPython magic table for this header, not a runtime interpreter.",
|
||||
...(mapped.negative_code_fields === "revision-dependent" &&
|
||||
codeObjects.some((code) => code.stacksize < 0 || code.flags < 0)
|
||||
? [
|
||||
"Magic 3410 is shared by CPython revisions with different negative co_stacksize/co_flags constructor checks; these raw values are preserved, but this header cannot establish constructor acceptance.",
|
||||
]
|
||||
: []),
|
||||
...(unknownFlags === 0
|
||||
? []
|
||||
: [
|
||||
@@ -323,7 +329,7 @@ class MarshalReader {
|
||||
readonly bytes: Buffer,
|
||||
readonly layout: PythonPycCodeLayout,
|
||||
readonly input: InspectPythonPycInput,
|
||||
readonly rejectNegativeCodeFlags: boolean,
|
||||
readonly rejectNegativeCodeFields: boolean,
|
||||
) {}
|
||||
|
||||
readObject(depth: number): MarshalValue {
|
||||
@@ -437,9 +443,11 @@ class MarshalReader {
|
||||
const kwonlyargcount = this.nonNegativeInt32("co_kwonlyargcount");
|
||||
const nlocals =
|
||||
this.layout === "py311" ? null : this.nonNegativeInt32("co_nlocals");
|
||||
const stacksize = this.nonNegativeInt32("co_stacksize");
|
||||
const stacksize = this.i32();
|
||||
if (this.rejectNegativeCodeFields && stacksize < 0)
|
||||
malformed("co_stacksize is negative.");
|
||||
const flags = this.i32();
|
||||
if (this.rejectNegativeCodeFlags && flags < 0)
|
||||
if (this.rejectNegativeCodeFields && flags < 0)
|
||||
malformed("co_flags is negative.");
|
||||
const bytecode = asBytes(this.readObject(depth + 1), "co_code");
|
||||
// 3.11+ `_PyCode_Validate` rejects a length that is not a whole code unit.
|
||||
@@ -1501,7 +1509,7 @@ class MarshalReader {
|
||||
return value;
|
||||
}
|
||||
|
||||
/** CPython `r_long` int fields must not be negative in a code object. */
|
||||
/** CPython `r_long` count fields that its constructors require non-negative. */
|
||||
nonNegativeInt32(field: string): number {
|
||||
const value = this.i32();
|
||||
if (value < 0) malformed(`${field} is negative.`);
|
||||
|
||||
Reference in New Issue
Block a user