...
As a result of being uninitialized, padding bytes could have random data (sensitive data). This structure could be passed to functions that do not have privilege.
For Exampleexample, There there have been instances in Linux kernel where uninitialized stack bytes are leaked to unprivileged users as a result of copying structures to user space.
Some Compilers compilers do not initialize the padding bytes if all the members of the structure are initialized.
Noncompliant Code Example
In this noncompliant code example, the padding bytes after char b are left uninitialized and are leakedmay not be initialized.
| Code Block | ||
|---|---|---|
| ||
#include <stddef.h>
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) {
struct test arg = {.a=1,.b=2,.c=3};
/* ... perform operations on arg ... */
/* copy arg to user space */
copy_to_user(usr_buf, &arg, sizeof(arg));
/* ... */
}
|
The padding bytes could can be explicitly initialized using memset to zero as shown below.by calling memset():
| Code Block | ||
|---|---|---|
| ||
#include <stddef.h>
#include <string.h>
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) {
struct test arg;
/* initializes all bytes (including padding bytes) of the struct to zero */
memset(&arg, 0, sizeof(arg));
arg.a = 1;
arg.b = 2;
arg.c = 3;
/* ... perform operations on arg ... */
/* copy arg to user space */
copy_to_user(usr_buf, &arg, sizeof(arg));
/* ... */
}
|
Here, the compiler could implement arg.b = 2 in the following way,
- setting the low byte of a 32-bit register to 2, leaving the high bytes unchanged,
- storing all 32 bits of the register into memory,
...
So the above example could leak data under some specific compiler.
Compliant Solution
...
(Structure Packing - GCC)
GCC allows specifying attributes of variables and structures using the keyword __attribute__((__packed__)).
This means that that GCC will not add any padding bytes (for memory alignment) and make variables or fields immediately next to each other.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
struct test{
int a;
char b;
int c;
} __attribute__((__packed__));
/* ... safely copy bytes to user space ... */
extern int copy_to_user(void *dest, void *src, size_t size);
void do_stuff(void *usr_buf) {
struct test arg = {.a=1,.b=2,.c=3};
/* ... perform operations on arg ... */
/* copy arg to user space */
copy_to_user(usr_buf, &arg, sizeof(arg));
/* ... */
}
|
Compliant Solution
...
(Structure Packing - MSVC)
In case of MSVC, use Microsoft Visual Studio supports the #pragma pack() instead of the __packed__ attribute to ensure no padding bytes are added.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
#pragma pack(1) /* 1 byte */
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) {
struct test arg = {.a=1,.b=2,.c=3};
/* ... perform operations on arg ... */
/* copy arg to user space */
copy_to_user(usr_buf, &arg, sizeof(arg));
/* ... */
}
|
The pack takes pragma takes effect at the first struct first struct declaration after the pragma is seen. The The alignment of a member will be on a boundary that is a multiple of 1 byte.
Compliant Solution
...
(Adding Padding bytes)
The padding bytes could be explicitly declared. This should be done carefully based on the memory architecture.
...
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
struct test{
int a;
char b;
char padding_1, padding_2, padding_3;
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) {
/* make sure c is the next byte after the last padding byte */
static_assert(offsetof(struct test, c) == \
offsetof(struct test, padding_3) + 1, \
"Error: not compiling for IA-32");
struct test arg = {.a=1,.b=2,.c=3};
arg.padding_1 = 0;
arg.padding_2 = 0;
arg.padding_3 = 0;
/* ... perform operations on arg ... */
/* copy arg to user space */
copy_to_user(usr_buf, &arg, sizeof(arg));
/* ... */
}
|
The staticThe static_assert() macro macro, a feature of the C1X standard, 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 output.
...
The explicit insertion of the padding bytes into the struct should ensure that no "invisible" padding bytes are added in by the compiler, and thus the expression in static_assert should be true.
Explicitly,
| Code Block |
|---|
offsetof(struct test, c ) = 8 |
and
| Code Block |
|---|
offsetof(struct test, padding_3 ) = 7, |
and thus, consequently the expression should return 1.
...
This approach ensures that no padding bytes are inserted.
Compliant Solution
...
If memset of the original structure to zero (as mentioned in the Non-Compliant example 2) does not work under some compilers, then copy the original structure to an unsigned char memory and pass that memory to the user as shown below.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
#include <string.h>
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) {
struct test arg = {.a=1,.b=2,.c=3};
unsigned char r[sizeof(arg)];
/* ... perform operations on arg ... */
/* just before passing arg to the function */
memset(r, 0, sizeof(r));
memset(r+offsetof(struct test,a), arg.a, sizeof(arg.a));
memset(r+offsetof(struct test,b), arg.b, sizeof(arg.b));
memset(r+offsetof(struct test,c), arg.c, sizeof(arg.c));
/* now pass r to the function */
copy_to_user(usr_buf, r, sizeof(r));
/* ... */
}
|
This ensures that no uninitialized padding bytes are copied to unprivileged users.
Risk Assessment
Recommendation | Severity | Likelihood | Remediation Cost | Priority | Level |
|---|---|---|---|---|---|
DCL39-C | low | unlikely | medium | P2 | L3 |
...