-
Notifications
You must be signed in to change notification settings - Fork 123
C aliasing #1728
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
C aliasing #1728
Conversation
The serial-link parser subtracts 5 bytes (type+len+crc) when validating a frame. If a truncated frame reaches SERIAL_LINK_STATE_CHECK with frame_len < 5, the subtraction underflows, allowing out-of-bounds reads during CRC/length handling. Reject frames shorter than the minimum header+CRC size before parsing the length.
The serial-link frame buffer is byte-addressed. Casting &frame[1] / &frame[3+len] to a uint16_t* can be unaligned, which is undefined behavior in C. Decode the little-endian length and CRC fields bytewise instead of via unaligned uint16_t loads.
The BLE HWW path previously relied on ASSERT(payload_length == 64), which is compiled out in release builds. Enforce the expected USB HID report size at runtime and drop malformed frames so usb_packet_process() never consumes stale/partial payload bytes. Also include the headers that define USB_REPORT_SIZE and USB_FRAME explicitly.
Enable GCC -Wcast-align=strict globally (Clang uses -Wcast-align) so misaligned pointer-cast issues are caught across firmware, bootloader, unit tests, and simulators. Fixes fall into two UB classes: 1) Alignment UB: casting byte buffers/packed payloads to wider pointer types and then dereferencing can require stricter alignment than the source object provides. This is undefined behavior per C. 2) Effective-type / strict-aliasing UB: reading a value by reinterpreting a uint8_t buffer as a different object type via a pointer cast violates C’s effective-type/aliasing rules, so the compiler may miscompile even when the address happens to be aligned. Resolve by decoding from bytes explicitly or memcpy’ing into properly-typed locals (e.g. USB_FRAME, u32/version_t) before use. Useful references: - https://www.open-std.org/jtc1/sc22/WG14/www/docs/n3519.pdf For C11 (ISO/IEC 9899:2011), the key places are: - Alignment UB from pointer casts: §6.3.2.3 p7 (“Pointers”) — converting to a different object pointer type and the result not being correctly aligned is UB. - Effective type rule: §6.5 p6 (“Expressions”) — defines an object’s effective type (including the memcpy/memmove wording). - Strict-aliasing rule: §6.5 p7 — lists the allowed lvalue types you may use to access an object’s stored value (the classic aliasing bullet list).
NickeZ
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good in general, left a few comments, I will need to test as well.
|
|
||
| // bytes with index 1-2 are the length | ||
| uint16_t len = *((uint16_t*)&self->frame[1]); | ||
| uint16_t len = (uint16_t)self->frame[1] | ((uint16_t)self->frame[2] << 8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not memcpy here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could do, but the agent thought it was better to be explicit about endianness. Do you prefer to change it to memcpy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But why doesn't that argument apply to the other places? where in one case there even is a comment about endianess.
I don't see any reasons for different locations to use different solutions and just wanted to point out the inconsistency. memcpy seems more readable/maintainable to me. I don't really care what the agent thinks, I care about what you think is more readable and maintainable. The end result is probably the same instructions.
| // bytes with index n-2 and n-1 are the crc | ||
| uint16_t crc_frame = *(uint16_t*)&self->frame[3 + len]; | ||
| uint16_t crc_frame = | ||
| (uint16_t)self->frame[3 + len] | ((uint16_t)self->frame[3 + len + 1] << 8); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not memcpy here?
| } | ||
| // util_log("read %s", util_dbg_hex(buffer, read)); | ||
| uint16_t len = *((uint16_t*)buffer); | ||
| uint16_t len = ((uint16_t)buffer[0] << 8) | (uint16_t)buffer[1]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not memcpy here?
No description provided.