This document describes a proof of concept that may be used for experiments to run the Postfix master(8) daemon without "root" privileges (and consequently, all Postfix daemon processes). As discussed below, this will make Postfix less secure.
Feasible for production with some weakened security: unprivileged master(8) deployments that forward all messages with SMTP or LMTP.
MUST NOT BE USED FOR PRODUCTION: unprivileged master(8) deployments that can execute non-Postfix programs (with local(8), pipe(8) or spawn(8)) or that deliver to files (with local(8) or virtual(8).
The remainder of this document is organized as follows:
This section summarizes the Postfix security model for privileged master(8) operation, and how it is weakened with an unprivileged master(8).
Over-all Postfix architecture:
Postfix consists of a) service daemons that receive and deliver email messages in the background, b) client commands that are invoked by users, and c) commands for system administrators. The privileged master(8) daemon runs all service daemons in a controlled environment. Postfix has no set-uid program files, eliminating a large class of attacks.
Impact of unprivileged master(8) operation: the master(8) daemon runs with fixed "postfix" privilege. This affects all Postfix service daemon processes as discussed next.
Postfix service daemon processes:
When launched by a privileged master(8) daemon, a Postfix service daemon can revoke "root" privileges after initialization, before it handles any requests. This allows each service process to read sensitive files (e.g. TLS private keys) and to open sensitive databases or logfiles for writing. When a service process becomes compromised, it no longer has that level of access.
All but four Postfix service daemons irrevocably release root privileges, and therefore do not in any sense run as "root" once they're handling service requests. Here, "root" privilege is used to separate privileges within Postfix (internal privilege separation).
The only exceptions are local(8), pipe(8), spawn(8) and virtual(8) which run as "postfix" most of the time, but retain a saved effective uid of "root" so that they can temporarily run as the target user (the user that owns the target mailbox or maildir file, or that is configured to execute the given command). Here, root privilege is used to select different privileges for Postfix and non-Postfix resources ("postfix" privilege confinement).
If the Postfix service daemons in question do not access non-Postfix files and if they do not execute non-Postfix commands, then all the services launched by the master(8) daemon will irrevocably run unprivileged after initialization.
Impact of unprivileged master(8) operation: Postfix cannot maintain internal privilege separation (service daemons must use the same Postfix privileges while initializing and while handling requests), and it cannot maintain "postfix" privilege confinement (service daemons must use the same "postfix" privileges to execute non-Postfix programs or to access non-Postfix files). Examples of how this makes the mail system more vulnerable:
The local(8) deliver agent will execute "|command" etc. instructions in a user's ".forward" file with "postfix" privileges, allowing the user to take control of the Postfix mail system.
All local(8) and virtual(8) mailbox and maildir files will be owned by the "postfix" user. If a user can read one mailbox and maildir file, they can read, write, and delete all such files; additionally they can read, write, and delete all messages in the Postfix queue.
The local(8), pipe(8) or spawn(8) daemons will execute non-Postfix commands with "postfix" privileges; if the command has a vulnerability, then an attacker can take over the Postfix mail system.
The weaker protection of secret-key files and the optional Postfix "maillog_file" logging are discussed under the next item.
Postfix configuration, secrets, and logs:
Postfix configuration files and "maillog_file" logfiles are owned by the "root" user. Additionally, secret key files are readable only by "root". This protects secret keys, configuration files, and Postfix logging, after someone compromises an unprivileged Postfix service daemon process.
Impact of an unprivileged master(8) operation: configuration files including secret keys will still be owned by "root", but keys will need to be readable by the "postfix" user, and optional Postfix "maillog_file" logfiles will need to be owned by "postfix". After someone compromises an unprivileged Postfix service daemon process, they will be able to read all Postfix secret key files, and will be able to read, write, or delete the optional Postfix "maillog_file".
Handling requests from local users:
Postfix uses the set-gid postdrop(1) command to submit messages into the mail queue; uses the set-gid postqueue(1) command to list or mail queue, or to request delivery; and uses the set-gid postlog(1) command to report events to the optional Postfix "maillog_file". Postfix provides sendmail-compatible helper programs sendmail(1) and mailq(1) that invoke postdrop(1) or postqueue(1).
Impact of an unprivileged master(8) operation: the optional Postfix "maillog_file" will be owned by the "postfix" user; a compromised service daemon process will be able to read, write, or delete Postfix logging.
Postfix queue files and directories:
The "postfix" user owns all Postfix queue directories and queue files, except for local submissions created with the postdrop(1) command. Those queue files are owned by the originating user, and are stored in the "maildrop" queue directory, from where they are ingested into Postfix by the pickup(8) service daemon. The Postfix queue is not directly accessible by users.
Impact of an unprivileged master(8) operation: none.
The following instructions assume that the steps in the " Appendix have already been implemented.
To start, reload or stop Postfix, execute as the "root" or "postfix" user:
postfix start
postfix reload
postfix stop
Commands that change Postfix permissions or configuration files must be executed as the "root" user:
postfix set-permissions
postconf "parameter = value"
postmap "maptype:mapname..."
postalias "maptype:mapname..."
Postfix configurations MUST NOT be owned by the postfix user: that would be too convenient for someone who compromises a Postfix daemon process.
Wietse Venema originally implemented unprivileged master(8) operation, for integration tests.
Viktor Dukhovni contributed background on the postfix-users mailing list.
Unprivileged master(8) operation requires that Postfix is built with dynamically-linked libraries, that is, with "shared=yes" on the "make makefiles" command line.
After installing Postfix from a standard pre-compiled package or from source code, a few tweaks are needed to make unprivileged operation possible. These changes should persist after subsequent Postfix updates.
To revert to the privileged Postfix operation, stop Postfix and undo the changes described in the subsections that follow.
If you configure a non-default "maillog_file" setting, then Postfix will create that file with user "postfix" privileges (instead of "root"). The parent directory and logfile will need to be writable by the "postfix" user. Example:
postconf maillog_file=/var/log/postfix/maillog mkdir -p /var/log/postfix touch /var/log/postfix/maillog chown postfix /var/log/postfix /var/log/postfix/maillog
To revert to privileged Postfix master(8) operation, make the parent directory (in the example: /var/log/postfix) writable by "root" only.
As user "root", update the file "main.cf" with the following command:
postconf "import_environment = $(postconf -dh import_environment) \
LD_PRELOAD=\$shlib_directory/fake_eugid.so"
Do not omit the "\" in "\$shlib_directory".
The result is an import_environment setting that contains both the default import_environment value plus the literal string LD_PRELOAD=$shlib_directory/fake_eugid.so. With Postfix 3.12 the result looks like:
import_environment = MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG TZ XAUTHORITY
DISPLAY LANG=C POSTLOG_SERVICE POSTLOG_HOSTNAME XDG_RUNTIME_DIR
LD_PRELOAD=$shlib_directory/fake_eugid.so
(it will be all on one long line).
To revert to privileged Postfix master(8) operation, execute as the "root" user:
postconf -X import_environment
As root, execute the following command:
echo '$queue_directory/pid:d:$mail_owner:-:755:uc' \
> $(postconf -xh meta_directory)/postfix-files.d/unprivileged
NOTE: use single quotes (') not double quotes (").
This adds add a file "unprivileged" in a subdirectory "postfix-files.d" under the Postfix directory configured with "meta_directory". The result should look like this:
$queue_directory/pid:d:$mail_owner:-:755:uc
Execute as the "root" user:
postfix set-permissions
To revert to privileged Postfix master(8) operation, delete the file "unprivileged" that was created above, and execute as the "root" user:
rm $(postconf -xh meta_directory)/postfix-files.d/unprivileged
postfix set-permissions
Normally, Postfix daemons open secret key files before dropping "root" privileges. With unprivileged Postfix that is no longer possible.
Execute the following command as "root" for each such file:
chmod 644 /path/to/secret-key-file.pem
You may need to update Let'sEncrypt etc. key management automation to ensure correct file permissions after a future key update.
To revert to privileged Postfix master(8) operation, execute the following command as "root" for each of the above files:
chmod 600 /path/to/secret-key-file.pem
and revert the above changes to Let'sEncrypt etc. key management automation.