Static Analysis of Popular Open Source Internet Communication Applications - Embedded.com

Static Analysis of Popular Open Source Internet Communication Applications

Static source code analyzers attempt to find code sequences that when executed could result inbuffer overflows, resource leaks, or many other security andreliability problems. Source code analyzers are effective at locating asignificant class of flaws that are not detected by compilers duringstandard builds and often go undetected during run-time testing aswell.

Recently, our source code analyzer was used to find flaws in severalopen source applications that are widely used in Internetcommunication. The latest releases of Apache, OpenSSL, and sendmailwere analyzed. An overview of each application follows.

Open Source Software Under Test
APACHE . According to apache.org,the Apache open source hypertext transfer protocol (HTTP) server is themost popular web server in the world, powering more than 70% of the websites on the Internet. Given the ubiquity of Apache and the world’sdependence on the Internet, the reliability and security of Apacherepresent an important concern for all of us.

The Apache web server consists of approximately 200,000 lines ofcode, 80,000 individual executable statements, and 2,000 functions.2.2.3 is the version under test.

OPENSSL. OpenSSLis an open source implementation of Secure Sockets Layer (SSL) and TransportLayer Security (TLS). TLS is the modern reimplementation ofSSL, although SSL is often used as a general term covering bothprotocols. SSL forms the basis of much of the secure communication onthe Internet.

For example, SSL is what enables users to send private credit cardinformation securely from their browsers to an online merchant’s remoteserver. In addition to being intimately involved with datacommunication, OpenSSL contains implementations of a variety ofcryptographic algorithms used to secure the data in transit.

OpenSSL is available for Windows; however, OpenSSL is thestandard SSL implementation for Linux and UNIXworldwide. In addition, because of its liberal licensing terms (notGPL), OpenSSL has been used as abasis for a number of commercial offerings. Like Apache, OpenSSL is akeystone of worldwide secure Internet communication. Flaws in thissoftware could have widespread deleterious consequences.

OpenSSL consists of approximately 175,000 lines of code, 85,000individual executable statements, and 5,000 functions. 0.9.8b is theversion under test.

SENDMAIL . According towikipedia.org , sendmailis the most popular electronic mail server software used in theInternet. Sendmail has been the de-facto electronic mail transfer agentfor UNIX (and now Linux) systems since the early 1980s.

Given the dependence on electronic mail, the stability and securityof sendmail is certainly an important concern for many. The name“sendmail” might lead one to think that this application is not verycomplicated. Anyone who has ever tried to configure a sendmail serverknows otherwise.

Sendmail consists of approximately 70,000 lines of code, 32,000individual executable statements, and 750 functions. 8.13.8 is theversion under test.

How Source Analysis Works
A source code analyzer is usually run as a separate tool, independentof the compiler used to build application code. Sometimes the analyzeris built into the same compiler used to build production code (as isthe case with the GreenHills analyzer).

The analyzer takes advantage of compiler-style dataflow algorithmsin order to perform its bug-finding mission. One advantage of using asingle tool for both compiling and analyzing is that the source codeparsing need only be done once instead of twice. In addition, sourceanalysis can be configured to cause build errors when flaws aredetected so that developers will be encouraged to find and fix themquickly.

A typical compiler will issue warnings and errors for basiccodeproblems, such as violations of the language standard or use ofimplementation-defined constructs. In contrast, the analyzer performs afull program analysis, finding bugs caused by complex interactionsbetween pieces of code that may not even be in the same source file.

The analyzer determines potential execution paths through code,including paths into and across subroutine calls, and how the values ofprogram objects (such as standalone variables or fields withinaggregates) could change across these paths. The objects could residein memory or in machine registers.

The analyzer looks for many types of flaws. It looks for bugs thatwould normally compile without error or warning. The following is alist of some of the more common errors that the analyzer will detect:

* Potential NULL pointerdereferences
* Access beyond an allocated area(e.g. array or dynamically allocated buffer); otherwise known as abuffer overflow
* Writes to potentially read-onlymemory
* Reads of potentially uninitializedobjects
* Resource leaks (e.g. memory leaksand file descriptor leaks)
* Use of memory that has already beendeallocated
* Out of scope memory usage (e.g.returning the address of an automatic variable from a subroutine)
* Failure to set a return value froma subroutine
* Buffer and array underflows

The analyzer understands the behavior of many standard runtimelibrary subroutines. For example it knows that subroutines like freeshould be passed pointers to memory allocated by subroutines likemalloc. The analyzer uses this information to detect errors in codethat calls or uses the result of a call to these subroutines.

Limiting False Positives
The analyzer can also be taught about properties of user-definedsubroutines. For example if a custom memory allocation system is used,the analyzer can be taught to look for misuses of this system.

By teaching the analyzer about properties of subroutines, users canreduce the number of false positives. A falsepositive is a potential flaw identified by the analyzerthat could not actually occur during program execution. One of themajor design goals of a source code analyzer is to limit the number offalse positives so that developers can minimize time looking at them.

