You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

The C++ Standard, [class.mem], paragraph 13 [ISO/IEC 14882-2014], describes the layout of non-static data members of a non-union class, specifying:

Nonstatic data members of a (non-union) class with the same access control are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified. Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions and virtual base classes.

Further, [class.bit], paragraph 1, states, in part:

Allocation of bit-fields within a class object is implementation-defined. Alignment of bit-fields is implementation-defined. Bit-fields are packed into some addressable allocation unit.

Thus, padding bits may be present at any location within a class object instance (including at the beginning of the object, in the case of an unnamed bit-field as the first member declared in a class). Unless initialized by zero-initialization, padding bits contain indeterminate values, which may contain sensitive information.

When passing a pointer to a class object instance across a trust boundary to a different trusted domain, the programmer must ensure that the padding bits of such an object does not contain sensitive information.

Noncompliant Code Example

This noncompliant code example runs in kernel space and copies data from test to user space. However, padding bits may be used within the object, for example, to ensure the proper alignment of class data members. These padding bits may contain sensitive information, which may then be leaked when the data is copied to user space.

#include <cstddef>
 
struct test {
  int a;
  char b;
  int c;
};
 
// Safely copy bytes to user space
extern int copy_to_user(void *dest, void *src, std::size_t size);
 
void do_stuff(void *usr_buf) {
  test arg{1, 2, 3};
  copy_to_user(usr_buf, &arg, sizeof(arg));
}

Noncompliant Code Example

In this noncompliant code example, arg is value-initialized through direct initialization. Because test does not have a user-provided default constructor, the value-initialization is preceded by a zero-initialization which guarantees that the padding bits are initialized to 0 before any further initialization occurs. It is akin to using std::memset() to initialize all of the bits in the object to 0.

#include <cstddef>
 
struct test {
  int a;
  char b;
  int c;
};
 
// Safely copy bytes to user space
extern int copy_to_user(void *dest, void *src, std::size_t size);
 
void do_stuff(void *usr_buf) {
  test arg{};
 
  arg.a = 1;
  arg.b = 2;
  arg.c = 3;
 
  copy_to_user(usr_buf, &arg, sizeof(arg));
}

However, compilers are free to implement arg.b = 2 by setting the low byte of a 32-bit register to 2, leaving the high bytes unchanged, and storing all 32 bits of the register into memory. This could leak the high-order bytes resident in the register to a user.

Compliant Solution

This compliant solution serializes the structure data before copying it to an untrusted context:

#include <cstddef>
#include <cstring>
 
struct test {
  int a;
  char b;
  int c;
};
 
// Safely copy bytes to user space.
extern int copy_to_user(void *dest, void *src, size_t size);
 
void do_stuff(void *usr_buf) {
  test arg{1, 2, 3};
  // May be larger than strictly needed.
  unsigned char buf[sizeof(arg)];
  size_t offset = 0;
 
  std::memcpy(buf + offset, &arg.a, sizeof(arg.a));
  offset += sizeof(arg.a);
  std::memcpy(buf + offset, &arg.b, sizeof(arg.b));
  offset += sizeof(arg.b);
  std::memcpy(buf + offset, &arg.c, sizeof(arg.c));
  offset += sizeof(arg.c);
 
  copy_to_user(usr_buf, buf, offset /* size of info copied */);
}

This code ensures that no uninitialized padding bits are copied to unprivileged users. Note that the structure copied to user space is now a packed structure and that the copy_to_user() function would need to unpack it to re-create the original, padded structure.

Compliant Solution (Padding Bytes)

Padding bits can be explicitly declared as fields within the structure. This solution is not portable, however, because it depends on the implementation and target memory architecture. The following solution is specific to the x86-32 architecture:

#include <cstddef>

struct test {
  int a;
  char b;
  char padding_1, padding_2, padding_3;
  int c;
 
  test(int a, char b, int c) : a(a), b(b),
    padding_1(0), padding_2(0), padding_3(0),
    c(c) {}
};
// Ensure c is the next byte after the last padding byte.
static_assert(offsetof(test, c) == offsetof(test, padding_3) + 1,
              "Object contains intermediate padding");
// Ensure there is no trailing padding.
static_assert(sizeof(test) == offsetof(test, c) + sizeof(int),
              "Object contains trailing padding");



// Safely copy bytes to user space.
extern int copy_to_user(void *dest, void *src, size_t size);

void do_stuff(void *usr_buf) {
  test arg{1, 2, 3};
  copy_to_user(usr_buf, &arg, sizeof(arg));
}

The static_assert() declaration accepts a constant expression and an error message. The expression is evaluated at compile time and, if false, the compilation is terminated and the error message is used as the diagnostic. The explicit insertion of the padding bytes into the struct should ensure that no additional padding bytes are added by the compiler, and consequently both static assertions should be true. However, it is necessary to validate these assumptions to ensure that the solution is correct for a particular implementation.

Risk Assessment

Padding bits might inadvertently contain sensitive data such as pointers to kernel data structures or passwords. A pointer to such a structure could be passed to other functions, causing information leakage.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

DCL60-CPP

Low

Unlikely

High

P1

L3

Related Vulnerabilities

Numerous vulnerabilities in the Linux Kernel have resulted from violations of this rule. CVE-2010-4083 describes a vulnerability in which the semctl() system call allows unprivileged users to read uninitialized kernel stack memory, because various fields of a semid_ds struct declared on the stack are not altered or zeroed before being copied back to the user. CVE-2010-3881 describes a vulnerability in which structure padding and reserved fields in certain data structures in QEMU-KVM were not initialized properly before being copied to user space. A privileged host user with access to /dev/kvm could use this flaw to leak kernel stack memory to user space. CVE-2010-3477 describes a kernel information leak in act_police where incorrectly initialized structures in the traffic-control dump code may allow the disclosure of kernel memory to user space applications.

 Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]

Subclause 8.5, "Initializers"
Subclause 9.2, "Class Members"
Subclause 9.6, "Bit-fields"

 

  

  • No labels