Repository navigation
Conversation
… past the end decodeItemAt read data[offset] unchecked, so an offset past the end decoded undefined as the integer 0. decodeArrayAt allocated new Array(length) for the declared length first, so the five bytes 9a7fffffff (array of 2^31 - 1 items, none present) aborted the Node process with a heap out-of-memory that try/catch cannot stop, and decodeMapAt looped over the declared count. A 4-byte length also read as a signed number, so 0xffffffff became -1 and slipped past the loops. - decodeItemAt throws CBORError at or past the end of the input - definite array/map lengths larger than the bytes left throw before allocating or looping (each item, key and value takes >= 1 byte) - decodeLengthAt reads the 4-byte length unsigned (>>> 0) Well-formed CBOR never hits any of these checks. Fixes IntersectMBO#661.
|
Closing this in favour of #663 — @solidsnakedev opened it 35 minutes before this PR, fixing the same issue with the same bounds checks, and one fix for #661 is enough for the maintainers to review. The one difference in this version — reading the 4-byte length in Building in the open as an independent Cardano developer — not affiliated with the Cardano Foundation, IOG or Intersect. @Kshot9000 · https://x.com/kshot9000 · github.com/Kshot3000 |
Summary
Fixes #661. The CBOR decoder trusted declared lengths and read past the end of its input:
CBOR.fromCBORHex("9a7fffffff")— five bytes declaring an array of 2^31 − 1 items — aborted the Node process with "JavaScript heap out of memory" (exit 134), whichtry/catchcannot stop. Reproduced locally before the fix; after the fix it throwsCBORErrorin ~1 ms.Transaction.fromCBORHexhit the same path.Cause
In
packages/evolution/src/CBOR.ts:decodeItemAtreadsdata[offset]with no bounds check. Past the end that isundefined, which bit-shifts to major type 0, so a truncated input silently decodes items as the integer 0 instead of failing.decodeArrayAtallocatesnew Array(length)for the declared length before decoding anything, anddecodeMapAtloops over it.decodeLengthAtbuilds a 4-byte length with signed 32-bit shifts, so a length with the top bit set (e.g.0xffffffff) reads as a negative number and slips past both loops.Fix
Three checks, all of which well-formed CBOR never hits (so data read from the chain decodes exactly as before):
decodeItemAtthrowsCBORErrorwhen the offset is at or past the end of the input.decodeLengthAtis read unsigned (>>> 0).Tests
New
CBOR Decoder Bounds (issue #661)block intest/CBOR.test.ts:9a7fffffffandba7fffffffthrowCBORError(array and map forms, including throughfromCBORHexWithFormat).baffffffffthrows — the signed-length case.8300,81anda100throw — declared items/pairs running past the end.c0and81c0throw — an item starting exactly at the end of the input.83010203,a10102, and a 24-item array announced with a 1-byte length header.Full package suite: 1697 passed, 75 skipped, 0 test failures. Three test files fail to import in this checkout for pre-existing environment reasons, untouched by this PR:
test/CBOR.KeyOrder.test.ts(FastCheckfromeffect— verified broken the same way on unmodifiedmain), andtest/Transaction.CanonicalWitnessAdd.test.ts/test/TxBuilder.CodecOptions.test.ts(cardano-hw-interop-libis not installed in the checkout).A patch changeset is included.
Building in the open as an independent Cardano developer — not affiliated with the Cardano Foundation, IOG or Intersect.
@Kshot9000 · https://x.com/kshot9000 · github.com/Kshot3000
ADA donations: addr1q8hnl6vl5a6k3rw3n5g3jtte696zcl76kfatzv7gpswa9r0dj7fma6klq55y4ffm7tf0em09udnyhuk4ah92pl5x9jpqjae44v
cc @cardano-foundation @IntersectMBO