mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(evm): reject repeated container keys in Solidity metadata (#6230)
* fix(evm): reject repeated container keys in Solidity metadata * test(evm): check the repeat-key diagnostic for deeply nested keys
This commit is contained in:
2 files changed
+36
-12
No files matched your search
@@ -119,6 +119,9 @@ it("decodes distinct byte keys and distinct nested-container keys", () => {
|
||||
expect(inspectSolidityMetadata(bytecode("a2810101810202"))).toMatchObject({
|
||||
status: "partial",
|
||||
});
|
||||
expect(inspectSolidityMetadata(bytecode("a281800181a002"))).toMatchObject({
|
||||
status: "partial",
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
@@ -128,6 +131,9 @@ it.each([
|
||||
["floating point", "a16178fa3f800000"],
|
||||
["duplicate key", "a2617801617802"],
|
||||
["duplicate byte key", "a2410101410102"],
|
||||
["duplicate array key", "a280018002"],
|
||||
["duplicate map key", "a2a001a002"],
|
||||
["duplicate map key in another order", "a2a20102030401a20304010202"],
|
||||
["excessive nesting", "a16178" + "81".repeat(65) + "00"],
|
||||
["impossible collection", "a161788a00"],
|
||||
])("keeps exact ranges and unavailable interpretation for %s", (_name, hex) => {
|
||||
@@ -139,6 +145,18 @@ it.each([
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects repeated deeply nested container keys promptly", () => {
|
||||
const key = "81".repeat(62) + "00";
|
||||
const started = performance.now();
|
||||
expect(
|
||||
inspectSolidityMetadata(bytecode("a2" + key + "01" + key + "02")),
|
||||
).toMatchObject({
|
||||
status: "not-decoded",
|
||||
diagnostic: expect.stringContaining("found repeat map key"),
|
||||
});
|
||||
expect(performance.now() - started).toBeLessThan(1000);
|
||||
});
|
||||
|
||||
it.each(["", "60006000f3", "000000", "00010001"])(
|
||||
"does not claim absent Solidity metadata for an unavailable trailer: %s",
|
||||
(hex) =>
|
||||
|
||||
+18
-12
@@ -90,6 +90,17 @@ const scalar = (value: unknown): SolidityMetadataScalar => {
|
||||
);
|
||||
};
|
||||
|
||||
/** Value identity for duplicate-key checks; map entry order is not significant. */
|
||||
const keyIdentity = (value: unknown): string => {
|
||||
if (value instanceof Map)
|
||||
return `{${[...value]
|
||||
.map(([key, item]) => `${keyIdentity(key)}:${keyIdentity(item)}`)
|
||||
.sort()
|
||||
.join(",")}}`;
|
||||
if (Array.isArray(value)) return `[${value.map(keyIdentity).join(",")}]`;
|
||||
return JSON.stringify(scalar(value));
|
||||
};
|
||||
|
||||
/** Parse only a plausible terminal root-map candidate; never establish absence. */
|
||||
export const inspectSolidityMetadata = (
|
||||
bytes: Uint8Array,
|
||||
@@ -122,20 +133,15 @@ export const inspectSolidityMetadata = (
|
||||
});
|
||||
if (!(raw instanceof Map))
|
||||
throw new Error("Terminal CBOR candidate did not decode to a root map.");
|
||||
// cborg compares Map keys by identity, so repeated byte-string keys (each
|
||||
// its own Uint8Array) pass its duplicate check; compare scalars by value.
|
||||
// cborg compares Map keys by identity, so repeated byte-string or container
|
||||
// keys (each its own object) pass its duplicate check; compare by value.
|
||||
const seen = new Set<string>();
|
||||
for (const [key, value] of raw as Map<unknown, unknown>) {
|
||||
const entry = { key: scalar(key), value: scalar(value) };
|
||||
if (entry.key.kind !== "uninterpreted") {
|
||||
const identity = JSON.stringify(entry.key);
|
||||
if (seen.has(identity))
|
||||
throw new Error(
|
||||
`CBOR decode error: found repeat map key ${identity}`,
|
||||
);
|
||||
seen.add(identity);
|
||||
}
|
||||
result.entries.push(entry);
|
||||
const identity = keyIdentity(key);
|
||||
if (seen.has(identity))
|
||||
throw new Error(`CBOR decode error: found repeat map key ${identity}`);
|
||||
seen.add(identity);
|
||||
result.entries.push({ key: scalar(key), value: scalar(value) });
|
||||
}
|
||||
result.status = result.entries.some(
|
||||
({ key, value }) =>
|
||||
|
||||
Reference in new issue
Block a user