- 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:
JavaJava 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:
C and C++ Error Examples
NULL Pointer Dereferences 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.
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.
|
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.
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.
|
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.
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 derived classes defining different elements that all must be cleaned up by the virtual destructor chain in order to guarantee no memory leaks.
|
Identify and Describe Other Common Coding Errors Klocwork products don't just identify memory leaks in code. Their comprehensive static analysis techniques automatically locate critical programming bugs, including:
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.
|
![]()
Java
Concurrency 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:
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.
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:
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.
|
Resource Leaks 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. 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:
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.
|
Web Application Vulnerabilities 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:
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.
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:
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:
|
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.
|


