Unchecked Return Value to NULL Pointer Dereference
Definition in a New Window
Compound Element ID: 690 (Compound Element Base: Chain)
Status: Draft
Description
Description Summary
The product does not check for an error after calling a function that can return with a NULL pointer if the function fails, which leads to a resultant NULL pointer dereference.
Extended Description
While unchecked return value weaknesses are not limited to returns of NULL pointers (see the examples in CWE-252), functions often return NULL to indicate an error status. When this error condition is not checked, a NULL pointer dereference can occur.
Applicable Platforms
Languages
C
C++
Common Consequences
Scope
Effect
Availability
Technical Impact: DoS: crash / exit /
restart
Detection Methods
Black Box
This typically occurs in rarely-triggered error conditions, reducing
the chances of detection during black box testing.
White Box
Code analysis can require knowledge of API behaviors for library
functions that might return NULL, reducing the chances of detection when
unknown libraries are used.
Demonstrative Examples
Example 1
The code below makes a call to the getUserName() function but
doesn't check the return value before dereferencing (which may cause a
NullPointerException).
(Bad Code)
Example
Language: Java
String username = getUserName();
if (username.equals(ADMIN_USER)) {
...
}
Example 2
This example takes an IP address from a user, verifies that it is
well formed and then looks up the hostname and copies it into a
buffer.
(Bad Code)
Example
Language: C
void host_lookup(char *user_supplied_addr){
struct hostent *hp;
in_addr_t *addr;
char hostname[64];
in_addr_t inet_addr(const char *cp);
/*routine that ensures user_supplied_addr is in the right
format for conversion */
validate_addr_form(user_supplied_addr);
addr = inet_addr(user_supplied_addr);
hp = gethostbyaddr( addr, sizeof(struct in_addr),
AF_INET);
strcpy(hostname, hp->h_name);
}
If an attacker provides an address that appears to be well-formed, but the address does not resolve to a hostname, then the call to gethostbyaddr() will return NULL. Since the code does not check the return value from gethostbyaddr (CWE-252), a NULL pointer dereference (CWE-476) would then occur in the call to strcpy().
Note that this example is also vulnerable to a buffer overflow (see CWE-119).
URI parsing API sets argument to NULL when a
parsing failure occurs, such as when the Referer header is missing a
hostname, leading to NULL dereference.
chain: unchecked return value can lead to NULL
dereference
Other Notes
A typical occurrence of this weakness occurs when an application includes
user-controlled input to a malloc() call. The related code might be correct
with respect to preventing buffer overflows, but if a large value is
provided, the malloc() will fail due to insufficient memory. This problem
also frequently occurs when a parsing routine expects that certain elements
will always be present. If malformed input is provided, the parser might
return NULL. For example, strtok() can return NULL.
A NULL pointer dereference occurs when the application dereferences a pointer that it expects to be valid, but is NULL, typically causing a crash or exit.
Extended Description
NULL pointer dereference issues can occur through a number of flaws, including race conditions, and simple programming omissions.
Time of Introduction
Implementation
Applicable Platforms
Languages
C
C++
Java
.NET
Common Consequences
Scope
Effect
Availability
Technical Impact: DoS: crash / exit /
restart
NULL pointer dereferences usually result in the failure of the process
unless exception handling (on some platforms) is available and
implemented. Even when exception handling is being used, it can still be
very difficult to return the software to a safe state of
operation.
Integrity
Confidentiality
Availability
Technical Impact: Execute unauthorized code or
commands
In very rare circumstances and environments, code execution is
possible.
Likelihood of Exploit
Medium
Detection Methods
Automated Dynamic Analysis
This weakness can be detected using dynamic tools and techniques that
interact with the software using large test suites with many diverse
inputs, such as fuzz testing (fuzzing), robustness testing, and fault
injection. The software's operation may slow down, but it should not
become unstable, crash, or generate incorrect results.
Effectiveness: Moderate
Manual Dynamic Analysis
Identify error conditions that are not likely to occur during normal
usage and trigger them. For example, run the program under low memory
conditions, run with insufficient privileges or permissions, interrupt a
transaction before it is completed, or disable connectivity to basic
network services such as DNS. Monitor the software for any unexpected
behavior. If you trigger an unhandled exception or similar error that
was discovered and handled by the application's environment, it may
still indicate unexpected conditions that were not handled by the
application itself.
Demonstrative Examples
Example 1
While there are no complete fixes aside from conscientious
programming, the following steps will go a long way to ensure that NULL
pointer dereferences do not occur.
(Mitigation Code)
if (pointer1 != NULL) {
/* make use of pointer1 */
/* ... */
}
If you are working with a multithreaded or otherwise asynchronous
environment, ensure that proper locking APIs are used to lock before the
if statement; and unlock when it has finished.
Example 2
This example takes an IP address from a user, verifies that it is
well formed and then looks up the hostname and copies it into a
buffer.
(Bad Code)
Example
Language: C
void host_lookup(char *user_supplied_addr){
struct hostent *hp;
in_addr_t *addr;
char hostname[64];
in_addr_t inet_addr(const char *cp);
/*routine that ensures user_supplied_addr is in the right
format for conversion */
validate_addr_form(user_supplied_addr);
addr = inet_addr(user_supplied_addr);
hp = gethostbyaddr( addr, sizeof(struct in_addr),
AF_INET);
strcpy(hostname, hp->h_name);
}
If an attacker provides an address that appears to be well-formed, but the address does not resolve to a hostname, then the call to gethostbyaddr() will return NULL. Since the code does not check the return value from gethostbyaddr (CWE-252), a NULL pointer dereference would then occur in the call to strcpy().
Note that this example is also vulnerable to a buffer overflow (see CWE-119).
Example 3
In the following code, the programmer assumes that the system always
has a property named "cmd" defined. If an attacker can control the program's
environment so that "cmd" is not defined, the program throws a NULL pointer
exception when it attempts to call the trim() method.
race condition causes a table to be corrupted if a
timer activates while it is being modified, leading to resultant NULL
dereference; also involves locking.
If all pointers that could have been modified are sanity-checked
previous to use, nearly all NULL pointer dereferences can be
prevented.
Phase: Requirements
The choice could be made to use a language that is not susceptible to
these issues.
Phase: Implementation
Check the results of all functions that return a value and verify that
the value is non-null before acting upon it.
Effectiveness: Moderate
Checking the return value of the function will typically be sufficient, however beware of race conditions (CWE-362) in a concurrent environment.
This solution does not handle the use of improperly initialized variables (CWE-665).
Phase: Architecture and Design
Identify all variables and data stores that receive information from
external sources, and apply input validation to make sure that they are
only initialized to expected values.
Phase: Implementation
Explicitly initialize all your variables and other data stores, either
during declaration or just before the first usage.
Phase: Testing
Use automated static analysis tools that target this type of weakness.
Many modern techniques use data flow analysis to minimize the number of
false positives. This is not a perfect solution, since 100% accuracy and
coverage are not feasible.
Weakness Ordinalities
Ordinality
Description
Resultant
NULL pointer dereferences are frequently resultant from rarely
encountered error conditions, since these are most likely to escape
detection during the testing phases.
The software does not check the return value from a method or function, which can prevent it from detecting unexpected states and conditions.
Extended Description
Two common programmer assumptions are "this function call can never fail" and "it doesn't matter if this function call fails". If an attacker can force the function to fail or otherwise return a value that is not expected, then the subsequent program logic could lead to a vulnerability, because the software is not in a state that the programmer assumes. For example, if the program calls a function to drop privileges but does not check the return code to ensure that privileges were successfully dropped, then the program will continue to operate with the higher privileges.
The data which were produced as a result of a function call could be
in a bad state upon return. If the return value is not checked, then
this bad data may be used in operations and lead to a crash or other
unintended behaviors.
Likelihood of Exploit
Low
Demonstrative Examples
Example 1
Consider the following code segment:
(Bad Code)
Example
Language: C
char buf[10], cp_buf[10];
fgets(buf, 10, stdin);
strcpy(cp_buf, buf);
The programmer expects that when fgets() returns, buf will contain a
null-terminated string of length 9 or less. But if an I/O error occurs,
fgets() will not null-terminate buf. Furthermore, if the end of the file
is reached before any characters are read, fgets() returns without
writing anything to buf. In both of these situations, fgets() signals
that something unusual has happened by returning NULL, but in this code,
the warning will not be noticed. The lack of a null terminator in buf
can result in a buffer overflow in the subsequent call to strcpy().
Example 2
The following code does not check to see if memory allocation
succeeded before attempting to use the pointer returned by
malloc().
(Bad Code)
Example
Language: C
buf = (char*) malloc(req_size);
strncpy(buf, xfer, req_size);
The traditional defense of this coding error is: "If my program runs
out of memory, it will fail. It doesn't matter whether I handle the
error or simply allow the program to die with a segmentation fault when
it tries to dereference the null pointer." This argument ignores three
important considerations:
Depending upon the type and size of the application, it may be
possible to free memory that is being used elsewhere so that
execution can continue.
It is impossible for the program to perform a graceful exit if
required. If the program is performing an atomic operation, it can
leave the system in an inconsistent state.
The programmer has lost the opportunity to record diagnostic
information. Did the call to malloc() fail because req_size was too
large or because there were too many requests being handled at the
same time? Or was it caused by a memory leak that has built up over
time? Without handling the error, there is no way to know.
Example 3
The following code loops through a set of users, reading a private
data file for each user. The programmer assumes that the files are always 1
kilobyte in size and therefore ignores the return value from Read(). If an
attacker can create a smaller file, the program will recycle the remainder
of the data from the previous user and treat it as though it belongs to the
attacker.
(Bad Code)
Example
Language: Java
char[] byteArray = new char[1024];
for (IEnumerator i=users.GetEnumerator(); i.MoveNext()
;i.Current()) {
String userName = (String) i.Current();
String pFileName = PFILE_ROOT + "/" + userName;
StreamReader sr = new StreamReader(pFileName);
sr.Read(byteArray,0,1024);//the file is always 1k bytes
sr.Close();
processPFile(userName, byteArray);
}
(Bad Code)
Example
Language: Java
FileInputStream fis;
byte[] byteArray = new byte[1024];
for (Iterator i=users.iterator(); i.hasNext();) {
String userName = (String) i.next();
String pFileName = PFILE_ROOT + "/" + userName;
FileInputStream fis = new FileInputStream(pFileName);
fis.read(byteArray); // the file is always 1k bytes
fis.close();
processPFile(userName, byteArray);
Example 4
The following code does not check to see if the string returned by
getParameter() is null before calling the member function compareTo(),
potentially causing a NULL dereference.
The following code does not check to see if the string returned by
theItem property is null before calling the member function Equals(),
potentially causing a NULL dereference. string itemName =
request.Item(ITEM_NAME);
(Bad Code)
if (itemName.Equals(IMPORTANT_ITEM)) {
...
}
...
The traditional defense of this coding error is: "I know the requested
value will always exist because.... If it does not exist, the program
cannot perform the desired behavior so it doesn't matter whether I
handle the error or simply allow the program to die dereferencing a null
value." But attackers are skilled at finding unexpected paths through
programs, particularly when exceptions are involved.
Example 5
The following code shows a system property that is set to null and
later dereferenced by a programmer who mistakenly assumes it will always be
defined.
(Bad Code)
System.clearProperty("os.name");
...
String os = System.getProperty("os.name");
if (os.equalsIgnoreCase("Windows 95")) System.out.println("Not
supported");
The traditional defense of this coding error is: "I know the requested
value will always exist because.... If it does not exist, the program
cannot perform the desired behavior so it doesn't matter whether I
handle the error or simply allow the program to die dereferencing a null
value." But attackers are skilled at finding unexpected paths through
programs, particularly when exceptions are involved.
Example 6
The following VB.NET code does not check to make sure that it has
read 50 bytes from myfile.txt. This can cause DoDangerousOperation() to
operate on an unexpected value.
(Bad Code)
Dim MyFile As New FileStream("myfile.txt", FileMode.Open,
FileAccess.Read, FileShare.Read)
Dim MyArray(50) As Byte
MyFile.Read(MyArray, 0, 50)
DoDangerousOperation(MyArray(20))
In .NET, it is not uncommon for programmers to misunderstand Read()
and related methods that are part of many System.IO classes. The stream
and reader classes do not consider it to be unusual or exceptional if
only a small amount of data becomes available. These classes simply add
the small amount of data to the return buffer, and set the return value
to the number of bytes or characters read. There is no guarantee that
the amount of data returned is equal to the amount of data requested.
Example 7
It is not uncommon for Java programmers to misunderstand read() and
related methods that are part of many java.io classes. Most errors and
unusual events in Java result in an exception being thrown. But the stream
and reader classes do not consider it unusual or exceptional if only a small
amount of data becomes available. These classes simply add the small amount
of data to the return buffer, and set the return value to the number of
bytes or characters read. There is no guarantee that the amount of data
returned is equal to the amount of data requested. This behavior makes it
important for programmers to examine the return value from read() and other
IO methods to ensure that they receive the amount of data they
expect.
Example 8
This example takes an IP address from a user, verifies that it is
well formed and then looks up the hostname and copies it into a
buffer.
(Bad Code)
Example
Language: C
void host_lookup(char *user_supplied_addr){
struct hostent *hp;
in_addr_t *addr;
char hostname[64];
in_addr_t inet_addr(const char *cp);
/*routine that ensures user_supplied_addr is in the right
format for conversion */
validate_addr_form(user_supplied_addr);
addr = inet_addr(user_supplied_addr);
hp = gethostbyaddr( addr, sizeof(struct in_addr),
AF_INET);
strcpy(hostname, hp->h_name);
}
If an attacker provides an address that appears to be well-formed, but the address does not resolve to a hostname, then the call to gethostbyaddr() will return NULL. When this occurs, a NULL pointer dereference (CWE-476) will occur in the call to strcpy().
Note that this example is also vulnerable to a buffer overflow (see CWE-119).
Example 9
The following function attempts to acquire a lock in order to
perform operations on a shared resource.
(Bad Code)
Example
Language: C
void f(pthread_mutex_t *mutex) {
pthread_mutex_lock(mutex);
/* access shared resource */
pthread_mutex_unlock(mutex);
}
However, the code does not check the value returned by
pthread_mutex_lock() for errors. If pthread_mutex_lock() cannot acquire
the mutex for any reason the function may introduce a race condition
into the program and result in undefined behavior.
In order to avoid data races correctly written programs must check the
result of thread synchronization functions and appropriately handle all
errors, either by attempting to recover from them or reporting them to
higher levels.
Program does not check return value when invoking
functions to drop privileges, which could leave users with higher privileges
than expected by forcing those functions to
fail.
Program does not check return value when invoking
functions to drop privileges, which could leave users with higher privileges
than expected by forcing those functions to
fail.
chain: unchecked return value (CWE-252) leads to free of invalid, uninitialized pointer (CWE-824).
Potential Mitigations
Phase: Implementation
Check the results of all functions that return a value and verify that
the value is expected.
Effectiveness: High
Checking the return value of the function will typically be sufficient, however beware of race conditions (CWE-362) in a concurrent environment.
Phase: Implementation
Ensure that you account for all possible return values from the
function.
Phase: Implementation
When designing a function, make sure you return a value or throw an
exception in case of an error.
Background Details
Many functions will return some value about the success of their actions.
This will alert the program whether or not to handle any errors caused by
that function.
[REF-7] Mark Dowd, John McDonald
and Justin Schuh. "The Art of Software Security Assessment". Chapter 7, "Program Building Blocks" Page
341.. 1st Edition. Addison Wesley. 2006.
[REF-11] M. Howard and
D. LeBlanc. "Writing Secure Code". Chapter 20, "Checking Returns" Page 624. 2nd Edition. Microsoft. 2002.