Main Content

Illegally dereferenced pointer

Pointer is dereferenced outside bounds

Description

This check on a pointer dereference determines whether the pointer is NULL, points outside its bounds or points to dynamically allocated memory that was later deallocated. The check occurs only when you dereference a pointer and not when you assign an address to the pointer, reassign to another pointer or pass the pointer to a function.

The check message shows you the points-to size of the pointer, the pointer offset and the allocated buffer size in bytes. A pointer points outside its bounds when the sum of the points-to size and the offset exceeds the buffer size.

  • Points-to size: This is the number of bytes accessed when you dereference the pointer. For instance, if you dereference an int* pointer, you access 4 bytes on most common targets.

  • Buffer size: When you assign an address to a pointer, a block of memory is allocated to the pointer. You cannot access memory beyond that block using the pointer. The size of this block is the buffer size. For instance, if you assign the address of a char variable to a pointer, the allocated buffer size is 1 byte on most common targets.

    Sometimes, instead of a definite value, the size can be a range. For instance, if you create a buffer dynamically using malloc with an unknown input for the size, Polyspace® assumes that the array size can take the full range of values allowed by the input data type.

  • Offset: You can move a pointer within the allocated memory block by using pointer arithmetic. The difference between the initial location of the pointer and its current location is the offset.

    Sometimes, instead of a definite value, the offset can be a range. For instance, if you access an array in a loop, the offset changes value in each loop iteration and takes a range of values throughout the loop.

For instance, if the pointer points to an array:

  • The buffer size is the array size.

  • The offset is the difference between the beginning of the array and the current location of the pointer.

Buffer size is size of allocated buffer. Offset is difference between initial pointer location and pointer location when dereferenced.

The check uses your specifications for Target processor type (-target) to determine sizes of data types.

Examples

expand all

int main() {
    short x=0; 
    int *ptr = (int *) &x;
    *ptr = 2; 
    return 0;
}

In this example, the variable x has the data type short. This data type corresponds to a buffer size of 2 bytes on most targets.

However, the address of this buffer is cast to an int* pointer, ptr. An int* pointer points to a buffer of size 4 bytes on most targets. Dereferencing ptr involves accessing those 4 bytes. Since the original allocation was 2 bytes only, the pointer dereference leads to an access outside the allowed bounds. Therefore, the Illegally dereferenced pointer check shows a red error.

Result Details: The check message confirms that the pointer points to 4 bytes at offset 0 in buffer of 2 bytes, so is outside bounds:

  • Points-to size is 4 bytes because the pointer ptr points to int.

  • Buffer size is 2 bytes because the buffer size is based on the type of the variable pointed to, that is, x. The type of x is short.

  • Offset is 0 because the pointer points to the beginning of the buffer. No pointer arithmetic is involved.

Note that the sizes of data types are target-dependent. The sizes in this example describe most common targets.

#define Size 1024

int input(void);

void main() {
    int arr[Size];
    int *p = arr;

    for (int index = 0; index < Size ; index++, p++){
        *p = input();
    }
    *p = input();
}

In this example:

  • Before the for loop, p points to the beginning of the array arr.

  • After the for loop, p points outside the array.

The Illegally dereferenced pointer check on dereference of p after the for loop shows a red error.

Result Details: The check message states that the pointer points to 4 bytes at offset 4096 in buffer of 4096 bytes, so is outside bounds:

  • Points-to size is 4 bytes because the pointer p points to int.

  • Buffer size is 4096 bytes because the buffer size is the size of the array times the size of an individual array element type (int), that is 1024 * 4 bytes.

  • Offset is 4096 bytes because the pointer has moved across the array because of the operation p++ and now points 1024*4 bytes away from the beginning of the buffer.

Note that the sizes of data types are target-dependent. The sizes in this example describe most common targets.

Correction — Remove illegal dereference

One possible correction is to remove the illegal dereference of p after the for loop.

#define Size 1024

int input(void);

void main() {
    int arr[Size];
    int *p = arr;
 	
    for (int index = 0; index < Size ; index++, p++) {
        *p = input();
    }
}    
typedef struct S {
    int f1;
    int f2;
    int f3;
} S;

void Initialize(int *ptr) {
    *ptr = 0;
    *(ptr+1) = 0;
    *(ptr+2) = 0;
}

void main(void) {
    S myStruct;
    Initialize(&myStruct.f1);
}

In this example, in the body of Initialize, ptr is an int pointer that points to the first field of the structure. When you attempt to access the second field through ptr, the Illegally dereferenced pointer check shows a red error.

Result Details: The check message states that the pointer points to 4 bytes at offset 4 in buffer of 4 bytes, so is outside bounds:

  • Points-to size is 4 bytes because the pointer ptr points to int.

  • Buffer size is 4 bytes because the buffer size is based on the type of the variable pointed to, that is, myStruct.f1. The type of myStruct.f1 is int.

  • Offset is 4 bytes because the operation (ptr + 1) moves the pointer one int size away from the beginning of the buffer.

Note that the sizes of data types are target-dependent. The sizes in this example describe most common targets.

Correction — Avoid memory access outside structure field

One possible correction is to pass a pointer to the entire structure to Initialize.

typedef struct S {
    int f1;
    int f2;
    int f3;
} S;

void Initialize(S* ptr) {
    ptr->f1 = 0;
    ptr->f2 = 0;
    ptr->f3 = 0;
}

void main(void) {
    S myStruct;
    Initialize(&myStruct);
}
#include<stdlib.h>

void main() {
    int *ptr=NULL;
    *ptr=0;
}

In this example, ptr is assigned the value NULL. Therefore when you dereference ptr, the Illegally dereferenced pointer check shows a red error.

You see a similar error if the pointer is initialized with an absolute address such as 0x0000:

#define RAM_START 0x0000

void main() {
    int *ptr;
    ptr = RAM_START;
    *ptr = 0;
}

Result Details: The check message states that the pointer is null. Because the pointer is null, the check does not proceed to verify if the pointer points within an allocated buffer.

Correction — Avoid NULL pointer dereference

One possible correction is to initialize ptr with the address of a variable instead of NULL.

void main() {
    int var;
    int *ptr=&var;
    *ptr=0;
}

Work around the error by replacing 0x0000 with another address just for the purposes of the analysis. For instance:

#ifdef POLYSPACE
#define RAM_START 0x0001
#else
#define RAM_START 0x0000
#endif

void main() {
    int *ptr;
    ptr = (int*)RAM_START;//Cast int to int*
    *ptr = 0;
}
Use the analysis option -D POLYSPACE so that the address 0x0000 is replaced with an alternative address (in this case, 0x0001) for the Polyspace analysis. See also Preprocessor definitions (-D). Use this solution only when you know RAM_START is a valid address.

int getOffset(void);

void main() {
    int *ptr = (int*) 0 + getOffset();
    if(ptr != (int*)0)
        *ptr = 0;
}

In this example, although an offset is added to (int*) 0, Polyspace does not treat the result as a valid address. Therefore when you dereference ptr, the Illegally dereferenced pointer check shows a red error.

Result Details: The check message states that even though the pointer itself is not null, it might not be allocated any memory.

int arr[10];

int main(int arg, char* argv[]) {
    int *ptr = &arr[10];
    int val_ptr = *ptr;
    return 0;
}

In this example, the pointer ptr is assigned an address past the memory allocated for the array arr. However, this assignment does not trigger the Illegally dereferenced pointer check. The check occurs only when the pointer is dereferenced and shows a definite error (red).

struct flagCollection {
    unsigned int flag1: 1;
    unsigned int flag2: 1;
    unsigned int flag3: 1;
    unsigned int flag4: 1;
    unsigned int flag5: 1;
    unsigned int flag6: 1;
    unsigned int flag7: 1;
};

char getFlag(void);

int main()
{
    unsigned char myFlag = getFlag();
    struct flagCollection* myFlagCollection;
    myFlagCollection = (struct flagCollection *) &myFlag;
    if (myFlagCollection->flag1 == 1)
        return 1;
    return 0;
}

In this example:

  • The fields of flagCollection have type unsigned int. Therefore, a flagCollection structure requires 32 bits of memory in a 32-bit architecture even though the fields themselves occupy 7 bits.

  • When you cast a char address &myFlag to a flagCollection pointer myFlagCollection, you assign only 8 bits of memory to the pointer. Therefore, the Illegally dereferenced pointer check on dereference of myFlagCollection shows a red error.

Result Details: The check message states that the pointer points to 4 bytes at offset 0 in buffer of 1 bytes, so is outside bounds.:

  • Points-to size is 4 bytes because the pointer points to a structure with effectively one unsigned int field. All the bit fields can be accommodated within one unsigned int.

  • Buffer size is 1 byte because the buffer size is based on the type of the variable pointed to, that is, myFlag. The variable data type is char.

  • Offset is 0 because the pointer points to the beginning of the buffer. No pointer arithmetic is involved.

Note that the sizes of data types are target-dependent. The sizes in this example describe most common targets.

Correction — Use correct type for bit fields

One possible correction is to use unsigned char as field type of flagCollection instead of unsigned int. In this case:

  • The structure flagCollection requires 8 bits of memory.

  • When you cast the char address &myFlag to the flagCollection pointer myFlagCollection, you also assign 8 bits of memory to the pointer. Therefore, the Illegally dereferenced pointer check on dereference of myFlagCollection is green.

struct flagCollection {
    unsigned char flag1: 1;
    unsigned char flag2: 1;
    unsigned char flag3: 1;
    unsigned char flag4: 1;
    unsigned char flag5: 1;
    unsigned char flag6: 1;
    unsigned char flag7: 1;
};

char getFlag(void);

int main()
{
    unsigned char myFlag = getFlag();
    struct flagCollection* myFlagCollection;
    myFlagCollection = (struct flagCollection *) &myFlag;
    if (myFlagCollection->flag1 == 1)
        return 1;
    return 0;
}
#include <stdlib.h>

void main(void)
{
    char *p = (char*)malloc(1);
    char *q = p;
    *q = 'a';
}

In this example, malloc can return NULL to p. Therefore, when you assign p to q and dereference q, the Illegally dereferenced pointer check shows an orange error.

Result Details: The check message states that the pointer may be null.

Correction — Check return value of malloc for NULL

One possible correction is to check p for NULL before dereferencing q.

#include <stdlib.h>
void main(void)
{
    char *p = (char*)malloc(1);
    char *q = p;
    if(p!=NULL) *q = 'a';
}
#include <stdlib.h>

typedef struct {
   int state;
   union {
        char myChar;
        int myInt;
    } myVar;
} myType;

void main() {
    myType* myTypePtr;
    myTypePtr = (myType*)malloc(sizeof(int) + sizeof(char));
    if(myTypePtr != NULL) {
        myTypePtr->state = 0;
    }
}

In this example:

  • Because the union myVar has an int variable as a field, it must be assigned 4 bytes in a 32-bit architecture. Therefore, the structure myType must be assigned 4+4 = 8 bytes.

  • malloc returns sizeof(int) + sizeof(char)=4+1=5 bytes of memory to myTypePtr, a pointer to a myType structure. Therefore, when you dereference myTypePtr, the Illegally dereferenced pointer check returns a red error.

Result Details: The check message states that the pointer points to 8 bytes at offset 0 in buffer of 5 bytes, so is outside bounds:

  • Points-to size is 8 bytes because the pointer myTypePtr points to the type myType. The structure myType has two fields:

    • An int field (4 bytes).

    • An union field, where the largest type in the union is int (4 bytes).

  • Buffer size is 5 bytes because the heap-allocated memory has size equal to sizeof(int) + sizeof(char) or 4+1 bytes.

  • Offset is 0 because the pointer points to the beginning of the buffer. No pointer arithmetic is involved.

Note that the sizes of data types are target-dependent. The sizes in this example describe most common targets.

Correction — Assign sufficient memory to pointer

One possible correction is to assign 8 bytes of memory to myTypePtr before dereference.

#include <stdlib.h>

typedef struct {
   int state;
   union {
        char myChar;
        int myInt;
    } myVar;
} myType;

void main() {
    myType* myTypePtr;
    myTypePtr = (myType*)malloc(sizeof(int) * 2);
    if(myTypePtr != NULL) {
        myTypePtr->state = 0;
    }
}
#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} rectangle;

typedef struct {
    int length;
    int breadth;
    int height;
} cuboid;

void main() {
    cuboid *cuboidPtr = (cuboid*)malloc(sizeof(rectangle));
    if(cuboidPtr!=NULL) {
        cuboidPtr->length = 10;
        cuboidPtr->breadth = 10;
    }
}

In this example, cuboidPtr obtains sufficient memory to accommodate two of its fields. Because the ANSI® C standards do not allow such partial memory allocations, the Illegally dereferenced pointer check on the dereference of cuboidPtr shows a red error.

Result Details: The check message states that the pointer points to 12 bytes at offset 0 in buffer of 8 bytes, so is outside bounds:

  • Points-to size is 12 bytes because the pointer ptr points to the type cuboid. The type cuboid has three int members, so its size is 3 * 4 bytes.

  • Buffer size is 8 bytes because the heap-allocated memory has size equal to sizeof(rectangle). The type rectange has two int members, so its size is 2 * 4 bytes.

  • Offset is 0 because the pointer points to the beginning of the buffer. No pointer arithmetic is involved.

Note that the sizes of data types are target-dependent. The sizes in this example describe most common targets.

Correction — Allocate full memory

To observe ANSI C standards, cuboidPtr must be allocated full memory.

#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} rectangle;

typedef struct {
    int length;
    int breadth;
    int height;
} cuboid;

void main() {
    cuboid *cuboidPtr = (cuboid*)malloc(sizeof(cuboid));
    if(cuboidPtr!=NULL) {
        cuboidPtr->length = 10;
        cuboidPtr->breadth = 10;
    }
}
Correction — Use Polyspace analysis option

You can allow partial memory allocation for structures, yet not have a red Illegally dereferenced pointer error. To allow partial memory allocation, on the Configuration pane, under Check Behavior, select Allow incomplete or partial allocation of structures.

#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} rectangle;

typedef struct {
    int length;
    int breadth;
    int height;
} cuboid;

void main() {
    cuboid *cuboidPtr = (cuboid*)malloc(sizeof(rectangle));
    if(cuboidPtr!=NULL) {
        cuboidPtr->length = 10;
        cuboidPtr->breadth = 10;
    }
}
#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} square;


void main() {
    square mySquare;
    char* squarePtr = (char*)&mySquare.length;
//Assign zero to mySquare.length byte by byte
    for(int byteIndex=1; byteIndex<=4; byteIndex++) {
        *squarePtr=0;
        squarePtr++;
    }
//Assign zero to first byte of mySquare.breadth
    *squarePtr=0;
}

In this example, although squarePtr is a char pointer, it is assigned the address of the integer mySquare.length. Because:

  • char occupies 1 byte,

  • int occupies 4 bytes in a 32–bit architecture,

squarePtr can access the four bytes of mySquare.length through pointer arithmetic. But when it accesses the first byte of another field mySquare.breadth, the Illegally dereferenced pointer check shows a red error.

Result Details: The check message confirms that the pointer points to 1 bytes at offset 4 in buffer of 4 bytes, so is outside bounds:

  • Points-to size is1 byte because the pointer squarePtr points to char.

  • Buffer size is 4 bytes because the buffer size is based on the type of the variable pointed to, that is, mySquare.length. The type of mySquare.length is int.

  • Offset is 4 bytes because the pointer has been moved one int size away from the beginning of the buffer using the operation squarePtr++.

Note that the sizes of data types are target-dependent. The sizes in this example describe most common targets.

Correction — Assign address of structure instead of field

One possible correction is to assign squarePtr the address of the full structure mySquare instead of mySquare.length. squarePtr can then access all the bytes of mySquare through pointer arithmetic.

#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} square;


void main() {
    square mySquare;
    char* squarePtr = (char*)&mySquare;
//Assign zero to mySquare.length byte by byte
    for(int byteIndex=1; byteIndex<=4; byteIndex++) {
        *squarePtr=0;
        squarePtr++;
    }
//Assign zero to first byte of mySquare.breadth
    *squarePtr=0;
}
Correction — Use Polyspace analysis option (not available in C++)

You can use a pointer to navigate across the fields of a structure and not produce a red Illegally dereferenced pointer error. To allow such navigation, on the Configuration pane, under Check Behavior, select Enable pointer arithmetic across fields.

This option is not available for C++ projects. In C++, pointer arithmetic becomes nontrivial when dealing with concepts such as polymorphic types.


#include <stdlib.h>
typedef struct {
    int length;
    int breadth;
} square;


void main() {
    square mySquare;
    char* squarePtr = (char*)&mySquare.length;
//Assign zero to mySquare.length byte by byte
    for(int byteIndex=1; byteIndex<=4; byteIndex++) {
        *squarePtr=0;
        squarePtr++;
    }
//Assign zero to first byte of mySquare.breadth
    *squarePtr=0;
}
void func2(int *ptr) {
    *ptr = 0; 
}

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

In the following code, ptr points to ret. Because the scope of ret is limited to func1, when ptr is accessed in func2, the access is illegal. The verification shows a red Illegally dereferenced pointer check on *ptr.

By default, Polyspace Code Prover™ does not detect functions returning pointers to local variables. To detect such cases, use the option Detect stack pointer dereference outside scope (-detect-pointer-escape).

Result Details: The check message states that the pointer points to a local variable that is accessed outside its scope.

#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;
    
    if(shift < 0) {
       free(pi);
    }

    j = *pi + shift;

    return j;
}

In this example, if the argument shift is negative, the pointer pi is dereferenced after the allocated memory is freed using the free() function. The Illegally dereferenced pointer check can detect this issue and report a possible run-time error (orange check).

In this example, an alias of the pointer ptr1 is conditionally reallocated. If randomVar is true, the alias ptr2 points to a new location. Otherwise, both ptr1 and ptr2 points to the same location. The dereferencing of ptr2 after the free statement attempts to dereference an already freed pointer when randomVar is false. Polyspace reports an orange check of this run-time error.

int main() {
	volatile int randomVar;
	int *ptr1 = (int *)malloc(sizeof(int));

	void *ptr2 = (void *)ptr1;

	if(randomVar) {
		ptr2 = malloc(sizeof(int));
		assert(ptr2 != 0);
	}

	free(ptr1);
	*(int *)ptr2 = randomVar; // Potential IDP


}

Check Information

Group: Static memory
Language: C | C++
Acronym: IDP