C/C++ Error Examples

Many bugs created within C-derived languages are common to all derivations as well as to the root language. The most notable problems derive from C’s requirement for the programmer to manage memory explicitly. This places an ongoing lifetime maintenance burden on the original developer as well as on every developer who inherits the code.

Whether it’s newly created or legacy code, Klocwork finds a broad range of issues specific to C and C++ in these high-level categories:

NULL pointer dereferences

Dereferencing NULL pointers (or null object references in Java) causes numerous problems usually due to insufficient defenses being placed in code. It can happen anywhere, but we often see this type of error in larger systems where different developers are responsible for inter-dependent modules. Finding and addressing these errors earlier in the code development process creates a stronger build and more reliable code for years to come.

Example: NULL pointer dereference

This is a simple example of NULL pointer dereference:

1
2
3
4
5
6
7
void foo(int* p) {
    *p = 32;
}

void bar() {
    foo(NULL);
}

When both of these functions are physically resident within the same module or the same subsystem, it is trivial to find the error. But, when these functions are separated into dependent subsystems written by different developers or different development teams, these errors become much more difficult to spot. Value projection also adds another layer of complexity:

1
2
3
4
5
6
7
8
9
10
11
12
13
void foo(int* p) {
    *p = 32;
}

void bar(int x) {
    int* p = NULL;

    if (x == 15 || x == 20)
        p = &x;

    if(x > 10 && x <= 20)
        foo(p);
}

In this example, certain values of the incoming parameter will cause errors and other values will not. Klocwork static code analysis understands how these values affect the available space of code paths – and alerts the developer immediately.

Buffer overflow

Buffer overflows, more generally referred to as array bounds violations, are errors that occur when code addresses elements of an array outside of the bounds that are defined for that data object. This coding pattern is at the heart of some of the most pernicious security vulnerabilities that exist in software today.

Example: Buffer overflow

Here’s a simple example of a buffer overflow:

1
2
3
4
char arr[32];

for(int i = 0; i < 64; i++)
    arr[i] = (char)i;

In this example, the programmer is explicitly addressing memory outside of the range of the stack-based variable “arr.” This will cause memory to be overwritten, potentially including the stack frame information that is required for the function to successfully return to its caller, etc.

While the specifics of the vulnerability change from instance to instance, the underlying problem is the same: performing array copy operations that are incorrectly or insufficiently guarded against exploit. Consider the following example:

1
2
3
4
5
6
void foo(unsigned char* data) {
    unsigned char value[32];

    int len = (int)data[0];
    memcpy(value, data + 1, len);
}

The “memcpy” call will copy up to 256 bytes (the result of taking the zero’th element of the “data” buffer as its length) into a fixed sized stack-based array of only 32 bytes. If that data stream is available to the outside world, the system can be hacked. For a high-profile example of this error in a long line of cases of buffer overflow causing stack corruption and vulnerability to code injection, consider Microsoft’s well-published problems with animated cursor files.

Memory leak detection

One of the major problems with C-derived languages is the requirement for the programmer to manage memory completely. Buffers or structures that are allocated on the heap must be released appropriately when all references to those buffers or structures are about to head out of scope.

Example: Memory leaks

This is a simple example of a memory leak:

1
2
3
void foo() {
    malloc(32);
}

On return from this function, a data block of 32 bytes in length will languish unreferenced on the heap. If repeated enough times, the heap manager will fail. By applying rigorous control and data flow analysis, Klocwork finds occurrences of memory leaks within code constructs that might easily pass manual inspection. For example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
typedef struct List {
    struct List* next;
    char* value;
    int len;
} List;

void addToList(List* root, char* str) {
    List* elem = (List*)malloc(sizeof(List));
    if(elem) {
        elem->next = root->next;
        root->next = elem;

        /* Duplicate the string, allocating memory */
        elem->value = strdup(str);
        elem->len = strlen(str);
    }
}

void removeList(List* root) {
    List* ptr;
    while ((ptr = root) != NULL ) {
        root = root->next;

        /* This releases the structure, but not the string */
        free(ptr);
    }
}

void foo() {
    List* root = (List*)calloc(1, sizeof(List));
    addToList(root, "hello");
    addTolist(root, "world");
    removeList(root);
    free(root);
}

Here, a simple singly linked list leaks memory whenever it is cleaned up because while the list elements themselves are released, the strings to which each element points are not released.

This type of error is particularly elusive in C++ with base and derived classes defining different elements that all must be cleaned up by the virtual destructor chain to guarantee no memory leaks.

Other languages

For examples of the types of defects Klocwork can detect in other languages please see the C# error examples and Java error examples pages.