Skip to content

fix: bound bolos_ux_sskr_combine() by the frame it was handed - #148

Merged
aido merged 2 commits into
aido:bip85from
buzzromain:fix/sskr-combine-frame-length-bip85
Aug 4, 2026
Merged

aido merged 2 commits into
aido:bip85from
buzzromain:fix/sskr-combine-frame-length-bip85

Conversation

@buzzromain

Copy link
Copy Markdown

The function reads byte 3 of the frame, and byte 4 when the CBOR header is
the long form, before anything has said the frame is that long. It then
hands sskr_combine_shards() one pointer per share and a shard length taken
out of those bytes, and that function reads the whole shard from each.

Neither bound already there covers this. The share-count bound constrains
the divisor of the stride, not the buffer it divides; the shard-length
bound says the declared length is one a shard may have, not one this frame
can hold. A ten-byte frame whose byte 3 declares the 21-byte minimum
satisfies both and then has twenty-one bytes read out of it.

bolos_ux_sskr_hex_check() bounds the same quotient for the same reason and
says so where it does it -- "Reading byte 3 is in bounds because of the
stride bound" -- but that guard was never carried over. Four things are
brought across:

  • a stride of at least five, so that bytes 3 and 4 are inside the first
    share;
  • CBOR additional information above 24 refused, those being the two-,
    four- and eight-byte lengths, the reserved value and the indefinite
    form, none of which carries a length this function can act on;
  • the header length derived from the additional information rather than
    from the decoded length, which is what makes the two functions agree:
    a long form declaring 21 to 23 bytes put the shard at offset 4 here
    and at offset 5 there;
  • the declared shard required to fit in its stride. Stated as an
    inequality rather than as hex_check()'s equality, because this
    function never looks at the CRC and has no reason to insist a frame
    carry one -- the 256-bit share set in the test data does not.

No path in the application reaches any of this: both entry points call
hex_check() first and its bounds are stricter. What is closed is the
contract of the function, for the next caller rather than for a current
one.

tests/unit/tests/sskr_combine_frame_length.c holds all four, calling
bolos_ux_sskr_combine() directly the way sskr_hex_check_guards.c calls
hex_check() directly, and allocating every frame at exactly its own length
so that a read one byte past it is a heap overflow AddressSanitizer
reports rather than a read of adjacent padding. Without the bounds it
aborts under ASan; the whole suite is 79/79 with them.

The function reads byte 3 of the frame, and byte 4 when the CBOR header is
the long form, before anything has said the frame is that long. It then
hands sskr_combine_shards() one pointer per share and a shard length taken
out of those bytes, and that function reads the whole shard from each.

Neither bound already there covers this. The share-count bound constrains
the divisor of the stride, not the buffer it divides; the shard-length
bound says the declared length is one a shard may have, not one this frame
can hold. A ten-byte frame whose byte 3 declares the 21-byte minimum
satisfies both and then has twenty-one bytes read out of it.

bolos_ux_sskr_hex_check() bounds the same quotient for the same reason and
says so where it does it -- "Reading byte 3 is in bounds because of the
stride bound" -- but that guard was never carried over. Four things are
brought across:

  * a stride of at least five, so that bytes 3 and 4 are inside the first
    share;
  * CBOR additional information above 24 refused, those being the two-,
    four- and eight-byte lengths, the reserved value and the indefinite
    form, none of which carries a length this function can act on;
  * the header length derived from the additional information rather than
    from the decoded length, which is what makes the two functions agree:
    a long form declaring 21 to 23 bytes put the shard at offset 4 here
    and at offset 5 there;
  * the declared shard required to fit in its stride. Stated as an
    inequality rather than as hex_check()'s equality, because this
    function never looks at the CRC and has no reason to insist a frame
    carry one -- the 256-bit share set in the test data does not.

No path in the application reaches any of this: both entry points call
hex_check() first and its bounds are stricter. What is closed is the
contract of the function, for the next caller rather than for a current
one.

tests/unit/tests/sskr_combine_frame_length.c holds all four, calling
bolos_ux_sskr_combine() directly the way sskr_hex_check_guards.c calls
hex_check() directly, and allocating every frame at exactly its own length
so that a read one byte past it is a heap overflow AddressSanitizer
reports rather than a read of adjacent padding. Without the bounds it
aborts under ASan; the whole suite is 79/79 with them.
Only src/ is linted, and the reusable workflow runs clang-format with
--Werror, so a hand-wrapped ternary fails the check.
@aido
aido merged commit 1a34d1d into aido:bip85 Aug 4, 2026
8 checks passed
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.

2 participants