Skip to content

Narrow field values with truncatingIfNeeded instead of the trapping initializer - #15

Open
JanLahmann wants to merge 1 commit into
roznet:mainfrom
JanLahmann:narrowing-conversions-truncate
Open

JanLahmann wants to merge 1 commit into
roznet:mainfrom
JanLahmann:narrowing-conversions-truncate

Conversation

@JanLahmann

Copy link
Copy Markdown

What

rzfit_swift_string_for_type narrows a FIT_UINT32 into the field's own type (FIT_ENUM, FIT_UINT8, FIT_UINT16, …) with the exact initializer:

case 1: return rzfit_swift_string_from_file( FIT_ENUM(val) )

FIT_ENUM(val) traps when val does not fit a byte. A structurally valid FIT can carry such a value — a vendor's private enum, or one corrupt byte in an otherwise good file — and then FitFile.init takes the whole process down before the caller sees a line of it. There is nothing to catch: it is a Swift runtime trap, not an error.

This PR changes the one line in the generator (python/fitsdkparser.py, swift_stmt_case_type_function_call) and the generated rzfit_swift_map.swift it produces to truncatingIfNeeded:, which is what the C SDK does when it reads the same bytes into the narrower type, and adds a test with a value that does not fit an enum and one that does not fit a uint16. The generated diff is 179 lines, all of the shape FIT_X(val)FIT_X(truncatingIfNeeded: val).

How it was found

A mutation fuzz over a corpus of Garmin recordings in a wing-foiling app that imports a rider's whole year from intervals.icu: one tester's first sync crashed the app on their first activity, the crash was inside the decoder, and no amount of validation on our side (header, CRC, message framing) could see it, because the file is valid. We are shipping a vendored copy with this change until it is upstream; thank you for the library.

…nitializer

rzfit_swift_string_for_type narrows a FIT_UINT32 into the field's own type (FIT_ENUM,
FIT_UINT8, FIT_UINT16, ...) with the exact initializer, which traps when the value does
not fit. A structurally valid file can carry such a value - a vendor's private enum, or
one corrupt byte - and then FitFile.init takes the whole process down before the caller
sees a line of it. This changes the generator template and the generated map to
truncatingIfNeeded:, which is what the C SDK does when it reads the same bytes into the
narrower type, and adds a test with a value that does not fit an enum and one that does
not fit a uint16.

Found by a mutation fuzz over a corpus of Garmin recordings in a wing-foiling app that
imports a rider's whole year from intervals.icu.
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