CertC++-EXP59¶
Use offsetof() on valid types and members
Required inputs: IR
The
offsetof() macro is defined by the C Standard as a portable way to
determine the offset, expressed in bytes, from the start of the object to a
given member of that object. The C Standard, subclause 7.17, paragraph 3 [
ISO/IEC
9899:1999], in part, specifies the following:
offsetof(type, member-designator)which expands to an integer constant expression that has typesize_t, the value of which is the offset in bytes, to the structure member (designated by member-designator), from the beginning of its structure (designated by type). The type and member designator shall be such that givenstatic type t;then the expression&(t.member-designator)evaluates to an address constant. (If the specified member is a bit-field, the behavior is undefined.)
The C++ Standard, [support.types], paragraph 4 [ ISO/IEC 14882-2014], places additional restrictions beyond those set by the C Standard:
The macro
offsetof(type, member-designator)accepts a restricted set of type arguments in this International Standard. If type is not a standard-layout class, the results are undefined. The expressionoffsetof(type, member-designator)is never type-dependent and it is value-dependent if and only if type is dependent. The result of applying theoffsetofmacro to a field that is a static data member or a function member is undefined. No operation invoked by theoffsetofmacro shall throw an exception andnoexcept(offsetof(type, member-designator))shall be true.
When specifying the type argument for
the
offsetof() macro, pass only a standard-layout class. The full
description of a standard-layout class can be found in paragraph 7 of
the [class] clause of the C++ Standard, or the type can be checked
with the
std::is_standard_layout<> type trait. When specifying the
member designator argument for the
offsetof() macro, do not pass a bit-field, static data member, or
function member. Passing an invalid type or member to the
offsetof() macro is
undefined
behavior.
Noncompliant Code Example
In this noncompliant code example, a type
that is not a standard-layout class is passed to the
offsetof() macro, resulting in
undefined
behavior.
#include <cstddef>
struct D {
virtual void f() {}
int i;
};
void f() {
size_t off = offsetof(D, i);
// ...
}
Implementation Details
The noncompliant code example does not emit a diagnostic when compiled with the
/Wall switch in
Microsoft
Visual Studio2015 on x86, resulting in
off being
4, due to the presence of a vtable for type
D.
Compliant Solution
It is not possible to
determine the offset to
i within
D because
D is not a standard-layout class. However, it is possible to make
a standard-layout class within
D if this functionality is critical to the application, as
demonstrated by this compliant solution.
#include <cstddef>
struct D {
virtual void f() {}
struct InnerStandardLayout {
int i;
} inner;
};
void f() {
size_t off = offsetof(D::InnerStandardLayout, i);
// ...
}
Noncompliant Code Example
In this noncompliant code example, the offset to
i is calculated so that a value can be stored at that offset
within
buffer. However, because
i is a static data member of the class, this example results in
undefined
behavior. According to the C++ Standard, [class.static.data],
paragraph 1 [
ISO/IEC
14882-2014], static data members are not part of the subobjects of a class.
#include <cstddef>
struct S {
static int i;
// ...
};
int S::i = 0;
extern void store_in_some_buffer(void *buffer, size_t offset, int val);
extern void *buffer;
void f() {
size_t off = offsetof(S, i);
store_in_some_buffer(buffer, off, 42);
}
Implementation Details
The noncompliant code example does not emit a diagnostic when compiled with
the
/Wall switch in Microsoft Visual Studio 2015 on x86, resulting
in
off being a large value representing the offset between the null
pointer address
0 and the address of the static variable
S::i.
Compliant Solution
Because static data members are not a part of the class layout, but are instead
an entity of their own, this compliant solution passes the address of the
static member variable as the buffer to store the data in and passes
0 as the offset.
#include <cstddef>
struct S {
static int i;
// ...
};
int S::i = 0;
extern void store_in_some_buffer(void *buffer, size_t offset, int val);
void f() {
store_in_some_buffer(&S::i, 0, 42);
}
Risk Assessment
Passing an invalid type or member to
offsetof() can result in
undefined
behavior that might be
exploited to
cause data integrity violations or result in incorrect values from the
macro expansion.
| Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
| EXP59-CPP | Medium | Unlikely | Medium | P4 | L3 |
Bibliography
| [ ISO/IEC 9899:1999] | Subclause 7.17, "Common Definitions
<stddef.h>"
|
| [ ISO/IEC 14882-2014] | Subclause 9.4.2, "Static Data Members"Subclause 18.2, "Types" |
Possible Messages
Key |
Text |
Severity |
Disabled |
|---|---|---|---|
cafe_message |
{} |
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
message_predicate¶
message_predicate : typing.Callable[[Cafe_Message], bool] | None = None
True for messages to
report.
reported_messages¶
reported_messages : set[int] | None = {1424, 1425, 1426, 1427}
reported_severities¶
reported_severities : set[str] = {'error', 'warning'}
use_error_number¶
use_error_number : bool = False
use_rule_severity¶
use_rule_severity : bool = True