Skip to content

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:

  1. Use the reserved field: consume bits from reserved without shifting others
  2. Add a new struct version: RegMapV2 with the wider field
  3. 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.