fix: bound bolos_ux_sskr_combine() by the frame it was handed - #148
Merged
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:
share;
four- and eight-byte lengths, the reserved value and the indefinite
form, none of which carries a length this function can act on;
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;
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.