Klocwork products apply complex static analysis techniques to C, C++ and Java to automatically locate critical programming bugs, including:
  • Operational defects
  • Security vulnerabilities
  • Architectural anomalies

By applying inter-procedural control flow, data flow, value-range propagation and symbolic logic evaluation, Klocwork can find hundreds of errors on well-validated, feasible execution paths. Some notable key findings are summarized below – click on each error for details.


C and 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 over time.

Three common C and C++ errors that Klocwork detects are:

1. NULL pointer dereference [READ MORE]

NULL pointers (or null object references in Java) cause numerous problems when they are dereferenced, usually due to insufficient defenses being placed in code. This type of error shows itself most notably in larger systems where different developers are responsible for inter-dependent modules. For example:

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 for even junior developers 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 manually.

Value projection also adds another layer of complexity:

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 products are able to understand how these values affect the available space of code paths.

2. Buffer overflow [READ MORE]

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. For example:

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

In this simple 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.

This coding pattern is at the heart of some of the most pernicious security vulnerabilities that exist in software today.

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:

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.

3. Memory leak [READ MORE]

As pointed out in the NULL pointer dereference examples above, 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.

This is a simple example:

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 products can spot occurrences of memory leaks within code constructs that might easily pass manual inspection. For example:

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 pernicious in C++ with base and derivedclasses defining different elements that all must be cleaned up by the virtual destructor chain in order to guarantee no memory leaks.



Java
Java programming also comes with its own unique set of common error vulnerabilities. The following three are examples of the many Java code defects that Klocwork products can detect:

1. Concurrency [READ MORE]

Concurrent programming, or the practice of dividing up a program’s execution context into two or more threads, is becoming increasingly common due to the prevalence of multi-core and multi-CPU hardware environments. Regardless of the programming language being used, there are several basic requirements placed on any source code analysis engine that must be taken seriously in a concurrent context. Two of the most important of these are:

  • The ability to spot dead-lock or live-lock situations
  • The ability to spot race conditions

Without these capabilities, programmers are left to guess for themselves how their programs will operate at runtime.

Dead-locks and live-locks refer to situations in which programs use locking semantics to guard sections of code against two or more threads of execution attempting access simultaneously. Typically, this involves modifying global data, but is by no means limited to that context.

Consider the following example:

public void foo()
{
    if( someCondition() ) {
        synchronized( someGlobalSemaphore ) {
            // First problem, this blocks all threads
            Thread.Sleep(1000);
            if( someOtherCondition() ) {
                // Second problem, potential contention
                synchronized( someOtherGlobalSemaphore ) {
                    ...
                }
            }
        }
    }
}

Here, several different locking scenarios are shown, any of which can cause blocking situations, including:
  • Explicitly blocking one thread while holding a process-wide lock
  • Lock contention through potentially inconsistent acquisition semantics

Significant debug time can also be spent chasing almost impossible-to-replicate behavior caused by the “race condition.” This condition occurs when two or more threads each has an equal chance to modify data in each others’ context. A simple example is static data in a re-entrant class, such as a servlet running within a J2EE container. Modifying class data on one thread that might be read or differently modified at the same time by another thread will lead to unexpected behavior.

2. Resource leaks [READ MORE]

Like memory leaks, resource leaks can be crippling to an application and, over time, will lead to denial-of-service scenarios. The family of resource leaks to be most concerned with are those that tie into either operating system handles or descriptors, or to framework memory that requires explicit release semantics.

Klocwork supports a wide variety of resource semantics, from basic types like file descriptors and streams to framework-specific semantics for environments such as Google’s Web Toolkit, Struts, Java Mail, J2ME, ImageIO, Hibernate, and many more.

A very common mistake is to assume that the runtime garbage collector (GC) will look after resources in the same way that it looks after memory references. However, while the memory associated with the object itself is collected by the GC, the resources associated with that object may not be cleaned up. For example:

public void foo(String name) throws IOException {
    Reader r = new InputStreamReader(new FileInputStream(name));
    char ch;
    while( (ch = r.read()) != -1 ) {
       if( ch == ' ' )
           return;
    }
}

There are at least two types of resource that are collected under the input stream object in the example above:
  • Memory associated with the stream object itself, and with managing state within the stream
  • An operating system file descriptor or handle that relates to the underlying file within the file system
The GC will take care of the first aspect while leaving the underlying descriptor open, thus consuming valuable system resources over time and eventually resulting in a denial-of-service scenario.

3. Web application vulnerabilities [READ MORE]

With many potential pitfalls inherent in creating web applications, this has quickly become one of the most popular areas to investigate automated approaches to debugging. Klocwork's source code analysis products detect vulnerabilities like:

  • SQL injection
  • Process or file injection
  • Code injection
  • Cross-site scripting (XSS)
  • Request forging

Each of these types of failure requires specific checks and analysis. Generally, however, a many of the attack vectors exposed by such weaknesses can be generalized to the propagation of tainted data around an under-defensive design. That is, taking input from a user or another process and using that data without rigorous validation of its format, its range, or whatever else might make sense for the data type in question.

In a servlet context, for example, consider the following snippet:

public void doGet(HttpServletRequest req,
                 HttpServletResponse res)
{
    String name = req.getParameter("username");
    String pwd = req.getParameter("password");

    // SQL Injection
    int id = validateUser(username, password);

    // XSS
    String retstr = "User : " + name + " has ID: " + id;
    res.getOutputStream().write(retstr.getBytes());
}

private int validateUser(String user, String pwd)
    throws Exception
{
    Statement stmt = myConnection.createStatement();
    ResultSet rs;

    rs = stmt.executeQuery("select id from users where user='" +
                     user + "' and key='" + pwd + "'");
    return rs.next() ? rs.getInt(1) : -1;
}

This example exhibits several different kinds of common errors. Ignoring the obvious resource leakage and management issues for now, these include:
  • SQL injection via the use of an unfiltered incoming URL parameter as both username and password
  • Cross-site scripting, or request mirroring, by including the unfiltered incoming URL parameter in our response to the user

A malicious user could provide suitably marked-up URL parameters that would cause many problems. Likewise, applications that fail to validate strings that end up being used as file names are also open to file or process injection.

As a result of its comprehensive web vulnerability analysis, Klocwork can detect all OWASP Top 10 vulnerabilities which can be found through static source code analysis:

  • Unvalidated Input
  • Broken Access Control
  • Broken Authentication and Session Management
  • Cross Site Scripting
  • Buffer Overflow
  • Injection Flaws
  • Improper Error Handling
  • Insecure Storage
  • Application Denial of Service



Identify and Describe Common Errors
In all of these scenarios, effective source code analysis must do more than simply spot the flaw in the programmer's code. It's just as important to describe the flaw to the programmer such that it is easily understood in terms of the impact of the vulnerability and how an educated hacker could use it to harm the system under construction.

Klocwork products are effective tools not just because they identify these and hundreds of other defects – they also provide education about coding errors. This helps developers to check in error-free code, and to become more efficient, error-free programmers over time.