Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

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 6The C standard, Section 6.2.6.1 , paragraph 6,[ISO/IEC 9899:2011], states:

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.4251)

4251) Thus, for example, structure assignment may be implemented element-at-a-time or via memcpyan automatic variable can be initialized to a trap representation without causing undefined behavior, but the value of the variable cannot be used until a proper value is stored in it.

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 the Linux kernel where uninitialized stack bytes are leaked to unprivileged users as a result of copying structures to user space.

...

In this noncompliant code example, the padding bytes after char b may not be initialized.

Code Block
bgColor#FFCCCC
langc

#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));

  /* ... */
}

...

Code Block
bgColor#FFCCCC
langc

#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));

  /* ... */
}

...

  • 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,

Thus leaking thereby leaking stack bytes to an unprivileged user. This may not be the case with all compilers. But the , but compilers are free to implement it in their own way.So the above example , so this 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
bgColor#CCCCFF
borderStylesolid

#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)

Microsoft Visual Studio supports the #pragma pack() instead of the __packed__ attribute to ensure no padding bytes are added.

Code Block
bgColor#CCCCFF
borderStylesolid

#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 padding bytes could be explicitly declared. This It should be done carefully based on carefully on the basis of the memory architecture.

The following solution assumes it will be run on an IA-32 architecture.

Code Block
bgColor#CCCCFF
borderStylesolid

#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 static_assert() macro, a feature of the C1X C 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 DCL03-C. Use a static assertion to test the value of a constant expression

For example, in an IA-32 machine, The 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 consequently 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 consequently the expression should return 1.

However, if we're compiling if you are compiling for a different architecture and the compiler adds padding bytes, then the memory would not be contiguous and the expression would return 0.

This approach ensures that no padding bytes are inserted.

Compliant Solution

If setting memset of the original structure to zero (as mentioned in the Non-Compliant noncompliant 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.follows:

Code Block
bgColor#CCCCFF
borderStylesolid

...

#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 code 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

Related Guidelines

ISO/IEC 9899:19992011 Section 6.2.6.1 p6, "General" (para. 6)

Bibliography

...