The C Standard, 6.7.3.2, discusses the layout of structure fields. It specifies that non-bit-field members are aligned in an implementation-defined manner and that there may be padding within or at the end of a structure. Furthermore, initializing the members of the structure does not guarantee initialization of the padding bytes. The C Standard, 6.2.6.1, paragraph 6 [ISO/IEC 9899:2024], states
When a value is stored in an object of structure or union type, including in a member object, the bytes of the object
A compiler might insert padding bytes to a structure to ensure that structure members appear in the correct location. Initializing the members of the structure does not always initialize the padding bytes.
According to ISO/IEC 9899:1999 (C99), in 6.2.6.1, paragraph 6,
When a value is stored in an object of structure or union type, including in a member object, the bytes of the object representation that correspond to any padding bytes take unspecified values .42)
42) Thus, for example, structure assignment may be implemented element-at-a-time or via memcpy.
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 Example, 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 do not initialize the padding bytes if all the members of the structure are initialized.
Noncompliant Code Example
(e.g. structure and union assignment may or may not copy any padding bits).
Additionally, the storage units in which a bit-field resides may also have padding bits. For an object with automatic storage duration, these padding bits do not take on specific values and can contribute to leaking sensitive information.
When passing a pointer to a structure across a trust boundary to a different trusted domain, the programmer must ensure that the padding bytes and bit-field storage unit padding bits of such a structure do not contain sensitive information.
Noncompliant Code Example
This noncompliant code example runs in kernel space and copies data from arg to user space. However, padding bytes may be used within the structure, for example, to ensure the proper alignment of the structure members. These padding bytes may contain sensitive information, which may then be leaked when the data is copied to user spaceIn this example, the padding bytes after char b are left uninitialized and are leaked.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h> struct test { int a; char b; int c; }; /* ... safelySafely 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, sizeofcopy_to_user(usr_buf, &arg, sizeof(arg)); /* ... */ } } |
Noncompliant Code Example (memset())
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; }; /* ... safelySafely 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; /* initializesSet all bytes (including padding bytes) of the struct toto 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)); /* ... */ } } |
However, a conforming compiler is free to Here, the compiler could implement arg.b = 2 in the following way, by setting the low
...
-order bits of a
...
register to 2, leaving the high
...
-order bits unchanged and containing sensitive information. Then the platform copies all register bits into memory, leaving sensitive information in the padding bits. Consequently, this implementation could leak the high-order bits from the register to a user.
Compliant Solution
This compliant solution serializes the structure data before copying it to an untrusted context:
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
#include <string.h>
struct test {
|
Thus leaking stack bytes to unprivileged user.
This may not be the case with all compilers. But the compilers are free to implement it in their own way.
So the above example could leak data under some specific compiler.
Compliant Solution 1(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; /* 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}; /* May ...be performlarger operationsthan onstrictly argneeded ... */ unsigned /* copy arg to user space */ copy_to_user(usr_bufchar buf[sizeof(arg)]; size_t offset = 0; memcpy(buf + offset, &arg.a, sizeof(arg.a)); offset /* ... */ } |
Compliant Solution 2(Structure Packing - MSVC)
In case of MSVC, use #pragma pack() instead of _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));
/* ... */
}
|
pack takes effect at the first struct declaration after the pragma is seen. The alignment of a member will be on a boundary that is a multiple of 1 byte.
Compliant Solution 3(Adding Padding bytes)
The padding bytes could be explicitly declared. This should be done carefully based on the memory architecture.
+= sizeof(arg.a);
memcpy(buf + offset, &arg.b, sizeof(arg.b));
offset += sizeof(arg.b);
memcpy(buf + offset, &arg.c, sizeof(arg.c));
offset += sizeof(arg.c);
/* Set all remaining bytes to zero */
memset(buf + offset, 0, sizeof(arg) - offset);
copy_to_user(usr_buf, buf, offset /* size of info copied */);
} |
This code ensures that no uninitialized padding bytes are copied to unprivileged users. Important: The structure copied to user space is now a packed structure and the copy_to_user() function (or other eventual user) would need to unpack it to recreate the original padded structure.
Compliant Solution (Padding Bytes)
Padding bytes 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:The following solution assumes to be implemented in an IA32 machine.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <assert.h> #include <stddef.h> struct test { int a; char b; char padding_1, padding_2, padding_3; int c; }; /* ...Safely safely copy bytes to user space ... **/ extern int copy_to_user(void *dest, void *src, size_t size); void do_stuff(void *usr_buf) { /* makeEnsure sure c is the next byte after the last padding byte */ static_assert(offsetof(struct test, c) == \ offsetof(struct test, padding_3) + 1, \ "Error:Structure notcontains compiling for IA-32intermediate padding"); struct/* testEnsure argthere = {.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)); /* ... */ } is no trailing padding */ static_assert(sizeof(struct test) == offsetof(struct test, c) + sizeof(int), "Structure contains trailing padding"); struct test arg = {.a = 1, .b = 2, .c = 3}; arg.padding_1 = 0; arg.padding_2 = 0; arg.padding_3 = 0; copy_to_user(usr_buf, &arg, sizeof(arg)); } |
The C Standard static_assert() macro The static_assert() 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. See recommendation (See DCL03-C. Use a static assertion to test the value of a constant expression
For Example, In an IA32 machine,
The padding bytes inserted should ensure that no for more details.) The explicit insertion of the padding bytes into the struct should ensure that no additional padding bytes are added and thus the expression in static_assert by the compiler and consequently both static assertions should be true
i.e.
offsetof(struct test, c ) = 8
offsetof(struct test, padding_3 ) = 7
But, if there is padding bytes added then,
The memory would not be continuous and the expression would return 0.
This approach ensures that no padding bytes are inserted.
Compliant Solution 4
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.
. However, it is necessary to validate these assumptions to ensure that the solution is correct for a particular implementation.
Compliant Solution (Structure Packing—GCC)
GCC allows specifying declaration attributes using the keyword __attribute__((__packed__)). When this attribute is present, the compiler will not add padding bytes for memory alignment unless an explicit alignment specifier for a structure member requires the introduction of padding bytes.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
struct test {
| ||||
| Code Block | ||||
| ||||
#include <stddef.h> #include <string.h> struct test{ int a; char b; int c; } __attribute__((__packed__)); /* ...Safely 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[copy_to_user(usr_buf, &arg, 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
}
|
Compliant Solution (Structure Packing—Microsoft Visual Studio)
Microsoft Visual Studio supports #pragma pack() to suppress padding bytes [MSDN]. The compiler adds padding bytes for memory alignment, depending on the current packing mode, but still honors the alignment specified by __declspec(align()). In this compliant solution, the packing mode is set to 1 in an attempt to ensure all fields are given adjacent offsets:
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
#pragma pack(push, 1) /* 1 byte */
struct test {
int a;
char b;
int c;
};
#pragma pack(pop)
/* 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 = {1, 2, 3};
copy_to_user(usr_buf, &arg, sizeof(arg));
}
|
The pack pragma takes effect at the first struct declaration after the pragma is seen.
Noncompliant Code Example
This noncompliant code example also runs in kernel space and copies data from struct test to user space. However, padding bits will be used within the structure due to the bit-field member lengths not adding up to the number of bits in an unsigned object. Further, there is an unnamed bit-field that causes no further bit-fields to be packed into the same storage unit. These padding bits may contain sensitive information, which may then be leaked when the data is copied to user space. For instance, the uninitialized bits may contain a sensitive kernel space pointer value that can be trivially reconstructed by an attacker in user space.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <stddef.h>
struct test {
unsigned a : 1;
unsigned : 0;
unsigned b : 4;
};
/* 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 = 10 };
copy_to_user(usr_buf, &arg, sizeof(arg));
} |
Compliant Solution
Padding bits can be explicitly declared, allowing the programmer to specify the value of those bits. When explicitly declaring all of the padding bits, any unnamed bit-fields of length 0 must be removed from the structure because the explicit padding bits ensure that no further bit-fields will be packed into the same storage unit.
| Code Block | ||||
|---|---|---|---|---|
| ||||
#include <assert.h>
#include <limits.h>
#include <stddef.h>
struct test {
unsigned a : 1;
unsigned padding1 : sizeof(unsigned) * CHAR_BIT - 1;
unsigned b : 4;
unsigned padding2 : sizeof(unsigned) * CHAR_BIT - 4;
};
/* Ensure that we have added the correct number of padding bits. */
static_assert(sizeof(struct test) == sizeof(unsigned) * 2,
"Incorrect number of padding bits for type: unsigned");
/* 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, .padding1 = 0, .b = 10, .padding2 = 0 };
copy_to_user(usr_buf, &arg, sizeof(arg));
} |
This solution is not portable, however, because it depends on the implementation and target memory architecture. The explicit insertion of padding bits into the struct should ensure that no additional padding bits are added by the compiler. However, it is still necessary to validate these assumptions to ensure that the solution is correct for a particular implementation. For instance, the DEC Alpha is an example of a 64-bit architecture with 32-bit integers that allocates 64 bits to a storage unit.
In addition, this solution assumes that there are no integer padding bits in an unsigned int. The portable version of the width calculation from INT35-C. Use correct integer precisions cannot be used because the bit-field width must be an integer constant expression.
From this situation, it can be seen that special care must be taken because no solution to the bit-field padding issue will be 100% portable.
Risk Assessment
Padding units might contain sensitive data because the C Standard allows any padding to take unspecified values. A pointer to such a structure could be passed to other functions, causing information leakage.
Rule | Severity | Likelihood | Detectable | Repairable | Priority | Level |
|---|---|---|---|---|---|---|
DCL39-C | Low | Unlikely | No | Yes | P2 | L3 |
Automated Detection
Tool | Version | Checker | Description | ||||||
|---|---|---|---|---|---|---|---|---|---|
| Astrée |
| function-argument-with-padding | Partially checked | ||||||
| Axivion Bauhaus Suite |
| CertC-DCL39 | Detects composite structures with padding, in particular those passed to trust boundary routines. | ||||||
| CodeSonar |
| MISC.PADDING.POTB | Padding Passed Across a Trust Boundary | ||||||
| Cppcheck Premium |
| premium-cert-dcl39-c | |||||||
| Helix QAC |
| DF4941, DF4942, DF4943 | Fully implemented | ||||||
| Klocwork |
| PORTING.STORAGE.STRUCT | Fully implemented | ||||||
| Parasoft C/C++test |
| CERT_C-DCL39-a | A pointer to a structure should not be passed to a function that can copy data to the user space | ||||||
| CERT C: Rule DCL39-C | Checks for information leak via structure padding | |||||||
| RuleChecker |
| function-argument-with-padding | Partially checked | ||||||
| Security Reviewer - Static Reviewer |
| C20, C21,C22, C23, C25 | Fully implemented |
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
Key here (explains table format and definitions)
Taxonomy | Taxonomy item | Relationship |
|---|---|---|
| CERT C Secure Coding Standard | DCL03-C. Use a static assertion to test the value of a constant expression | Prior to 2018-01-12: CERT: Unspecified Relationship |
Bibliography
| [ISO/IEC 9899:2024] | 6.2.6.1, "General" 6.7.3.2, "Structure and Union Specifiers" |
| [Graff 2003] | |
| [Sun 1993] |
...
...
DCL39-C
...
low
...
unlikely
...
medium
...
P2
...
Related Guidelines
ISO/IEC 9899:1999 Section 6.2.6.1 p6
Bibliography
DCL20-C. Always specify void even if a function accepts no arguments 02. Declarations and Initialization (DCL)