Skip to content

Fix consClose corrupting decoder state for large enums#34

Open
Unisay wants to merge 1 commit intoQuid2:masterfrom
Unisay:fix/consClose-multi-byte-advance
Open

Fix consClose corrupting decoder state for large enums#34
Unisay wants to merge 1 commit intoQuid2:masterfrom
Unisay:fix/consClose-multi-byte-advance

Conversation

@Unisay
Copy link
Copy Markdown

@Unisay Unisay commented Mar 24, 2026

We ran into this in plutus (IntersectMBO/plutus#7683), which vendors a copy of flat. Enums with more than 256 constructors were causing the decoder to eat all memory and hang.

consClose only advances currPtr by 1 byte. That's fine until the constructor tag bits plus the bits already used in the current byte add up to 16 or more. A 9-bit tag after 7 bits already consumed gives usedBits = 16, the subtraction leaves it at 8, and the decoder state is toast from there. dBool computes 128 >> 8 as 0, reads every bit as False, and the Filler decoder spins building FillerBits until you run out of memory.

Fix is one line: consClose = dropBits. dropBits already does the divMod to handle arbitrary bit counts, so consClose doesn't need its own logic.

Added three tests to testLargeEnum: a control case that stays under the boundary (7 Bools + 8-bit tag = 15 bits), the actual trigger (7 Bools + 9-bit tag = 16 bits, with a timeout), and a check that a 9-bit tag in a 1-byte buffer gives NotEnoughSpace not TooMuchSpace.

consClose only ever advanced currPtr by 1 byte. When the constructor
tag bits plus the already-consumed bits in the current byte totalled
16 or more (e.g. a 9-bit tag after 7 bits already used), usedBits
overflowed past 7 and the decoder state was silently corrupted.

The immediate symptom: dBool computes (128 >> usedBits) as 0 when
usedBits >= 8, so every bit reads as False. The Filler decoder then
loops forever building FillerBit constructors until memory runs out.

Replace the hand-rolled if/else with dropBits, which already uses
divMod to handle any number of bytes correctly.

Add three regression tests:
- control: 7 Bool fields + 8-bit tag (15 total bits, no overflow)
- bug trigger: 7 Bool fields + 9-bit tag (16 bits, infinite loop
  without fix, guarded by a 5 s timeout)
- bounds check: 9-bit tag in a 1-byte buffer must fail with
  NotEnoughSpace, not TooMuchSpace
@what-the-diff
Copy link
Copy Markdown

what-the-diff bot commented Mar 24, 2026

PR Summary

  • Simplified a function
    The consClose function that is responsible for a specific part of the decoding process has been simplified, making the underlying code easier to manage.

  • Added thorough testing
    More in-depth tests have been conducted focusing on specific situations (edge cases) where the consClose function might cause other components to not work as they should because of a particular calculation error. This helps us ensure that our software is capable of properly handling such scenarios.

  • Enhanced safety measures in tests
    Additional checks in the testing processes have been integrated to make sure that the consClose function can correctly handle situations where it requires more resources than are available. This prevents it from causing bigger issues like calculation errors and endless loops in our Filler decoder. This means our product is more reliable and safe to use.

@Unisay Unisay marked this pull request as ready for review March 24, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant