Main Content

CWE Rule 825

Expired Pointer Dereference

Since R2023a

Description

Rule Description

The program dereferences a pointer that contains a location for memory that was previously valid, but is no longer valid.

Polyspace Implementation

The rule checker checks for these issues:

  • Accessing object with temporary lifetime

  • Deallocation of previously deallocated pointer

  • Environment pointer invalidated by previous operation

  • Pointer or reference to stack variable leaving scope

  • Use of automatic variable as putenv-family function argument

  • Use of previously freed pointer

Examples

expand all

Issue

This issue occurs when you attempt to read from or write to an object with temporary lifetime that is returned by a function call. In a structure or union returned by a function, and containing an array, the array members are temporary objects. The lifetime of temporary objects ends:

  • When the full expression or full declarator containing the call ends, as defined in the C11 Standard.

  • After the next sequence point, as defined in the C90 and C99 Standards. A sequence point is a point in the execution of a program where all previous evaluations are complete and no subsequent evaluation has started yet.

For C++ code, Accessing object with temporary lifetime raises a defect only when you write to an object with a temporary lifetime.

If the temporary lifetime object is returned by address, no defect is raised.

Risk

Modifying objects with temporary lifetime is undefined behavior and can cause abnormal program termination and portability issues.

Fix

Assign the object returned from the function call to a local variable. The content of the temporary lifetime object is copied to the variable. You can now modify it safely.

Example — Modifying Temporary Lifetime Object Returned by Function Call
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

/* func_temp() returns a struct value containing
* an array with a temporary lifetime.
*/
int func(void) {
 
/*Writing to temporary lifetime object is
 undefined behavior
 */
    return ++(func_temp().a[0]);  //Noncompliant
}

void main(void) {
    (void)func();
}

In this example, func_temp() returns by value a structure with an array member a. This member has temporary lifetime. Incrementing it is undefined behavior.

Correction — Assign Returned Value to Local Variable Before Writing

One possible correction is to assign the return of the call to func_temp() to a local variable. The content of the temporary object a is copied to the variable, which you can safely increment.

 #include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>

#define SIZE6 6

struct S_Array
{
    int t;
    int a[SIZE6];
};

struct S_Array func_temp(void);

int func(void) {

/* Assign object returned by function call to 
 *local variable
 */
    struct S_Array s = func_temp(); 

/* Local variable can safely be
 *incremented
 */
    ++(s.a[0]);                                           
    return s.a[0];
}

void main(void) {
    (void)func();
}
Issue

This issue occurs when a block of memory is freed more than once using the free function without an intermediate allocation.

Risk

When a pointer is allocated dynamic memory with malloc, calloc or realloc, it points to a memory location on the heap. When you use the free function on this pointer, the associated block of memory is freed for reallocation. Trying to free this block of memory can result in a segmentation fault.

Fix

The fix depends on the root cause of the defect. See if you intended to allocate a memory block to the pointer between the first deallocation and the second. Otherwise, remove the second free statement.

As a good practice, after you free a memory block, assign the corresponding pointer to NULL. Before freeing pointers, check them for NULL values and handle the error. In this way, you are protected against freeing an already freed block.

Example — Deallocation of Previously Deallocated Pointer Error
#include <stdlib.h>

void allocate_and_free(void)
{

    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) return;

    *pi = 2;
    free(pi);
    free (pi);  //Noncompliant
    /* Defect: pi has already been freed */
}

The first free statement releases the block of memory that pi refers to. The second free statement on pi releases a block of memory that has been freed already.

Correction — Remove Duplicate Deallocation

One possible correction is to remove the second free statement.

#include <stdlib.h>

void allocate_and_free(void)
{

    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) return;

    *pi = 2;
    free(pi);
    /* Fix: remove second deallocation */
 }
Example — Freeing Pointer Previously Reallocated With Possibly Zero Size
#include <stdlib.h>
  
void reshape(char *buf, size_t size) {
  char *reshaped_buf = (char *)realloc(buf, size);
  if (reshaped_buf == NULL) {
    free(buf);  //Noncompliant
  }
}

In this example, the argument size of the reshape() function can be zero and result in a zero-size reallocation with realloc(). In some implementations such as the GNU® library, zero-size reallocations free the memory leading to a double free defect.

Correction – Guard Against Zero-Size Reallocations

One possible correction is to check size argument of realloc() for zero values before use. If the size argument is zero, you can simply free the memory instead of reallocating it.

#include <stdlib.h>

void reshape(char *buf, size_t size) {
  if (size != 0) {
    char *reshaped_buf = (char *)realloc(buf, size);
    if (reshaped_buf == NULL) {
      free(buf);
    }
  }
  else {
    free(buf);
  }

}
Issue

This issue occurs when you use the third argument of main() in a hosted environment to access the environment after an operation modifies the environment. In a hosted environment, many C implementations support the nonstandard syntax:

main (int argc, char *argv[], char *envp[])
A call to a setenv or putenv family function modifies the environment pointed to by *envp.

Risk

When you modify the environment through a call to a setenv or putenv family function, the environment memory can potentially be reallocated. The hosted environment pointer is not updated and might point to an incorrect location. A call to this pointer can return unexpected results or cause an abnormal program termination.

Fix

Do not use the hosted environment pointer. Instead, use global external variable environ in Linux®, _environ or _wenviron in Windows®, or their equivalent. When you modify the environment, these variables are updated.

Example — Access Environment Through Pointer envp
#include <stdio.h>
#include <stdlib.h>

extern int check_arguments(int argc, char **argv, char **envp);
extern void use_envp(char **envp);

/* envp is from main function */
int func(char **envp) 
{
    /* Call to setenv may cause environment
     *memory to be reallocated 
     */
    if (setenv(("MY_NEW_VAR"),("new_value"),1) != 0) 
    {
        /* Handle error */
        return -1;
    }
    /* envp not updated after call to setenv, and may
     *point to incorrect location.
     **/
    if (envp != ((void *)0)) {  //Noncompliant
        use_envp(envp);
/* No defect on second access to
*envp because defect already raised */
    }
    return 0;
}

void  main(int argc, char **argv, char **envp)
{
    if (check_arguments(argc, argv, envp))
    {
        (void)func(envp);
    }
}

In this example, envp is accessed inside func() after a call to setenv that can reallocate the environment memory. envp can point to an incorrect location because it is not updated after setenv modifies the environment. No defect is raised when use_envp() is called because the defect is already raised on the previous line of code.

Correction — Use Global External Variable environ

One possible correction is to access the environment by using a variable that is always updated after a call to setenv. For instance, in the following code, the pointer envp is still available from main(), but the environment is accessed in func() through the global external variable environ.

#include <stdio.h>
#include <stdlib.h>
extern char **environ;

extern int check_arguments(int argc, char **argv, char **envp);
extern void use_envp(char **envp);

int func(void)
{
    if (setenv(("MY_NEW_VAR"), ("new_value"),1) != 0) {
        /* Handle error */
        return -1;
    }
  /* Use global external variable environ
   *which is always updated after a call to setenv */
    
    if (environ != NULL) { 
        use_envp(environ);
    }
    return 0;
}

void  main(int argc, char **argv, char **envp)
{
    if (check_arguments(argc, argv, envp))
    {
        (void)func();
    }
} 
Issue

This issue occurs when a pointer or reference to a local variable leaves the scope of the variable. For instance:

  • A function returns a pointer to a local variable.

  • A function performs the assignment globPtr = &locVar. globPtr is a global pointer variable and locVar is a local variable.

  • A function performs the assignment *paramPtr = &locVar. paramPtr is a function parameter that is, for instance, an int** pointer and locVar is a local int variable.

  • A C++ method performs the assignment memPtr = &locVar. memPtr is a pointer data member of the class the method belongs to. locVar is a variable local to the method.

  • (C++11 and later) A function returns a lambda expression object that captures local variables of the function by reference.

The defect also applies to memory allocated using the alloca function. The defect does not apply to static, local variables. Polyspace® assumes that the local objects within a function definition are in the same scope.

Risk

Local variables are allocated an address on the stack. Once the scope of a local variable ends, this address is available for reuse. Using this address to access the local variable value outside the variable scope can cause unexpected behavior.

If a pointer to a local variable leaves the scope of the variable, Polyspace Bug Finder™ highlights the defect. The defect appears even if you do not use the address stored in the pointer. For maintainable code, it is a good practice to not allow the pointer to leave the variable scope. Even if you do not use the address in the pointer now, someone else using your function can use the address, causing undefined behavior.

Fix

Do not allow a pointer or reference to a local variable to leave the variable scope.

Example — Pointer to Local Variable Returned from Function
void func2(int *ptr) {
    *ptr = 0;
}

int* func1(void) {
    int ret = 0;  //Noncompliant
    return &ret ;
}
void main(void) {
    int* ptr = func1() ;
    func2(ptr) ;
}

In this example, func1 returns a pointer to local variable ret.

In main, ptr points to the address of the local variable. When ptr is accessed in func2, the access is illegal because the scope of ret is limited to func1,

Example — Pointer to Local Variable Escapes Through Lambda Expression
auto createAdder(int amountToAdd) {
  int addThis = amountToAdd;  //Noncompliant
  auto adder = [&] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}

In this example, the createAdder function defines a lambda expression adder that captures the local variable addThis by reference. The scope of addThis is limited to the createAdder function. When the object returned by createAdder is called, a reference to the variable addThis is accessed outside its scope. When accessed in this way, the value of addThis is undefined.

Correction – Capture Local Variables by Copy in Lambda Expression Instead of Reference

If a function returns a lambda expression object, avoid capturing local variables by reference in the lambda object. Capture the variables by copy instead.

Variables captured by copy have the same lifetime as the lambda object, but variables captured by reference often have a smaller lifetime than the lambda object itself. When the lambda object is used, these variables accessed outside scope have undefined values.

auto createAdder(int amountToAdd) {
  int addThis = amountToAdd;
  auto adder = [=] (int initialAmount) {
      return (initialAmount + addThis);
  };
  return adder;
}
 
void func() {
  auto AddByTwo = createAdder(2);
  int res = AddByTwo(10);
}
Issue

This issue occurs when the argument of a putenv-family function is a local variable with automatic duration.

Risk

The function putenv(char *string) inserts a pointer to its supplied argument into the environment array, instead of making a copy of the argument. If the argument is an automatic variable, its memory can be overwritten after the function containing the putenv() call returns. A subsequent call to getenv() from another function returns the address of an out-of-scope variable that cannot be dereferenced legally. This out-of-scope variable can cause environment variables to take on unexpected values, cause the program to stop responding, or allow arbitrary code execution vulnerabilities.

Fix

Use setenv()/unsetenv() to set and unset environment variables. Alternatively, use putenv-family function arguments with dynamically allocated memory, or, if your application has no reentrancy requirements, arguments with static duration. For example, a single thread execution with no recursion or interrupts does not require reentrancy. It cannot be called (reentered) during its execution.

Example — Automatic Variable as Argument of putenv()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE1024 1024

void func(int var)
{
    char env[SIZE1024];
    int retval = sprintf(env, "TEST=%s", var ? "1" : "0");
    if (retval <= 0) {
        /* Handle error */
    }
	/* Environment variable TEST is set using putenv().
	The argument passed to putenv is an automatic variable. */
    retval = putenv(env);   //Noncompliant
    if (retval != 0) {
        /* Handle error */
    }
}
              

In this example, sprintf() stores the character string TEST=var in env. The value of the environment variable TEST is then set to var by using putenv(). Because env is an automatic variable, the value of TEST can change once func() returns.

Correction — Use static Variable for Argument of putenv()

Declare env as a static-duration variable. The memory location of env is not overwritten for the duration of the program, even after func() returns.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE1024 1024 
void func(int var)
{
	/* static duration variable */
    static char env[SIZE1024]; 
    int retval = sprintf(env,"TEST=%s", var ? "1" : "0");
    if (retval <= 0) {
        /* Handle error */
    }
	
	/* Environment variable TEST is set using putenv() */
    retval=putenv(env);   
	if (retval != 0) {
        /* Handle error */
    }
}
Correction — Use setenv() to Set Environment Variable Value

To set the value of TEST to var, use setenv().

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE1024 1024 

void func(int var)
{
	/* Environment variable TEST is set using setenv() */
    int retval = setenv("TEST", var ? "1" : "0", 1); 
	
    if (retval != 0) {
        /* Handle error */
    }
}
Issue

This issue occurs when you access a block of memory after freeing the block using the free function.

Risk

When a pointer is allocated dynamic memory with malloc, calloc or realloc, it points to a memory location on the heap. When you use the free function on this pointer, the associated block of memory is freed for reallocation. Trying to access this block of memory can result in unpredictable behavior or even a segmentation fault.

Fix

The fix depends on the root cause of the defect. See if you intended to free the memory later or allocate another memory block to the pointer before access.

As a good practice, after you free a memory block, assign the corresponding pointer to NULL. Before dereferencing pointers, check them for NULL values and handle the error. In this way, you are protected against accessing a freed block.

Example — Use of Previously Freed Pointer Error
#include <stdlib.h>
#include <stdio.h>
 int increment_content_of_address(int base_val, int shift)
   { 
    int j;
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) return 0;

    *pi = base_val;
    free(pi);

    j = *pi + shift; //Noncompliant
    /* Defect: Reading a freed pointer */
 
    return j;
   }

The free statement releases the block of memory that pi refers to. Therefore, dereferencingpi after the free statement is not valid.

Correction — Free Pointer After Use

One possible correction is to free the pointer pi only after the last instance where it is accessed.

#include <stdlib.h>

int increment_content_of_address(int base_val, int shift)
{
    int j;
    int* pi = (int*)malloc(sizeof(int));
    if (pi == NULL) return 0;

    *pi = base_val;

    j = *pi + shift;
    *pi = 0;

    /* Fix: The pointer is freed after its last use */
    free(pi);               
    return j;
}

Check Information

Category: Pointer Issues

Version History

Introduced in R2023a