Using structures

If you use structures in your C/C++ code, you should minimize the consequences of a buffer overflow in any fixed-length buffer or array elements within a structure (but not in referenced buffers or arrays). The compiler can't rearrange structures, so buffer protection mechanisms such as stack canaries aren't sufficient to protect against overflows.

Best practices

If you are using structures that contain fixed-length buffers or arrays designed to receive data that's controlled or influenced by a user, you should follow these best practices:

Group buffers and arrays at the end of the structure.

Declare local structure variables after local buffers but before any other local variables.

Declare global structure variables after any other global variables and before any global buffers and arrays.

Ensure that pointers to structures do not need any special considerations.

Where practical, minimize the number of local variables that are cast as structures with buffers and arrays as elements. This best practice does not apply to elements in a structure that are pointers to arrays or buffers.

To illustrate these best practices, consider the following code sample:

Not applicable

Not applicable

typedef struct objective {
     struct tm start;
     struct tm end;
     char owner[64];       // These will overflow away
     char objective[64];   // from the other elements
} objective;

If you use this structure as a local variable, then you should order the local variables as follows, with the objective declared first.

Not applicable

Not applicable

bool objective_create() {
     char exampleBuffer[20];
     objective newObjective; // success cannot be modified by an
     bool success;           // overflow in newObjective
     success = get_user_string(USER_NAME, &newObjective.owner);
     success = get_user_string(OBJECTIVE_NAME, 
     success = get_user_date(START_DATE, &newObjective.start);
     success = get_user_date(END_DATE, &newObjective.end);
     // Even if the above get_user_string() and get_user_date()
     // functions are susceptible to buffer overflows, they
     // can only modify function metadata stored on the
     // stack (such as the Saved Return Address). This should
     // be mitigated by the use of stack canaries.
     return add_objective(&newObjective);

The direction of memory layout is opposite for global variables (generally, stacks expand down, while global memory expands up). The following code sample shows how to structure your code if you want to create a global objective:

Not applicable

Not applicable

#include "user.h"
#include "objective.h"      // Contains the objective related
                            // definitions

uint32_t g_objectivesMet;   // An overflow in g_CurrentObjective
bool g_objectiveRunning;    // cannot overwrite any of the other
user g_User;                // global variables
objective g_CurrentObjective;        

Structures in C/C++ and variable reordering

In C/C++, structures are aggregate types. The compiler can't reorder the elements of a structure. Similarly, arguments that are passed to a function can't be rearranged because this rearrangement would change the type of the function.

GCC and QNX QCC implement stack buffer protection systems to mitigate the security risk from stack-based buffer overflows. This approach involves using various techniques, such as stack canaries and address space layout randomization (ASLR), using hardware features such as No eXecute (NX), and reordering local variables in a function.

Variable reordering is used to isolate stack-based buffers as much as possible. If an overflow occurs, the stack canary is corrupted before function arguments or other local variables on the stack are corrupted. To reorder variables, allocate local stack space to all buffers directly below the stack canary, followed by other variables and copies of arguments that are output pointers.

Consider the following code sample:

Not applicable

Not applicable

bool check_domain(net_info info) {
     net_info tempInfo;
     bool status;
     char name[128];
     char domain[128];
     char answer[128];
     status = res_querydomain(&name, &domain, 
                              C_IN, T_PTR,
                              &answer, sizeof(answer));
     return status;

This code results in the stack frame shown in the diagram below.

Diagram showing a stack frame with a stack canary.

Although variables that are defined as structures can be reordered on the stack, the elements of the structure can't. You must take care when using structures that contain buffers. Ideally, buffers should be collected at the end of the structure. By doing so, they are stored higher in memory and can't corrupt other elements in the structure.

Not applicable

Not applicable

typedef struct _user_info {
     uint32_t cookie;
     tm lastLogon;
     uint32_t failedLogons;
     char name[256];
     char domain[256];
     char password[256];
} user_info;

This diagram illustrates how the above structure is arranged in memory.

Diagram showing a structure's layout in memory.

Placing buffers at the end of a structure protects the structure from corruption. Placing instances of structures at the top of the list of local variables (after any local stack buffers) protects the rest of the local variables. However, if multiple structures that contain buffers on the stack are used, then the elements of some of them may still be reliably corruptible if there is a buffer overflow.

In the following code sample, any of the elements of user (such as user.cookie or user.password) can be overwritten by a buffer overflow in the character buffers in temp (such as, temp.domain, or temp.password). This situation can occur without corrupting a stack canary.

Not applicable

Not applicable

bool AreUserDetailsCached() {
     user_info user;
     user_info temp;
     bool found = false;

     memset(temp, 0, sizeof(temp));

     while (NextUserInCache(&temp)) {
     return found;

A buffer overflow caused by invalid credentials in the cache can allow a Denial of Service (DoS) attack by modifying the credentials of another user who is trying to log in. This condition would be resolved only when the cache was cleared.

Last modified: 2015-05-07

Got questions about leaving a comment? Get answers from our Disqus FAQ.

comments powered by Disqus