secure programming practices
Security issues have crept into many systems over the years. This document is a
guide for programming practices that prevent these problems.
Writing secure applications takes a very scrutinous and pessimistic outlook.
Applications should be run with the principle of
” so that no process is
ever running with more than the bare minimum access it needs to accomplish its
function. Previously tested code should be reused whenever possible.
Generally, anything beyond the control of a program should never be trusted.
This includes all forms of user input, system resources, interprocess
communication, and the timing of events.
One of the most common types of security problems is the buffer overflow. In
short, if a program is not careful with the data it receives, it may be
possible for this data to be written across memory, overwriting the return
address for a function call, and the program will be forced to run code that
does unfriendly things.
A good number of functions in the standard C library make it difficult or even
impossible to prevent buffer overflows when used. These include
Many other functions that deal with strings can also open up a potential buffer
overflow when not used carefully. For example,
does not go out of its way to provide NUL character termination. Of course,
the proper length must always be specified. Usage of
ensure that strings are null terminated and of the specified length.
Functions that receive a string format must also be used carefully. It is
possible for a string to contain additional format specifiers, which open up
another possibility for a buffer overflow. Never pass a string with untrusted
data without using ‘
’. Always use the
proper secure idiom:
There are mechanisms that provide a backstop for these problems at the library
and compiler levels, however, there is no substitute for simply writing good
In many cases, it may be necessary for a program to operate with an increased
set of permissions. Reasons for this include binding to protected sockets,
reading and writing certain files and directories, and access to various
resources. Using a setuid program is frequently the solution. However, it is
important that programs give up these privileges as soon as possible. For
example, if a program is binding to a protected socket, it should give up its
privileges as soon as it has finished binding to that socket. This is
accomplished with the
family of system calls.
The traditional method of restricting a process is with the
system call. This system call changes the root directory from which all other
paths are referenced for a process and any child processes. Of course, the
process must have access to this path to begin with. The new environment does
not actually take effect until
is called to place the process into the new environment. Unfortunately, a
process can break out of this environment if root access is obtained.
can be used to create a more complete and enclosed environment than
can provide. A jail limits all processes inside that environment, including
processes with superuser privileges.
Fine grained privileges, as described by POSIX.1e extensions, are currently a
work in progress, and the focus of the TrustedBSD Project. More information
can be found at http://www.TrustedBSD.org/
Programs should not make assumptions about the environment in which they are
running. This includes user input, signals, environment variables, system
resources, interprocess communications, and shared memory, amongst other
things that are beyond the control of the program. They should not assume that
all forms of invalid data can be detected either. Instead, they should use
positive filtering, and only allow a specific subset of inputs that are known
to be safe. This is the same logic that an administrator should apply to a
firewall, that is, deny by default and specify what is to be accepted.
A race condition is anomalous behavior caused by the relative timing of events.
Programs should not assume that a particular event will occur before another.
The most common causes of race conditions are signals, access checks, and file
reads. Signals are asynchronous by nature, so special care must be taken while
dealing with them. Attempting to check access with sequential non-atomic
operations is a very bad idea, as files can be moved and changed at any given
time. Instead of using a sequence of
and then call
originally wrote this document based on a chapter of the
FreeBSD Developer's Handbook