Dereferencing a null pointer is undefined behavior.
On many platforms, dereferencing a null pointer results in abnormal program termination, but this is not required by the standard. See "Clever Attack Exploits Fully-Patched Linux Kernel" [Goodin 2009] for an example of a code execution exploit that resulted from a null pointer dereference.
Noncompliant Code Example
This noncompliant code example is derived from a real-world example
taken from a vulnerable version of the
libpng
library as deployed on a popular ARM-based cell phone [Jack
2007]. The
libpng
library allows applications to read, create, and manipulate PNG
(Portable Network Graphics) raster image files. The
libpng
library implements its own wrapper to
malloc()
that returns a null pointer on error or on being passed a
0-byte-length argument.
This code also violates ERR33-C. Detect and handle standard library errors.
#include <png.h> /*
From libpng */
#include <string.h>
void
func(png_structp png_ptr,
int
length,
const
void
*user_data) {
png_charp chunkdata;
chunkdata =
(png_charp)png_malloc(png_ptr, length + 1);
/* ... */
memcpy
(chunkdata, user_data, length);
/* ... */
}
|
If
length
has the value
−1
, the addition yields 0, and
png_malloc()
subsequently returns a null pointer, which is assigned to
chunkdata
. The
chunkdata
pointer is later used as a destination argument in a call to
memcpy()
, resulting in user-defined data overwriting memory starting at
address 0. In the case of the ARM and XScale architectures, the
0x0
address is mapped in memory and serves as the exception vector table;
consequently, dereferencing
0x0
did not cause an abnormal
program termination.
Compliant Solution
This compliant solution ensures that the pointer returned by
png_malloc()
is not null. It also uses the unsigned type
size_t
to pass the
length
parameter, ensuring that negative values are not passed to
func()
.
This solution also ensures that the
user_data
pointer is not null. Passing a null pointer to memcpy() would
produce undefined behavior, even if the number of bytes to copy were
0. The
user_data
pointer could be invalid in other ways, such as pointing to freed
memory. However there is no portable way to verify that the pointer is
valid, other than checking for null.
#include <png.h> /*
From libpng */
#include <string.h>
void
func(png_structp png_ptr,
size_t
length,
const
void
*user_data) {
png_charp chunkdata;
if
(length == SIZE_MAX) {
/* Handle error */
}
chunkdata =
(png_charp)png_malloc(png_ptr, length + 1);
if
(NULL == chunkdata) {
/* Handle error */
}
if
(NULL == user_data) {
/* Handle error */
}
/* ... */
memcpy
(chunkdata, user_data, length);
/* ... */
}
|
Noncompliant Code Example
In this noncompliant code example,
input_str
is copied into dynamically allocated memory referenced by
c_str
. If
malloc()
fails, it returns a null pointer that is assigned to
c_str
. When
is dereferenced in
c_str
memcpy()
, the program exhibits undefined
behavior. Additionally, if
input_str
is a null pointer, the call to
strlen()
dereferences a null pointer, also resulting in undefined
behavior. This code also violates ERR33-C.
Detect and handle standard library errors.
#include <string.h>
#include <stdlib.h>
void
f(
const
char
*input_str) {
size_t
size =
strlen
(input_str) + 1;
char
*c_str = (
char
*)
malloc
(size);
memcpy
(c_str, input_str, size);
/* ... */
free
(c_str);
c_str = NULL;
/* ... */
}
|
Compliant Solution
This compliant solution ensures that both
input_str
and the pointer returned by
malloc()
are not null:
#include <string.h>
#include <stdlib.h>
void
f(
const
char
*input_str) {
size_t
size;
char
*c_str;
if
(NULL == input_str) {
/* Handle error */
}
size =
strlen
(input_str) + 1;
c_str = (
char
*)
malloc
(size);
if
(NULL == c_str) {
/* Handle error */
}
memcpy
(c_str, input_str, size);
/* ... */
free
(c_str);
c_str = NULL;
/* ... */
}
|
Noncompliant Code Example
This noncompliant code example is from a version of
drivers/net/tun.c
and affects Linux kernel 2.6.30 [Goodin
2009]:
static
unsigned
int
tun_chr_poll(
struct
file *file, poll_table
*wait) {
struct
tun_file *tfile =
file->private_data;
struct
tun_struct *tun =
__tun_get(tfile);
struct
sock *sk = tun->sk;
unsigned
int
mask = 0;
if
(!tun)
return
POLLERR;
DBG(KERN_INFO
"%s: tun_chr_poll\n"
, tun->dev->name);
poll_wait(file,
&tun->socket.wait, wait);
if
(!skb_queue_empty(&tun->readq))
mask |= POLLIN | POLLRDNORM;
if
(sock_writeable(sk) ||
(!test_and_set_bit(SOCK_ASYNC_NOSPACE,
&sk->sk_socket->flags) &&
sock_writeable(sk)))
mask |= POLLOUT | POLLWRNORM;
if
(tun->dev->reg_state !=
NETREG_REGISTERED)
mask = POLLERR;
tun_put(tun);
return
mask;
}
|
The
sk
pointer is initialized to
tun->sk
before checking if
tun
is a null pointer. Because null pointer dereferencing is undefined
behavior, the compiler (GCC in this case) can optimize away the
if (!tun)
check because it is performed after
tun->sk
is accessed, implying that
tun
is non-null. As a result, this noncompliant code example is vulnerable
to a null pointer dereference exploit, because null pointer
dereferencing can be permitted on several platforms, for example, by
using
mmap(2)
with the
MAP_FIXED
flag on Linux and Mac OS X, or by using the
shmat()
POSIX function with the
SHM_RND
flag [Liu
2009].
Compliant Solution
This compliant solution eliminates the null pointer deference by
initializing
sk
to
tun->sk
following the null pointer check. It also adds assertions to document
that certain other pointers must not be null.
static
unsigned
int
tun_chr_poll(
struct
file *file, poll_table
*wait) {
assert
(file);
struct
tun_file *tfile =
file->private_data;
struct
tun_struct *tun =
__tun_get(tfile);
struct
sock *sk;
unsigned
int
mask = 0;
if
(!tun)
return
POLLERR;
assert
(tun->dev);
sk = tun->sk;
assert
(sk);
assert
(sk->socket);
/* The remaining code is
omitted because it is unchanged... */
}
|