Invalid bitmask

Vulnerability potential Low
DDoS potential None

The bitmask size is bigger than the variable itself

Impact

A bitmask whose set bits extend beyond the width of the variable it is applied to has dead bits: the high bits of the mask can never match anything, because the variable simply has no bits there. The visible effect is a logic error — a flag that is never seen, a field that is always read as zero, a test that never triggers. When the mask is applied, the operand is often promoted to a wider type first, so the surplus bits may instead pick up sign-extension bits or adjacent storage that the programmer never intended to read, giving subtly wrong results rather than a clean failure.

Vulnerability potential

The security relevance of this defect is usually limited. It is primarily a correctness bug: the mask and the variable are mismatched, so some bits are ignored or always read as zero. The main risk is indirect — if the masked value is used in a permission, flag, or length decision, a mask that drops the wrong bits could let a value pass a check it should fail, or mis-parse a security relevant field. There is no direct memory-corruption path from the mask itself, so the rating stays low absent such a downstream use.

Technical details

A bitmask is meaningful only over the bits that the target variable actually has. A uint8_t holds 8 bits, so any mask bit at position 8 or higher is dead. The mismatch usually stems from copying a constant defined for a wider type, or from changing a variable’s type without revisiting the masks that use it.

Integer promotion

The surplus bits are not always silently ignored. In uint8_t b; b & 0x1FF, b is promoted to int before the AND, so the expression is computed in 32-bit arithmetic. The mask’s bit 8 then ANDs against bit 8 of the promoted value, which is always zero for a uint8_t, so that bit can never be set — the mask is simply too big to be useful.

Sign extension

If the masked variable is a signed narrow type holding a negative value, promotion sign-extends it: int8_t b = -1; b & 0xFF0 becomes 0xFFFFFFFF & 0xFF0 = 0xFF0, so the “out-of-range” mask bits suddenly do match, producing a value the author likely did not expect. This is why oversized masks are bugs even when they “work”.

Field width mismatch

For bit-field extraction ((v >> shift) & mask), a mask wider than the field captures neighboring fields. The mask must equal (1u << field_width) - 1.

Catching the issue

Static analysis

PVS-Studio has dedicated diagnostics for masks that are larger than the operand (e.g. expressions that are always true/false because of the mask). Coverity and CodeQL flag bitwise operations whose result is constant or whose mask exceeds the operand width. clang-tidy’s bugprone-* and hicpp-signed-bitwise checks catch related mistakes.

Compiler warnings

-Wtautological-constant-out-of-range-compare and Clang’s -Wconstant-conversion warn when a constant cannot fit the type. Enable -Wconversion/-Wsign-conversion to surface the implicit promotions that make the surplus bits behave unexpectedly.

Code review

Define masks in terms of the type width, e.g. derive the mask from the field size rather than hard-coding a literal, and keep mask constants next to the variable definition so a type change is caught. Add a static assertion such as static_assert((MASK & ~(uint8_t)~0u) == 0) to prove the mask fits.

How to reproduce

Observe that the mask bit 8 (0x100) can never match an 8-bit value, so the “feature enabled” branch is unreachable for the unsigned case, while the signed case picks up sign-extension bits and matches unexpectedly.

#include <stdio.h>
#include <stdint.h>

int main(void)
{
    uint8_t flags = 0xFF;          /* every bit of an 8-bit value is set */

    /* 0x100 is bit 8 — beyond the width of uint8_t. */
    if (flags & 0x100u)
        printf("enabled\n");
    else
        printf("never reached: mask bit is outside the variable\n");

    int8_t sflags = -1;            /* promotes to 0xFFFFFFFF */
    printf("signed surplus bits match: 0x%X\n", sflags & 0xF00);
    return 0;
}