But you should must not dynamically construct format strings
to avoid concatenation because this would prevent GCC from
type-checking the argument lists.
It is not possible to use format strings like
`"%s%s"` to implement concatenation, unless
you use separate buffers. `snprintf` does
not support overlapping source and target strings.
==== `strlcpy` and `strlcat`
Some systems support `strlcpy` and
`strlcat` functions which behave this way,
but these functions are not part of GNU libc.
`strlcpy` is often replaced with
`snprintf` with a `"%s"`
format string. See <<sect-Defensive_Coding-C-Libc-strncpy>> for a caveat
related to the `snprintf` return value.
To emulate `strlcat`, use the approach
described in <<sect-Defensive_Coding-C-Libc-strncat>>.
==== ISO C11 Annex K *pass:attributes[{blank}]`_s` functions
ISO C11 adds another set of length-checking functions, but GNU
libc currently does not implement them.
==== Other `strn*` and `stpn*` functions
GNU libc contains additional functions with different variants
of length checking. Consult the documentation before using
them to find out what the length actually means.
=== Using tricky syscalls or library functions
==== `readlink`
This is the hardest system call to use correctly because of everything you have to do
* The buf should be of PATH_MAX length, that includes space for the terminating NUL character.
* The bufsize should be `sizeof(buf) - 1`
* `readlink` return value should be caught as a signed integer (ideally type `ssize_t`).
* It should be checked for < 0 for indication of errors.
* The caller needs to '\0' -terminate the buffer using the returned value as an index.
==== `chroot`
* Target dir should be writable only by root (this implies owned by).
* Must call `chdir` immediately after chroot or you are not really in the changed root.
==== `stat`, `lstat`, `fstatat`
* These functions have an inherent race in that you operate on the path name which could change in the mean time. Using fstat is recommended when stat is used.
* If `S_ISLNK` macro is used, the stat buffer MUST come from lstat or from fstatat with `AT_SYMLINK_NOFOLLOW`
* If you are doing something really important, call fstat after opening and compare the before and after stat buffers before trusting them.
==== `setgid`, `setuid`:
* Call these in the right order: groups and then uid.
* Always check the return code.
* If `setgid` & `setuid` are used, supplemental groups are not reset. This must be done with setgroups or initgroups before the uid change.
[[sect-Defensive_Coding-C-Allocators]]
== Memory Allocators
=== `malloc` and Related Functions
The C library interfaces for memory allocation are provided by
`malloc`, `free` and
`realloc`, and the
`calloc` function. In addition to these
generic functions, there are derived functions such as
`strdup` which perform allocation using
`malloc` internally, but do not return
untyped heap memory (which could be used for any object).
The C compiler knows about these functions and can use their
expected behavior for optimizations. For instance, the compiler
assumes that an existing pointer (or a pointer derived from an
existing pointer by arithmetic) will not point into the memory
area returned by `malloc`.
If the allocation fails, `realloc` does not
free the old pointer. Therefore, the idiom `ptr =
realloc(ptr, size);` is wrong because the memory
pointed to by `ptr` leaks in case of an error.
[[sect-Defensive_Coding-C-Use-After-Free]]
==== Use-after-free errors
After `free`, the pointer is invalid.
Further pointer dereferences are not allowed (and are usually
detected by [application]*valgrind*). Less obvious
is that any *use* of the old pointer value is
not allowed, either. In particular, comparisons with any other
pointer (or the null pointer) are undefined according to the C
standard.
The same rules apply to `realloc` if the
memory area cannot be enlarged in-place. For instance, the
compiler may assume that a comparison between the old and new
pointer will always return false, so it is impossible to detect
movement this way.
On a related note, `realloc` frees the memory area if the new size is
zero. If the size unintentionally becomes zero, as a result of
unsigned integer wrap-around for instance, the following idiom causes
a double-free.
[source,c]
----
new_size = size + x; /* 'x' is a very large value and the result wraps around to zero */
new_ptr = realloc(ptr, new_size);
if (!new_ptr) {
free(ptr);
}
----
==== Handling Memory Allocation Errors
Recovering from out-of-memory errors is often difficult or even
impossible. In these cases, `malloc` and
other allocation functions return a null pointer. Dereferencing
this pointer lead to a crash. Such dereferences can even be
exploitable for code execution if the dereference is combined
with an array subscript.
In general, if you cannot check all allocation calls and
handle failure, you should abort the program on allocation
failure, and not rely on the null pointer dereference to
terminate the process. See
xref:tasks/Tasks-Serialization.adoc#sect-Defensive_Coding-Tasks-Serialization-Decoders[Recommendations for Manually-written Decoders]
for related memory allocation concerns.
[[sect-Defensive_Coding-C-Allocators-alloca]]
=== `alloca` and Other Forms of Stack-based Allocation
Allocation on the stack is risky because stack overflow checking
is implicit. There is a guard page at the end of the memory
area reserved for the stack. If the program attempts to read
from or write to this guard page, a `SIGSEGV`
signal is generated and the program typically terminates.
This is sufficient for detecting typical stack overflow
situations such as unbounded recursion, but it fails when the
stack grows in increments larger than the size of the guard
page. In this case, it is possible that the stack pointer ends
up pointing into a memory area which has been allocated for a
different purposes. Such misbehavior can be exploitable.
A common source for large stack growth are calls to
`alloca` and related functions such as
`strdupa`. These functions should be avoided
because of the lack of error checking. (They can be used safely
if the allocated size is less than the page size (typically,
4096 bytes), but this case is relatively rare.) Additionally,
relying on `alloca` makes it more difficult
to reorganize the code because it is not allowed to use the
pointer after the function calling `alloca`
has returned, even if this function has been inlined into its
caller.
Similar concerns apply to *variable-length
arrays* (VLAs), a feature of the C99 standard which
started as a GNU extension. For large objects exceeding the
page size, there is no error checking, either.
In both cases, negative or very large sizes can trigger a
stack-pointer wraparound, and the stack pointer and end up
pointing into caller stack frames, which is fatal and can be
exploitable.
If you want to use `alloca` or VLAs for
performance reasons, consider using a small on-stack array (less
than the page size, large enough to fulfill most requests). If
the requested size is small enough, use the on-stack array.
Otherwise, call `malloc`. When exiting the
function, check if `malloc` had been called,
and free the buffer as needed.
If portability is not important in your program, an alternative way of
automatic memory management is to leverage the `cleanup` attribute
supported by the recent versions of GCC and Clang. If a local variable
is declared with the attribute, the specified cleanup function will be
called when the variable goes out of scope.
[source,c]
----
static inline void freep(void *p) {
free(*(void**) p);
}
void somefunction(const char *param) {
if (strcmp(param, "do_something_complex") == 0) {
__attribute__((cleanup(freep))) char *ptr = NULL;
/* Allocate a temporary buffer */
ptr = malloc(size);
/* Do something on it, but do not need to manually call free() */
}
}
----
[[sect-Defensive_Coding-C-Allocators-Arrays]]
=== Array Allocation
When allocating arrays, it is important to check for overflows.
The `calloc` function performs such checks.
If `malloc` or `realloc`
is used, the size check must be written manually. For instance,
to allocate an array of `n` elements of type
`T`, check that the requested size is not
greater than `((size_t) -1) / sizeof(T)`. See
<<sect-Defensive_Coding-C-Arithmetic>>.
GNU libc provides a dedicated function `reallocarray` that allocates
an array with those checks performed internally. However, care must
be taken if portability is important: while the interface originated
in OpenBSD and has been adopted in many other platforms, NetBSD
exposes an incompatible behavior with the same interface.
[[sect-Defensive_Coding-C-Allocators-Custom]]
=== Custom Memory Allocators
Custom memory allocates come in two forms: replacements for
`malloc`, and completely different interfaces
for memory management. Both approaches can reduce the
effectiveness of [application]*valgrind* and similar
tools, and the heap corruption detection provided by GNU libc, so
they should be avoided.
Memory allocators are difficult to write and contain many
performance and security pitfalls.
* When computing array sizes or rounding up allocation
requests (to the next allocation granularity, or for
alignment purposes), checks for arithmetic overflow are
required.
* Size computations for array allocations need overflow
checking. See <<sect-Defensive_Coding-C-Allocators-Arrays>>.
* It can be difficult to beat well-tuned general-purpose
allocators. In micro benchmarks, pool allocators can show
huge wins, and size-specific pools can reduce internal
fragmentation. But often, utilization of individual pools
is poor, and external fragmentation increases the overall
memory usage.
=== Conservative Garbage Collection
Garbage collection can be an alternative to explicit memory
management using `malloc` and
`free`. The Boehm-Dehmers-Weiser allocator
can be used from C programs, with minimal type annotations.
Performance is competitive with `malloc` on
64-bit architectures, especially for multi-threaded programs.
The stop-the-world pauses may be problematic for some real-time
applications, though.
However, using a conservative garbage collector may reduce
opportunities for code reduce because once one library in a
program uses garbage collection, the whole process memory needs
to be subject to it, so that no pointers are missed. The
Boehm-Dehmers-Weiser collector also reserves certain signals for
internal use, so it is not fully transparent to the rest of the
program.
[[sect-Defensive_Coding-C-Other]]
== Other C-related Topics
[[sect-Defensive_Coding-C-Wrapper-Functions]]
=== Wrapper Functions
Some libraries provide wrappers for standard library functions.
Common cases include allocation functions such as
`xmalloc` which abort the process on
allocation failure (instead of returning a
`NULL` pointer), or alternatives to relatively
recent library additions such as `snprintf`
(along with implementations for systems which lack them).
In general, such wrappers are a bad idea, particularly if they
are not implemented as inline functions or preprocessor macros.
The compiler lacks knowledge of such wrappers outside the
translation unit which defines them, which means that some
optimizations and security checks are not performed. Adding
`__attribute__` annotations to function
declarations can remedy this to some extent, but these
annotations have to be maintained carefully for feature parity
with the standard implementation.
At the minimum, you should apply these attributes:
* If you wrap function which accepts are GCC-recognized format
string (for example, a `printf`-style
function used for logging), you should add a suitable
`format` attribute, as in <<ex-Defensive_Coding-C-String-Functions-format-Attribute>>.
* If you wrap a function which carries a
`warn_unused_result` attribute and you
propagate its return value, your wrapper should be declared
with `warn_unused_result` as well.
* Duplicating the buffer length checks based on the
`__builtin_object_size` GCC builtin is
desirable if the wrapper processes arrays. (This
functionality is used by the
`-D_FORTIFY_SOURCE=2` checks to guard
against static buffer overflows.) However, designing
appropriate interfaces and implementing the checks may not
be entirely straightforward.
For other attributes (such as `malloc`),
careful analysis and comparison with the compiler documentation
is required to check if propagating the attribute is
appropriate. Incorrectly applied attributes can result in
undesired behavioral changes in the compiled code.
[[sect-Defensive_Coding-C-Common-Mistakes]]
=== Common mistakes
==== Mistakes in macros
A macro is a name given to a block of C statements as a pre-processor
directive. Being a pre-processor the block of code is transformed by
the compiler before being compiled.
A macro starts with the preprocessor directive, #define. It can
define a single value or any 'substitution', syntactically valid or
not.
A common mistake when working with macros is that programmers treat
arguments to macros like they would functions. This becomes an issue
when the argument may be expanded multiple times in a macro.
For example:
macro-misuse.c
[source,C]
----
#define simple(thing) do { \
if (thing < 1) { \
y = thing; \
} \
else if (thing > 100) { \
y = thing * 2 + thing; \
} \
else { \
y = 200; \
} \
} while (0)
int main(void) {
int x = 200;
int y = 0;
simple(x++);
return 0;
}
----
Each pass through the simple() macro would mean that x could be
expanded in-place each time 'thing' was mentioned.
The 'main' function would be processed and expanded as follows: