This chapter discusses a number of issues concerned with security, some of which are also covered in other parts of this manual.
For reasons that this author does not understand, some people have promoted Exim as a “particularly secure” mailer. Perhaps it is because of the existence of this chapter in the documentation. However, the intent of the chapter is simply to describe the way Exim works in relation to certain security concerns, not to make any specific claims about the effectiveness of its security as compared with other MTAs.
What follows is a description of the way Exim is supposed to be. Best efforts have been made to try to ensure that the code agrees with the theory, but an absence of bugs can never be guaranteed. Any that are reported will get fixed as soon as possible.
There are a number of build-time options that can be set in Local/Makefile to create Exim binaries that are “harder” to attack, in particular by a rogue Exim administrator who does not have the root password, or by someone who has penetrated the Exim (but not the root) account. These options are as follows:
ALT_CONFIG_PREFIX can be set to a string that is required to match the start of any file names used with the -C option. When it is set, these file names are also not allowed to contain the sequence “/../”. (However, if the value of the -C option is identical to the value of CONFIGURE_FILE in Local/Makefile, Exim ignores -C and proceeds as usual.) There is no default setting for ALT_CONFIG_PREFIX.
If the permitted configuration files are confined to a directory to which only root has access, this guards against someone who has broken into the Exim account from running a privileged Exim with an arbitrary configuration file, and using it to break into other accounts.
The Exim binary is normally setuid to root, which means that it gains root privilege (runs as root) when it starts execution. In some special cases (for example, when the daemon is not in use and there are no local deliveries), it may be possible to run Exim setuid to some user other than root. This is discussed in the next section. However, in most installations, root privilege is required for two things:
It is not necessary to be root to do any of the other things Exim does, such as receiving messages and delivering them externally over SMTP, and it is obviously more secure if Exim does not run as root except when necessary. For this reason, a user and group for Exim to use must be defined in Local/Makefile. These are known as “the Exim user” and “the Exim group”. Their values can be changed by the run time configuration, though this is not recommended. Often a user called exim is used, but some sites use mail or another user name altogether.
Exim uses setuid() whenever it gives up root privilege. This is a permanent abdication; the process cannot regain root afterwards. Prior to release 4.00, seteuid() was used in some circumstances, but this is no longer the case.
After a new Exim process has interpreted its command line options, it changes uid and gid in the following cases:
The processes that initially retain root privilege behave as follows:
A delivery process retains root privilege throughout most of its execution, but any actual deliveries (that is, the transports themselves) are run in subprocesses which always change to a non-root uid and gid. For local deliveries this is typically the uid and gid of the owner of the mailbox; for remote deliveries, the Exim uid and gid are used. Once all the delivery subprocesses have been run, a delivery process changes to the Exim uid and gid while doing post-delivery tidying up such as updating the retry database and generating bounce and warning messages.
While the recipient addresses in a message are being routed, the delivery process runs as root. However, if a user’s filter file has to be processed, this is done in a subprocess that runs under the individual user’s uid and gid. A system filter is run as root unless system_filter_user is set.
Some installations like to run Exim in an unprivileged state for more of its operation, for added security. Support for this mode of operation is provided by the global option deliver_drop_privilege. When this is set, the uid and gid are changed to the Exim user and group at the start of a delivery process (and also queue runner and address testing processes). This means that address routing is no longer run as root, and the deliveries themselves cannot change to any other uid.
Leaving the binary setuid to root, but setting deliver_drop_privilege means that the daemon can still be started in the usual way, and it can respond correctly to SIGHUP because the re-invocation regains root privilege.
An alternative approach is to make Exim setuid to the Exim user and also setgid to the Exim group. If you do this, the daemon must be started from a root process. (Calling Exim from a root process makes it behave in the way it does when it is setuid root.) However, the daemon cannot restart itself after a SIGHUP signal because it cannot regain privilege.
It is still useful to set deliver_drop_privilege in this case, because it stops Exim from trying to re-invoke itself to do a delivery after a message has been received. Such a re-invocation is a waste of resources because it has no effect.
If restarting the daemon is not an issue (for example, if mua_wrapper is set, or inetd is being used instead of a daemon), having the binary setuid to the Exim user seems a clean approach, but there is one complication:
In this style of operation, Exim is running with the real uid and gid set to those of the calling process, and the effective uid/gid set to Exim’s values. Ideally, any association with the calling process’ uid/gid should be dropped, that is, the real uid/gid should be reset to the effective values so as to discard any privileges that the caller may have. While some operating systems have a function that permits this action for a non-root effective uid, quite a number of them do not. Because of this lack of standardization, Exim does not address this problem at this time.
For this reason, the recommended approach for “mostly unprivileged” running is to keep the Exim binary setuid to root, and to set deliver_drop_privilege. This also has the advantage of allowing a daemon to be used in the most straightforward way.
If you configure Exim not to run delivery processes as root, there are a number of restrictions on what you can do:
Unless the local user mailboxes are all owned by the Exim user (possible in some POP3 or IMAP-only environments):
These restrictions severely restrict what can be done in local deliveries. However, there are no restrictions on remote deliveries. If you are running a gateway host that does no local deliveries, setting deliver_drop_privilege gives more security at essentially no cost.
If you are using the mua_wrapper facility (see chapter 47), deliver_drop_privilege is forced to be true.
Full details of the checks applied by appendfile before it writes to a file are given in chapter 26.
Many operating systems suppress IP source-routed packets in the kernel, but some cannot be made to do this, so Exim does its own check. It logs incoming IPv4 source-routed TCP calls, and then drops them. Things are all different in IPv6. No special checking is currently done.
Support for these SMTP commands is disabled by default. If required, they can be enabled by defining suitable ACLs.
Exim recognises two sets of users with special privileges. Trusted users are able to submit new messages to Exim locally, but supply their own sender addresses and information about a sending host. For other users submitting local messages, Exim sets up the sender address from the uid, and doesn’t permit a remote host to be specified.
However, an untrusted user is permitted to use the -f command line option in the special form -f <> to indicate that a delivery failure for the message should not cause an error report. This affects the message’s envelope, but it does not affect the Sender: header. Untrusted users may also be permitted to use specific forms of address with the -f option by setting the untrusted_set_sender option.
Trusted users are used to run processes that receive mail messages from some other mail domain and pass them on to Exim for delivery either locally, or over the Internet. Exim trusts a caller that is running as root, as the Exim user, as any user listed in the trusted_users configuration option, or under any group listed in the trusted_groups option.
Admin users are permitted to do things to the messages on Exim’s queue. They can freeze or thaw messages, cause them to be returned to their senders, remove them entirely, or modify them in various ways. In addition, admin users can run the Exim monitor and see all the information it is capable of providing, which includes the contents of files on the spool.
By default, the use of the -M and -q options to cause Exim to attempt delivery of messages on its queue is restricted to admin users. This restriction can be relaxed by setting the no_prod_requires_admin option. Similarly, the use of -bp (and its variants) to list the contents of the queue is also restricted to admin users. This restriction can be relaxed by setting no_queue_list_requires_admin.
Exim recognises an admin user if the calling process is running as root or as the Exim user or if any of the groups associated with the calling process is the Exim group. It is not necessary actually to be running under the Exim group. However, if admin users who are not root or the Exim user are to access the contents of files on the spool via the Exim monitor (which runs unprivileged), Exim must be built to allow group read access to its spool files.
Exim’s spool directory and everything it contains is owned by the Exim user and set to the Exim group. The mode for spool files is defined in the Local/Makefile configuration file, and defaults to 0640. This means that any user who is a member of the Exim group can access these files.
Exim examines the last component of argv[0], and if it matches one of a set of specific strings, Exim assumes certain options. For example, calling Exim with the last component of argv[0] set to “rsmtp” is exactly equivalent to calling it with the option -bS. There are no security implications in this.
The only use made of “%f” by Exim is in formatting load average values. These are actually stored in integer variables as 1000 times the load average. Consequently, their range is limited and so therefore is the length of the converted output.
Exim uses its own path name, which is embedded in the code, only when it needs to re-exec in order to regain root privilege. Therefore, it is not root when it does so. If some bug allowed the path to get overwritten, it would lead to an arbitrary program’s being run as exim, not as root.
A large number of occurrences of “sprintf” in the code are actually calls to string_sprintf(), a function that returns the result in malloc’d store. The intermediate formatting is done into a large fixed buffer by a function that runs through the format string itself, and checks the length of each conversion before performing it, thus preventing buffer overruns.
The remaining uses of sprintf() happen in controlled circumstances where the output buffer is known to be sufficiently long to contain the converted string.
Arbitrary strings are passed to both these functions, but they do their formatting by calling the function string_vformat(), which runs through the format string itself, and checks the length of each conversion.
These are used only in cases where the output buffer is known to be large enough to hold the result.