parts/6.03.BitRepresentation-STR.md

6.3 Bit Representations [STR]

6.3.1 Applicability to language

This vulnerabilities described in TR24772-1 clause 6.3 is applicable to C++. The “endianness” of integer types and packing of bit fields are implementation-defined properties and not portable.

The standard library type std::endian allows to portably check the endianness of a platform and code can use this information to operate on individual bytes of a machine word in the correct order.

There is no portable mapping from bitfields in a struct to individual bits in a machine word. Therefore, C++ bitfields should not be used to directly map to bits in hardware, even though the compiler provides suitable mapping and manipulation operations. A further complication is that accessing a bitfield can often not easily be performed atomically, because the non-participating bits of a memory location need to be read before the relevant bits can be mutated through masking, and the whole memory location has to be written again. It is possible to simulate bitfields with a defined layout through library class types that implement the required masking operations.

For individual bits std::bitset<N> and std::vector<bool> can provide suitable representations at run time, but don’t support a direct mapping to machine words. However, be aware that std::vector<bool> does not in general behave like a std::vector which can cause generic code to misbehave.

C++ provides a rich set of bitwise operators that can be used to address the issues of bit manipulation in a portable way. However, the shift operation can result in undefined behavior when shifting by a negative or too large value, or when shifting a signed operand. It is advisable to use bit operations only on appropriate unsigned integral types with a known width while being careful of potential integral promotion that might change a small unsigned operand type to be promoted to a signed integer type. When bitwise memory operations are needed, it is good practice to encapsulate such operations in a class type’s member functions.

For representing individual bitmasks values employed in bit operations, it is advisable to put the corresponding named constants in an enumeration type with the appropriate underlying type for easier recall.

While a bit shift of an integral value can be viewed as a multiplication or division by a power of two, it should not be used in arithmetic expression to implement such an operation. Compilers will automatically implement such a multiplication in the most efficient way, there is no need to obfuscate multiplication and division as shift operations.

Except for specific situations (trivial types), objects of class type can not be assumed to have a layout appropriate to be manipulated on a byte or bitwise level. Depending on the size and alignment of its data members a class type might have padding bytes between members. The absence of padding in a trivially copyable type T can be checked with a static_assert(std::has_unique_object_representations_v<T>).

std::bit_cast can be used to reinterpret suitably sized trivial types on a bitwise level, i.e., for accessing the binary representation of a floating point value. However, for types with padding bits that do do not participate in an object’s value representation, the corresponding bits in a bit_cast result have indeterminate values. If those bits are used to compare for equality, with the function memcmp for instance, the padding bits may differ and cause false negatives.

Malicious code could use such padding bits as a secret channel which might be accessed through copying the underlying bytes.

See C++ Core Guidelines ES101 use unsigned types for bit manipulation.

6.3.2 Avoidance mechanisms for language users

To avoid the vulnerability or mitigate its ill effects, C++ software developers can: