By definition, mail software processes information from potentially
untrusted sources. Therefore, mail software must be written with
great care, even when it runs with user privileges, and even when
it does not talk directly to a network.
Postfix is a complex system. The initial release has about 30,000
lines of code (after deleting the comments). With a system that
complex, the security of the system should not depend on a single
mechanism. If it did, one single error would be sufficient to
compromise the entire mail system. Therefore, Postfix uses multiple
layers of defense to control the damage from software and other
Most Postfix daemon programs can be run at fixed low privilege in
a chrooted environment. This is especially true for the programs
that are exposed to the network: the SMTP server and SMTP client.
Although chroot(2), even when combined with low privilege, is no
guarantee against system compromise it does add a considerable
hurdle. And we all know that every little bit helps.
Postfix uses separate processes to insulate activities from each
other. In particular, there is no direct path from the network to
the security-sensitive local delivery programs. An intruder first
has to break through multiple programs. Some parts of the Postfix
system are multi-threaded. However, all programs that interact with
the outside world are single-threaded. Separate processes give
better insulation than multiple threads within a shared address
No Postfix mail delivery program runs under control of a user
process. Instead, most Postfix programs run under control of a
resident master daemon that runs in a controlled environment,
without any parent-child relationship to user processes. This
approach eliminates exploits that involve signals, open files,
environment variables, and other process attributes that the UNIX
system passes on from a possibly-malicious parent to a child.
No Postfix program is set-uid. Introducing the concept was
the biggest mistake made in UNIX history. Set-uid (and its
weaker cousin, set-gid) causes more trouble than it is worth.
Each time a new feature is added to the UNIX system, set-uid
causes a security problem: shared libraries, the /proc file
system, multi-language support, to mention just a few examples.
Set-uid makes it impossible to introduce some of the features
that make UNIX successors such as plan9 so attractive, for example,
per-process file system name spaces.
By default, the maildrop queue directory is world-writable,
so that local processes can submit mail without assistance from a
set-uid or set-gid command or from a mail daemon process. The
maildrop directory is not used for mail coming in via the network,
and queue files are not readable for other users.
A writable directory opens up opportunities for annoyance: a local
user can make hard links to someone else's maildrop files so they
don't go away and/or are delivered multiple times; a local user
can fill the maildrop directory with garbage and try to make the
mail system crash; and a local user can hard link someone else's
files into the maildrop directory and try to have them delivered
as mail. However, Postfix queue files have a specific format; less
than one in 10^12 non-Postfix files would be recognized as a valid
Postfix queue file.
If a world-writable maildrop directory is not acceptable,
sites can revoke world write permission, and enable set-gid
privileges for a small helper program that is provided for this
As mentioned elsewhere in the overview, Postfix programs do not
trust the contents of queue files or of the Postfix internal IPC
messages. Queue files have no on-disk record for deliveries to
sensitive destinations such as files or commands. Instead, programs
such as the local delivery agent attempt to make security-sensitive
decisions on the basis of first-hand information.
Of course, Postfix programs do not trust data received from the
network, either. In particular, Postfix filters sender-provided
data before exporting it via environment variables. If there is one
lesson that people have learned from Web site security disasters
it is this one: don't let any data from the network near a
shell. Filtering is the best we can do.
Memory for strings and buffers is allocated dynamically, in
order to prevent buffer overrun problems.
Long lines in message input are broken up into sequences of
reasonably-sized chunks, and are reconstructed upon delivery.
Diagnostics are truncated (in one single place!) before they
are passed to the syslog(3) interface, in order to prevent buffer
overruns on older platforms. However, no general attempt is made
to truncate data before it is passed to system calls or to library
routines. On some platforms, the software may still exhibit buffer
overrun problems, due to vulnerabilities in the underlying software.
No specific attempt is made to defend against unreasonably-long
command-line arguments. UNIX kernels impose their own limits, which
should be sufficient to deal with runaway programs or with malicious
The number of in-memory instances of any object type is limited,
to prevent the mail system from becoming wedged under heavy load.
In case of problems, the software pauses before sending an
error response to a client, before terminating with a fatal error,
or before attempting to restart a failed program. The purpose is
to prevent runaway conditions that only make problems worse.