Skip to content

Conversation

@benma
Copy link
Collaborator

@benma benma commented Dec 25, 2025

No description provided.

benma's agent added 4 commits December 23, 2025 22:36
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).
Copy link
Collaborator

@NickeZ NickeZ left a 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not memcpy here?

Copy link
Collaborator Author

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?

Copy link
Collaborator

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);
Copy link
Collaborator

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];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not memcpy here?

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