fix(apple): preserve exact integer precision with post-table plist objects (#8910)
This commit is contained in:
1 parent
027c768f07
commit
281dbc8514
3 files changed
+113
-1
No files matched your search
@@ -723,6 +723,61 @@ describe("binary keyed archive dates", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("binary keyed archive post-table objects", () => {
|
||||
it("preserves exact integer decimals when an archive string follows the offset table", () => {
|
||||
const original = Buffer.from(
|
||||
buildBinary({
|
||||
$archiver: "NSKeyedArchiver",
|
||||
$version: 100000,
|
||||
$top: { root: { UID: 2 } },
|
||||
$objects: [
|
||||
"$null",
|
||||
"hello",
|
||||
9007199254740993n as unknown as PlistValue,
|
||||
],
|
||||
}),
|
||||
);
|
||||
const intMarkerOffset = original.indexOf(0x13);
|
||||
expect(intMarkerOffset).toBeGreaterThan(0);
|
||||
// Overwrite the 8-byte integer payload with exact 9007199254740993n
|
||||
original.writeBigInt64BE(9007199254740993n, intMarkerOffset + 1);
|
||||
|
||||
const stringBytes = Buffer.from("\x55hello", "latin1");
|
||||
const stringOffset = original.indexOf(stringBytes);
|
||||
expect(stringOffset).toBeGreaterThan(0);
|
||||
const trailerOffset = original.length - 32;
|
||||
const tableOffset = Number(original.readBigUInt64BE(trailerOffset + 24));
|
||||
const count = Number(original.readBigUInt64BE(trailerOffset + 8));
|
||||
const width = original[trailerOffset + 6] ?? 1;
|
||||
|
||||
const post = Buffer.concat([
|
||||
original.subarray(0, trailerOffset),
|
||||
stringBytes,
|
||||
original.subarray(trailerOffset),
|
||||
]);
|
||||
const newTrailerOffset = post.length - 32;
|
||||
for (let i = 0; i < count; i += 1) {
|
||||
const offset = post.readUIntBE(tableOffset + i * width, width);
|
||||
if (offset === stringOffset) {
|
||||
post.writeUIntBE(trailerOffset, tableOffset + i * width, width);
|
||||
}
|
||||
}
|
||||
post.writeBigUInt64BE(BigInt(tableOffset), newTrailerOffset + 24);
|
||||
|
||||
const graph = decodeKeyedArchiveBytes(post, { offset: 0, limit: 10 });
|
||||
expect(graph.objects[2]).toMatchObject({
|
||||
id: 2,
|
||||
status: "observed",
|
||||
value: { $plist_type: "integer", decimal: "9007199254740993" },
|
||||
});
|
||||
expect(
|
||||
graph.limitations.some((lim) =>
|
||||
lim.includes("could not be completely observed"),
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("binary plist date object region", () => {
|
||||
it("refuses a binary date that extends past the object region", () => {
|
||||
// Strict bounds end objects at the offset table. Four payload bytes
|
||||
|
||||
@@ -620,3 +620,53 @@ it.each([4n, 0xffffffffffffffffn])(
|
||||
).toThrow("binary plist trailer or object table is invalid");
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves exact pre-table integer precision when a string follows the offset table", () => {
|
||||
const stringRecord = Buffer.from("\x55hello", "latin1");
|
||||
const intRecord = int64(9007199254740993n);
|
||||
const intOffset = 8;
|
||||
const tableOffset = intOffset + intRecord.length;
|
||||
const stringOffset = tableOffset + 2;
|
||||
const trailerOffset = stringOffset + stringRecord.length;
|
||||
const bytes = Buffer.alloc(trailerOffset + 32);
|
||||
Buffer.from("bplist00", "ascii").copy(bytes, 0);
|
||||
intRecord.copy(bytes, intOffset);
|
||||
stringRecord.copy(bytes, stringOffset);
|
||||
bytes[tableOffset] = intOffset;
|
||||
bytes[tableOffset + 1] = stringOffset;
|
||||
bytes[trailerOffset + 6] = 1;
|
||||
bytes[trailerOffset + 7] = 1;
|
||||
bytes.writeBigUInt64BE(2n, trailerOffset + 8);
|
||||
bytes.writeBigUInt64BE(0n, trailerOffset + 16);
|
||||
bytes.writeBigUInt64BE(BigInt(tableOffset), trailerOffset + 24);
|
||||
|
||||
const metadata = scanBinaryPlistScalars(bytes);
|
||||
expect(metadata.literals.incomplete).toBe(false);
|
||||
expect(metadata.literals.unsafeIntegerLiterals).toBe(1);
|
||||
const decimals = metadata.literals.integers.get(Number(9007199254740993n));
|
||||
expect(decimals).toBeDefined();
|
||||
expect([...decimals!]).toEqual(["9007199254740993"]);
|
||||
});
|
||||
|
||||
it("marks literals incomplete when a numeric scalar follows the offset table", () => {
|
||||
const intRecord = int64(9007199254740993n);
|
||||
const intOffset = 8;
|
||||
const postInt = int64(42n);
|
||||
const tableOffset = intOffset + intRecord.length;
|
||||
const postOffset = tableOffset + 2;
|
||||
const trailerOffset = postOffset + postInt.length;
|
||||
const bytes = Buffer.alloc(trailerOffset + 32);
|
||||
Buffer.from("bplist00", "ascii").copy(bytes, 0);
|
||||
intRecord.copy(bytes, intOffset);
|
||||
postInt.copy(bytes, postOffset);
|
||||
bytes[tableOffset] = intOffset;
|
||||
bytes[tableOffset + 1] = postOffset;
|
||||
bytes[trailerOffset + 6] = 1;
|
||||
bytes[trailerOffset + 7] = 1;
|
||||
bytes.writeBigUInt64BE(2n, trailerOffset + 8);
|
||||
bytes.writeBigUInt64BE(0n, trailerOffset + 16);
|
||||
bytes.writeBigUInt64BE(BigInt(tableOffset), trailerOffset + 24);
|
||||
|
||||
const metadata = scanBinaryPlistScalars(bytes);
|
||||
expect(metadata.literals.incomplete).toBe(true);
|
||||
});
|
||||
@@ -268,13 +268,19 @@ export const scanBinaryPlistScalars = (
|
||||
const capacity = Math.floor((trailer - offsetTable) / offsetWidth);
|
||||
if (countRaw > BigInt(capacity)) return result(true);
|
||||
const count = Number(countRaw);
|
||||
const tableEnd = offsetTable + count * offsetWidth;
|
||||
if (referenceWidth < 1) return result(true);
|
||||
const objectOffsets: number[] = [];
|
||||
let incomplete = false;
|
||||
for (let index = 0; index < count; index += 1) {
|
||||
const rawOffset = sourceLayout.objectOffset(index);
|
||||
const offset = rawOffset > safeMaximum ? undefined : Number(rawOffset);
|
||||
if (offset === undefined || offset < 8 || offset >= offsetTable) {
|
||||
if (
|
||||
offset === undefined ||
|
||||
offset < 8 ||
|
||||
(offset >= offsetTable && offset < tableEnd) ||
|
||||
offset >= trailer
|
||||
) {
|
||||
incomplete = true;
|
||||
continue;
|
||||
}
|
||||
@@ -324,6 +330,7 @@ export const scanBinaryPlistScalars = (
|
||||
const marker = bytes[offset] ?? 0;
|
||||
const type = marker >> 4;
|
||||
if (type === 3 && marker === 0x33) {
|
||||
if (offset + 9 > offsetTable) incomplete = true;
|
||||
continue;
|
||||
}
|
||||
if (type !== 1 && type !== 2) continue;
|
||||
|
||||
Reference in new issue
Block a user