You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 90 Next »

An object of type void * is a generic data pointer. It can point to any data object. For any incomplete or object type T, C permits implicit conversion from T * to void * or from void * to T *. The standard C library uses void * to declare parameters and return types of functions designed to work for objects of different types. Such is the case with the standard memory allocation functions aligned_alloc(), malloc(), calloc(), and realloc().

For example, C Library declares malloc() as

void *malloc(size_t);

Calling malloc(s) allocates memory for an object whose size is s and returns either a null pointer or a pointer to the allocated memory. A program can implicitly convert the pointer that malloc() returns into a different pointer type.

Noncompliant Code Example

The argument to malloc() can be any value of (unsigned) type size_t. If the program uses the allocated storage to represent an object (possibly an array) whose size is greater than the requested size, the behavior is undefined. The implicit pointer conversion lets this slip by without complaint from the compiler.

Consider the following example:

#include <stdlib.h>

typedef struct gadget gadget;
struct gadget {
  int i;
  double d;
};

typedef struct widget widget;
struct widget {
  char c[10];
  int i;
  double d;
};

widget *p;

/* ... */

p = malloc(sizeof(gadget)); /* imminent problem */
if (p != NULL) {
  p->i = 0;               /* undefined behavior */
  p->d = 0.0;             /* undefined behavior */
}

An implementation may add padding to a gadget or widget so that sizeof(gadget) equals sizeof(widget), but this is highly unlikely. More likely, sizeof(gadget) is less than sizeof(widget). In that case,

p = malloc(sizeof(gadget)); /* imminent problem */

quietly assigns p to point to storage too small for a widget. The subsequent assignments to p->i and p->d will most likely produce memory overruns.

Casting the result of malloc() to the appropriate pointer type enables the compiler to catch subsequent inadvertent pointer conversions. When allocating individual objects, the "appropriate pointer type" is a pointer to the type argument in the sizeof expression passed to malloc().

In this code example, malloc() allocates space for a gadget, and the cast immediately converts the returned pointer to a gadget *:

widget *p;

/* ... */

p = (gadget *)malloc(sizeof(gadget)); /* invalid assignment */

This lets the compiler detect the invalid assignment because it attempts to convert a gadget * into a widget *.

Compliant Solution (Hand-Coded)

This compliant solution repeats the same type in the sizeof expression and the pointer cast:

widget *p;

/* ... */

p = (widget *)malloc(sizeof(widget));

Compliant Solution (Macros)

Repeating the same type in the sizeof expression and the pointer cast is easy to do but still invites errors. Packaging the repetition in a macro, such as

#define MALLOC(type) ((type *)malloc(sizeof(type)))

further reduces the possibility of error.

widget *p;

/* ... */

p = MALLOC(widget);     /* OK */
if (p != NULL) {
  p->i = 0;           /* OK */
  p->d = 0.0;         /* OK */
}

Here, the entire allocation expression (to the right of the assignment operator) allocates storage for a widget and returns a widget *. If p were not a widget *, the compiler would complain about the assignment.

When allocating an array with N elements of type T, the appropriate type in the cast expression is still T *, but the argument to malloc() should be of the form N * sizeof(T). Again, packaging this form as a macro, such as

#define MALLOC_ARRAY(number, type) \
    ((type *)malloc((number) * sizeof(type)))

reduces the chance of error in an allocation expression.

enum { N = 16 };
widget *p;

/* ... */

p = MALLOC_ARRAY(N, widget);    /* OK */

A small collection of macros can provide secure implementations for common uses for the standard memory allocation functions. The omission of a REALLOC() macro is intentional. (See MEM08-C. Use realloc() only to resize dynamically allocated arrays.)

/* allocates a single object using malloc(). */
#define MALLOC(type) ((type *)malloc(sizeof(type)))

/* allocates an array of objects using malloc(). */
#define MALLOC_ARRAY(number, type) \
    ((type *)malloc((number) * sizeof(type)))

/* allocates a single object with a flexible
 * array member using malloc(). */
#define MALLOC_FLEX(stype, number, etype) \
    ((stype *)malloc(sizeof(stype) \
    + (number) * sizeof(etype)))

/* allocates an array of objects using calloc(). */
#define CALLOC(number, type) \
    ((type *)calloc(number, sizeof(type)))

/* reallocates an array of objects using realloc(). */
#define REALLOC_ARRAY(pointer, number, type) \
    ((type *)realloc(pointer, (number) * sizeof(type)))

/* reallocates a single object with a flexible
 * array member using realloc(). */
#define REALLOC_FLEX(pointer, stype, number, etype) \
    ((stype *)realloc(pointer, sizeof(stype) \
    + (number) * sizeof(etype)))

The following is an example:

enum month { Jan, Feb, /* ... */ };
typedef enum month month;

typedef struct date date;
struct date {
  unsigned char dd;
  month mm;
  unsigned yy;
};

typedef struct string string;
struct string {
  size_t length;
  char text[];
};

date *d, *week, *fortnight;
string *name;

d = MALLOC(date);
week = MALLOC_ARRAY(7, date);
name = MALLOC_FLEX(string, 16, char);
fortnight = CALLOC(14, date);

If one or more of the operands to the multiplication operations used in many of these macro definitions can be influenced by untrusted data, these operands should be checked for overflow before the macro is invoked. (See INT32-C. Ensure that operations on signed integers do not result in overflow.)

The use of type-generic function-like macros is an allowed exception (PRE00-EX4) to PRE00-C. Prefer inline or static functions to function-like macros.

Exceptions

MEM02-EX1: Do not immediately cast the results of malloc() when compiling as C90 or earlier because it is possible for the cast to hide a bug in platforms where pointers are larger than ints.  

For example, if stdlib.h is not properly included, compilers will implicitly declare malloc() as int malloc(int).  If the size of int on the platform is 32 bits, but the size of a pointer is 64 bits, the resulting pointer could be truncated due to the implicit declaration of malloc() returning a 32-bit integer.

/* #include <stdlib.h> is missing */
 
int main() {
  int i;
  for (i = 0; i < 100; ++i) {
    char *ptr = (char*)malloc(0x10000000); /* int malloc() assumed */
    *ptr = 'a';
  }
  return 0;
}

When compiled with Microsoft Visual Studio, the above code will eventually cause an access violation when dereferencing ptr in the loop.  Note that this code also does not comply with DCL31-C. Declare identifiers before using them by using an implicit declaration of malloc().

Risk Assessment

Failing to cast the result of a memory allocation function call into a pointer to the allocated type can result in inadvertent pointer conversions. Code that follows this recommendation will compile and execute equally well in C++.

Recommendation

Severity

Likelihood

Remediation Cost

Priority

Level

MEM02-C

low

unlikely

low

P3

L3

Automated Detection

Tool

Version

Checker

Description

Compass/ROSE

 

 

Can detect some violations of this recommendation when checking EXP36-C. Do not convert pointers into more strictly aligned pointer types

ECLAIR1.2funcallsFully implemented

Fortify SCA

5.0

 

Can detect violations of this rule with CERT C Rule Pack

LDRA tool suite

9.7.1

 

 

PRQA QA-C
Unable to render {include} The included page could not be found.
0695Fully implemented

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

 


  • No labels