If an analyzer generates too many false positives, it will becomeirrelevant because the output will be ignored by engineers. Theanalyzer is much better at limiting false positives than traditionalUNIX programming tools like lint. However, since an analyzer is notable to understand complete program semantics, it is not possible tototally eliminate false positives.

In some cases, a flaw found by the analyzer may not result in afatal program fault, but could point to a questionable construct thatshould be fixed to improve code clarity. A good example of this is awrite to a variable that is never subsequently read.

Complexity Control
Much has been published regarding the benefits of reducing complexityat the subroutine level. Breaking up a software module into smallersubroutines makes each subroutine easier to understand, maintain, andtest. A complexity limitation coding rule is easily enforced at buildtime by calculating a complexity metric and generating a build-timeerror when the complexity metric is exceeded.

The source analyzer can optionally check code complexity. Onceagain, since the analyzer is already traversing the code tree, it doesnot require significant additional time to apply a simple complexitycomputation, such as the popular McCabe complexity metric. Someanalyzers can be configured to generate a build error pointing out theoffending subroutine.

Thus, the developer is unable to accidentally create code thatviolates the rule. In general, a good analyzer can be used to helpenforce coding standards that would otherwise need to be done withmanual human reviews or non-integrated third party products.

Figure1: Analyzer summary report

Output Of The Analyzer
Output format differs amongst analyzers, but a common mechanism is togenerate an intuitive set of web pages, hosted by an integrated webserver. The user can browse high level summaries of the different flawsfound by the analyzer (Figure 1,above) and then click on hyperlinks to investigate specificproblems. Within a specific problem display, the flaw is displayedinline with the surrounding code, making it easy to understand (Figure 2, below) .

Function names and other objects are hyperlinked for convenientbrowsing of the source code. Since the web pages are running under aweb server, the results can easily be shared and browsed by any memberof the development team on the network.

Figure2. In-context display of flaw

Analysis Time
Analysis time will obviously be a gating factor in the widespreadadoption of these tools. We performed some build and analysis timecomparisons using the Green Hills compiler and source code analyzer todetermine the added overhead of using the analyzer on a regular basis.

The build time for the Apache web server using a single desktop PCrunning Linux was 1.5 minutes. The source analysis time on the same PCwas 3.5 minutes. Build time using distributed PCs was 30 seconds.

With the Green Hills distributed build system, source codeprocessing is automatically parallelized across worker PCs on thenetwork. The system only uses PCs that have cycles to spare. In ourtest environment, 15 PCs were configured to act as workers, andapproximately 10 of them were used at any one time for a build.

The source analysis time using distributed processing was 1.0minutes, significantly less than the standard compile time using asingle dedicated PC.It seems clear that when 200,000 lines of code can be analyzed in aminute using commonly available PC resources, there really is no reasonnot to have all developers using these tools all the time.

Flaws Found. The followingsections provide examples of actual flaws in Apache, OpenSSL, andsendmail that were discovered by the Green Hills source code analyzer.The results are grouped by error type, with one or more examples ofeach error type per section.

Potential Null Pointer Dereference
This was by far the most common flaw found by the analyzer in all threesuites under test. Some cases involved calls to memory allocationsubroutines that were followed by accesses of the returned pointerwithout first checking for a NULL return. This is a robustness issue. Ideally, all memory allocationfailures are handled gracefully.

If there is a temporary memory exhaustion condition, service mayfalter, but not terminate. This is of particular importance to serverprograms such as Apache and sendmail. Algorithms can be introduced thatprevent denial of service in overload conditions such as that caused bya malicious attack.

The Apache web server, sendmail, and OpenSSL all make abundant useof C runtime library dynamic memory allocation. Unlike Java whichperforms automatic garbage collection, memory allocation using thestandard C runtime requires that the application itself handle memoryexhaustion failures. If a memory allocation call fails and returns a NULL pointer, a subsequentunguarded reference of the result pointer is all but guaranteed tocause a fatal crash.

On line 120 in the Apache source file scoreboard.c , we have the followingmemory allocation statement:

ap_scoreboard_image = calloc(1,
sizeof(scoreboard) + server_limit *
sizeof(worker_score *) + server_limit *
lb_limit * sizeof(lb_score *));

Clearly, this allocation of memory could be substantial. It would bea good idea to make sure that the allocation succeeds beforereferencing the contents of ap_scoreboard_image .However, soon after the allocation statement, we have this use:

ap_score_board_image->global =
(global_score *)more_storage;

The dereference is unguarded, making the application susceptible toa fatal crash. Another example from Apache can be found at line 765 inthe file mod_auth_digest.c :

entry= client_list->table[idx];
prev = NULL;
while (entry->next){/* find lastentry */
prev = entry;
entry = entry->next; …
}

Note that the variable entry is unconditionally dereferenced at thebeginning of the loop. This alone would not cause the analyzer toreport an error. At this point in the execution path, the analyzer hasno specific evidence or hint that entry could be NULL or otherwise invalid.However, the following statement occurs after the loop:

if(entry) {

}

By checking for a NULL entry pointer, the programmer has indicated that entry could be NULL . Tracing backwards, theanalyzer now sees that the previous dereference to entry at the top ofthe loop is a possible NULL reference.The following similar example was detected in the sendmail application,at line 8547 of file queue.c ,where the code unconditionally dereferences the pointer variable tempqfp:

errno= sm_io_error(tempqfp);

sm_io_error is a macrowhich resolves to a read of the tempqfp->f_flags field. Later, at line 8737, we have this NULL check:

if(tempqfp != NULL)
sm_io_close(tempqfp, SM_TIME_DEFAULT);

with no intervening writes to tempqfp after the previously noteddereference. The NULL check,of course, implies that tempqfp could be NULL ; if thatwas ever the case, the code would fault. If the pointer can never inpractice be NULL ,then the extra check is unnecessary and misleading. What may seem tosome as harmless sloppiness can translate into catastrophic failuregiven the right (wrong) conditions.

In sendmail, there are many other examples of unguarded pointerdereferences that are either preceded or followed by NULL checks ofthe same pointer.One more example in this category comes from OpenSSL, at line 914 infile ssl_lib.c:

if(s->handshake_func == 0) {
SSLerr(SSL_F_SSL_SHUTDOWN, SSL_R_UNINITIALIZED);
}

Shortly thereafter,we have a NULL check of the pointer s:

if ((s!= NULL) && !SSL_in_init(s))

Again, the programmer is telling us that s could be NULL , yet the preceding deferenceis not guarded.

Buffer Underflow
A buffer underflow is defined as an attempt to access memory before anallocated buffer or array. Similar to buffer overflows, bufferunderflows cause insidious problems due to the unexpected corruption ofmemory. The following flaw was discovered at line 5208 of file queue.c in sendmail:

if ((qd == -1|| qg == -1) &&
type != 120)

else {
switch (type) {

case 120:
if(bitset(QP_SUBXF,
Queue[qg]->qg_qpaths[qd].qp_subdirs))
}
}

The if statement implies that it is possible for qd or qg to be -1 when type is 120. Butin the subsequent switch statement, always executed when type is 120,the Queue array is unconditionally indexed through the variable qg.

If qg was -1, this is anunderflow. The code was not studied exhaustively to determine whetherqg can indeed be -1 when type is 120 and hence reach the fault.However, if qg can not be -1when type is 120, then the initial if check is incorrect, misleading,and/or unnecessary.

Another example of buffer underflow is found at line 1213 of file ssl_lib.c in OpenSSL:

p =buf;
sk = s->session->ciphers;
for (i = 0; i

*(p++)=':';
}
p[-1] = '';

The analyzer informs us that the underflow occurs when this code iscalled from line 1522 in file s_server.c .

From a look at the call site in s_server.c ,you can see that the analyzer has detected that buf points to thebeginning of a statically allocated buffer. Therefore, in the ssl_lib.c code , if there are nociphers in the cipher stack sk, then the access p[-1] is an underflow.

This demonstrates the need for an intermodule analysis, since therewould be no way of knowing what buf referenced without examining thecaller.

If it is the case that the number of ciphers cannot actually be 0 inpractice, then the for loop should be converted to a do loop in orderto make it clear that the loop is always executed at least once(ensuring that p[-1] does notunderflow).

Another problem is a potential buffer overflow. No check is made inthe ssl_lib.c code to ensurethat the number of ciphers does not exceed the size of the bufparameter. Instead of relying on convention, a better programmingpractice would be to pass in the length of buf and then add code tocheck that overflow does not occur.

Resource Leak s
In line 2564 of file speed.c in OpenSSL:

fds=malloc(multi*sizeof *fds);

fds is a local pointer and is never used to free the allocatedmemory prior to return from the subroutine. Furthermore, fds is notsaved in another variable where it could be later freed. Clearly, thisis a memory leak. A simple denial of service attack on OpenSSL would beto invoke or cause to be invoked the speed command until all of memoryis exhausted.

Conclusion
What better applications to pick, than popular, open source Internetcommunications, for demonstrating the importance of automated sourcecode analyzers? Not only is this software of tremendous importance tothe world at large, but the fact that the software is open sourcewould, as many would argue, indicate that the code quality is expectedto be relatively high.

According to the book OpenSources: Voices from the Open Source Revolution by DiBona, Ockman, andStone (O’Reilly 1999) : ”by sharing sourcecode, Open Source developers make software more robust. Programs getused and tested in a wider variety of contexts than one programmercould generate, and bugs get uncovered that otherwise would not befound.”

Unfortunately, in a complex software application such as Apache, itis simply not feasible for all flaws to be found by manual inspection.There are a number of mechanisms available to help in the struggle toimprove software, including improved testing and design paradigms.

But automated source code analyzers are one of the most promisingtechnologies. Using a source code analyzer should be a required part ofevery software organization’s development process. Not only is iteffective at locating anomalies, but it can be used with little or noimpact on build times and easily integrated with the regular softwaredevelopment environment.

David N. Kleidermacher is VicePresident of Engineering at Green Hills Software, Inc.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.