CertC++-EXP62¶
Do not access the bits of an object representation that are not part of the object’s value representation
Required inputs: IR
The C++ Standard, [basic.types], paragraph 9 [ ISO/IEC 14882-2014], states the following:
The object representation of an object of type
Tis the sequence of Nunsigned charobjects taken up by the object of typeT, where N equalssizeof(T). The value representation of an object is the set of bits that hold the value of typeT.
The narrow character types (
char,
signed char, and
unsigned char)-as well as some other integral types on specific
platforms-have an object representation that consists solely of the bits from
the object's value representation. For such types, accessing any of the bits of
the value representation is well-defined behavior. This form of object
representation allows a programmer to access and modify an object solely based
on its bit representation, such as by calling
std::memcmp() on its object representation.
Other types, such as classes, may not have an object representation composed solely of the bits from the object's value representation. For instance, classes may have bit-field data members, padding inserted between data members, a vtable to support virtual method dispatch, or data members declared with different access privileges. For such types, accessing bits of the object representation that are not part of the object's value representation may result in undefined behavior depending on how those bits are accessed.
Do not access the bits of an object representation that are not part of the
object's value representation. Even if the bits are accessed in a
well-defined manner, such as through an array of
unsigned char objects, the values represented by those bits are
unspecified or implementation-defined, and reliance on any particular value can
lead to abnormal program execution.
Noncompliant Code Example
In this noncompliant code example, the complete object representation is
accessed when comparing two objects of type
S. Per the C++ Standard, [class], paragraph 13 [
ISO/IEC
14882-2014], classes may be padded with data to ensure that they are
properly aligned in memory. The contents of the padding and the amount of
padding added is
implementation-defined.
This can lead to incorrect results when comparing the object representation of
classes instead of the value representation, as the padding may assume
different
unspecified
values for each object instance.
#include <cstring>
struct S {
unsigned char buffType;
int size;
};
void f(const S &s1, const S &s2) {
if (!std::memcmp(&s1, &s2, sizeof(S))) {
// ...
}
}
Compliant Solution
In this compliant solution,
S overloads
operator==() to perform a comparison of the value representation
of the object.
struct S {
unsigned char buffType;
int size;
friend bool operator==(const S &lhs, const S &rhs) {
return lhs.buffType == rhs.buffType &&
lhs.size == rhs.size;
}
};
void f(const S &s1, const S &s2) {
if (s1 == s2) {
// ...
}
}
Noncompliant Code Example
In this noncompliant code example,
std::memset() is used to clear the internal state of an
object. An
implementation may
store a vtable within the object instance due to the presence of a
virtual function, and that vtable is subsequently overwritten by the call
to
std::memset(), leading to
undefined
behavior when virtual method dispatch is required.
#include <cstring>
struct S {
int i, j, k;
// ...
virtual void f();
};
void f() {
S *s = new S;
// ...
std::memset(s, 0, sizeof(S));
// ...
s->f(); // undefined behavior
}
Compliant Solution
In this compliant solution, the data members of
S are cleared explicitly instead of calling
std::memset().
struct S {
int i, j, k;
// ...
virtual void f();
void clear() { i = j = k = 0; }
};
void f() {
S *s = new S;
// ...
s->clear();
// ...
s->f(); // ok
}
Exceptions
EXP62-CPP-EX1: It is permissible to access the bits of an object representation when that access is otherwise unobservable in well-defined code. Specifically, reading bits that are not part of the value representation is permissible when there is no reliance or assumptions placed on their values, and writing bits that are not part of the value representation is only permissible when those bits are padding bits. This exception does not permit writing to bits that are part of the object representation aside from padding bits, such as overwriting a vtable pointer.
For instance, it is acceptable to call
std::memcpy() on an object containing a bit-field, as in the
following example, because the read and write of the padding bits cannot be
observed.
#include <cstring>
struct S {
int i : 10;
int j;
};
void f(const S &s1) {
S s2;
std::memcpy(&s2, &s1, sizeof(S));
}
Code that complies with this exception must still comply with OOP57-CPP. Prefer special member functions and overloaded operators to C Standard Library functions.
Risk Assessment
The effects of accessing bits of an object representation that are not part of the object's value representation can range from implementation-defined behavior (such as assuming the layout of fields with differing access controls) to code execution vulnerabilities (such as overwriting the vtable pointer).
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| EXP62-CPP | High | Probable | High | P6 | L2 |
Related Guidelines
| SEI CERT C++ Coding Standard | OOP57-CPP. Prefer special member functions and overloaded operators to C Standard Library functions |
Bibliography
| [ ISO/IEC 14882-2014] | Subclause 3.9, "Types" Subclause 3.10, "Lvalues and Rvalues" Clause 9, "Classes" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
disallowed_memcmp_pointer_arg |
Disallowed type of pointer argument. |
None |
False |
invalid_memset |
memset shall not be used with non-trivially default constructible types. |
None |
False |
memcmp_char_pointer_arg |
memcmp shall not be used with char pointer argument, use strncmp instead. |
None |
False |
memcmp_float |
memcmp shall not be used to compare floats as the same value may be stored using different representations. |
None |
False |
memcmp_padding |
memcmp shall not be used to compare structs with padding. |
None |
False |
memcmp_struct_pointer_arg |
memcmp shall not be used with struct pointer argument as it would compare padding as well. |
None |
False |
memcmp_union_pointer_arg |
memcmp shall not be used with union pointer argument as it would compare padding and different kinds of representation. |
None |
False |
Options¶
This rule shares the following common options: exclude_in_macros, exclude_messages_in_system_headers, excludes, extend_exclude_to_macro_invocations, includes, justification_checker, languages, post_processing, provider, report_at, severity
The following places define options that affect this rule: Stylechecks, Analysis-GlobalOptions
allow_aggregates_with_nsdmi¶
allow_aggregates_with_nsdmi : bool = True
memset usage with aggregates that are not trivially
default constructible only because of a default non-static data member initializer
(NSDMI).
allow_char¶
allow_char : bool = True
memcmp on char type.
allow_composites_without_padding¶
allow_composites_without_padding : bool = True
memcmp on structs and unions that have no
padding bytes.
allow_float¶
allow_float : bool = False
memcmp on floating point types.
functions¶
functions : set[bauhaus.analysis.config.QualifiedName] = {'memcmp'}
ignore_calls_in_functions¶
ignore_calls_in_functions : set[bauhaus.analysis.config.QualifiedName] = set()
relevant_memset_functions¶
relevant_memset_functions : set[bauhaus.analysis.config.QualifiedName] = {'memset'}