Case 63: Bitfield Width Changed¶
| Field | Value |
|---|---|
| Verdict | ๐ด BREAKING |
| Category | Breaking |
| Platforms | Linux |
| Flags | ABI break, API break |
Detected ChangeKinds |
field_bitfield_changed |
| Source files | browse on GitHub |
Category: Type Layout | Verdict: BREAKING
What breaks¶
The mode bitfield in RegMap is widened from 3 bits to 5 bits. This shifts
every subsequent bitfield (channel, priority, reserved) to different bit
positions within the same 32-bit word. Any consumer compiled against the v1
layout reads priority from the wrong bits, getting a corrupt value.
Why this matters¶
Bitfields are widely used in hardware register maps, network protocol headers, and compact flag structs. Unlike regular struct fields where padding can absorb small changes, every bit position after the widened field shifts. There is no alignment padding between bitfields within the same storage unit.
This is especially dangerous because:
- sizeof(RegMap) does not change (still 4 bytes) โ naive size checks pass
- The struct looks "compatible" at the symbol level โ same functions, same names
- The corruption is silent: wrong values, no crash, no diagnostic
Code diff¶
| Field | v1 (bits) | v2 (bits) | Change |
|---|---|---|---|
enable |
0 | 0 | unchanged |
mode |
1-3 (3 bits) | 1-5 (5 bits) | widened +2 bits |
channel |
4-7 | 6-9 | shifted +2 |
priority |
8-15 | 10-17 | shifted +2 |
reserved |
16-31 (16 bits) | 18-31 (14 bits) | shrunk -2 bits |
Real Failure Demo¶
Severity: CRITICAL
Scenario: compile app against v1, swap in v2 .so without recompile.
# Build old library + app
gcc -shared -fPIC -g v1.c -o libfoo.so
gcc -g app.c -L. -lfoo -Wl,-rpath,. -o app
./app
# โ mode = 2 (expected 2)
# โ channel = 5 (expected 5)
# โ priority = 128 (expected 128)
# Swap in new library (no recompile)
gcc -shared -fPIC -g v2.c -o libfoo.so
./app
# โ mode = 2 (expected 2)
# โ channel = 4 (expected 5)
# โ priority = 1 (expected 128)
# โ CORRUPTION: bitfield layout mismatch
Why CRITICAL: The v2 library writes priority=128 into bits 10-17, but the
app (compiled against v1) reads bits 8-15 โ extracting a completely different
value. No crash occurs; the data is silently wrong. In a hardware register
context, this could program the wrong DMA priority, causing system instability.
How to fix¶
Never change bitfield widths in a public struct. If more bits are needed:
- Use the reserved field: consume bits from
reservedwithout shifting others - Add a new struct version:
RegMapV2with the wider field - Use an opaque handle: hide the register layout behind accessor functions
Real-world example¶
Linux kernel iphdr (IP header) structure uses bitfields for version and IHL.
Any change to these widths would corrupt every network packet parsed by
userspace tools like tcpdump that embed the struct layout at compile time.
The Windows BITMAP info header uses bitfields for color masks โ driver
compatibility across Windows versions depends on these widths being frozen.
abicheck detection¶
abicheck detects this as field_bitfield_changed (BREAKING) by comparing
DWARF DW_AT_bit_size / DW_AT_bit_offset attributes between the two
versions. Even though sizeof doesn't change, the per-field bit layout
difference is caught.
References¶
Source files¶
See also: Examples overview ยท All BREAKING cases ยท Category: Breaking.