...
In this noncompliant code example, padding bits may abound, including when:
- the alignment padding bits after a virtual method table requires additional padding bits for aligning or virtual base class data to align a subsequent data member,
- alignment padding bits are required to position a subsequent data member on a properly-aligned boundary,
- alignment padding bits to position data members of varying access control levels.
- bit-field padding bits are required because when the sequential set of bit-fields does not fill an entire allocation unit,
- bit-field padding bits when two adjacent bit-fields are declared with different underlying types,
- padding bits when a bit-field is declared with a length greater than the number of bits in the underlying allocation unit (the extra bits are treated as padding bits),
- the class instance itself requires additional padding bits to ensure it a class instance will be appropriately aligned for use within an array, or
- padding bits are required due to the allocation of data members of varying access control levels.
This code example runs in kernel space and copies data from arg to user space. However, the padding bits within the object instance may contain sensitive information that will then be leaked when the data is copied to user space.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <cstddef>
class base {
public:
virtual ~base() = default;
};
class test : public virtual base {
alignas(32) double h;
char i;
unsigned j : 80;
protected:
unsigned k;
unsigned l unsigned: k4;
unsigned short lm : 43;
public:
char mn;
double no;
test(double h, char i, unsigned j, unsigned k, unsigned l, unsigned short m,
char mn, double no) :
h(h), i(i), j(j), k(k), l(l), m(m), n(n), o(o) {}
virtual void foo();
};
// 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{0.0, 1, 2, 3, 4, 5, 6, 7.0};
copy_to_user(usr_buf, &arg, sizeof(arg));
} |
Padding bits are implementation-defined, so the layout of the class object may differ between compilers or architectures. When When compiled with GCC 5.3.0 for x86-32, the test object requires 96 bytes of storage to accommodate 27 29 bytes of data (31 33 bytes including the vtable) and has the following layout:
| Offset (bytes (bits)) | Storage Size (bytes (bits)) | Reason | Offset | Storage Size | Reason | |||
|---|---|---|---|---|---|---|---|---|
| 0 | 1 (32) | vtable pointer | 56 (448) | 4 (32) | unsigned k | |||
| 4 (32) | 28 (224) | data member alignment padding | 60 (480) | 0 (4) | unsigned l : 4 | |||
| 32 (256) | 8 (64) | double h | 60 (484) | 0 (43) | unused bit-field bitsunsigned short m : 3 | |||
| 40 (320) | 1 (8) | char i | 61 60 (488487) | 0 (1 (8) | char munused bit-field bits | |||
| 41 (328) | 3 (24) | data member alignment padding | 62 61 (496488) | 2 1 (168) | data member alignment paddingchar n | |||
| 44 (352) | 4 (32) | unsigned j : 80 | 64 62 (512496) | 8 2 (6416) | double ndata member alignment padding | |||
| 48 (384) | 6 (48) | extended bit-field size padding | 72 64 (576512) | 24 8 (19264) | class alignment paddingdouble o | |||
| 54 (432) | 2 (16) | alignment padding | 72 (576) | 24 (192) | class alignment padding |
Compliant Solution
Due to the complexity of the data structure, this compliant solution serializes the object data before copying it to an untrusted context instead of attempting to account for all of the padding bytes manually:
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <cstddef>
#include <cstring>
class base {
public:
virtual ~base() = default;
};
class test : public virtual base {
alignas(32) double h;
char i;
unsigned j : 80;
protected:
unsigned k;
unsigned l : 4;
unsigned short m : 3;
public:
char mn;
double no;
test(double h, char i, unsigned j, unsigned k, unsigned l, unsigned short m,
unsigned j, unsigned k, unsigned l, char mn, double no) :
h(h), i(i), j(j), k(k), l(l), m(m), n(n), o(o) {}
virtual void foo();
bool serialize(unsigned char *buffer, std::size_t &size) {
if (size < sizeof(test)) {
return false;
}
std::size_t offset = 0;
std::memcpy(buffer + offset, &h, sizeof(h));
offset += sizeof(h);
std::memcpy(buffer + offset, &i, sizeof(i));
offset += sizeof(i);
unsigned loc_j = j; // Only sizeof(unsigned) bits are valid, so this is not narrowing.
std::memcpy(buffer + offset, &loc_j, sizeof(loc_j));
offset += sizeof(loc_j);
std::memcpy(buffer + offset, &k, sizeof(k));
offset += sizeof(loc_j)k);
unsigned char loc_l = l & 0b1111;
std::memcpy(buffer + offset, &kloc_l, sizeof(kloc_l));
offset += sizeof(kloc_l);
unsigned charshort loc_lm = lm & 0b11110b111;
std::memcpy(buffer + offset, &loc_lm, sizeof(loc_lm));
offset += sizeof(loc_lm);
std::memcpy(buffer + offset, &mn, sizeof(mn));
offset += sizeof(mn);
std::memcpy(buffer + offset, &no, sizeof(no));
offset += sizeof(no);
size -= offset;
return true;
}
};
// 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{0.0, 1, 2, 3, 4, 5, 6, 7.0};
// May be larger than strictly needed, will be updated by
// calling serialize() to the size of the buffer remaining.
std::size_t size = sizeof(arg);
unsigned char buf[size];
if (arg.serialize(buf, size)) {
copy_to_user(usr_buf, buf, sizeof(test) - size);
} else {
// Handle error
}
} |
...