Security considerations for the PGP Forwarding Server

This document describes how I tried to make the program as secure as possible.

spooler

The spooler is the program that will be called by sendmail via the .forward files. Alternatively, for the hub@... or forwarder@... addresses, it is called via the aliases file.

As a C program it is vulnerable to buffer overflows. However, care was taken that is is actually NOT vulnerable. This includes the ommittance of strcpy in favour of strncpy, the non-usage of sprintf. Even the one command line argument is checked so that it will not overflow the buffer.

spooler run setgid "pgpforwarder". Therefore it has write access into the spool directory, normally /usr/pgpforwarder/spool.

FIX NEEDED: currently the spooler runs setuid "root". I made this because the spooler sends a signal to forwarder when a new message arrive. Because forwarder detaches itself and make a new process group, the spooler could not do this. Now, either we decide that spooler is simple enought to be run as setuid "root" safely. Or we need a better communications scheme, maybe a named pipe or a unix domain socket. I don't really know.

forwarder

forwarder is the process that executes all messages. It is a perl script and as such normally not vulnerable to buffer overflows. However, it calls a bunch of external Perl modules from CPAN, some of them are using Perl-XS, so one can never be sure.

For this reason we run in "tainted" mode. That means that all data that is coming from outside sources is tainted (e.g. considered untrustworthy) and can not be used directly for system calls like open, system etc.

To actually use this data we have to untaint them. This works by rather strict regular expressions. Look at this example for the command adduser:

  sub Cmd_adduser ($)
  {
      my $arg = shift;
      return Syntax('adduser')
        unless $arg =~ m/^($Conf::UserRegExp)\s+($Conf::GroupRegExp)$/o;
      my $gid = Database::GetGroupId($2);
      ...

Here the user supplied arguments end up in $arg. After this, they are sent throught a regular expression. The brackets group two parts. If the regular expression does not match, e.g. because of invalid characters like back-ticks, the function returns immediately, outputting the syntax for the commands.

However, if the arguments were sane, they are now stored in $1 and $2 and can be used accordingly. While $arg was tainted and could not have been used in file system level calls, $1 and $2 are clean and can be used for anything.

Currently the code uses a lot of global variables. That's probably bad coding style and might be eliminated at some time. However, it is important that everyone that modifies the code is aware of that situation. For this reason we have at the beginning of Mail::Process() a bunch of $varname = undef lines that make sure that settings from the previous e-mail won't make it into the current one.

The program uses it's own temp directory, not a system one. The filenames used there are fixed. Therefore, only one instance of the program can run at any time.

admin

This is a online command interface. It accepts (almost) all the commands that hub@... accepts. Almost, because when commands are coming from the input prompt and not from a MIME::Entity object we cannot read several lines. Therefore, you cannot send public keys and you cannot us the To command.

It is not setuid or setgid, so it runs under the identity of the current user. User will get this shell when they connect via SSH to the system. And of course, this script also runs in taint mode.

As a non-setuid and non-setgid one could not create new users. Therefore we have a little helper script named.

admin-root

That's a little helper script that runs setuid "root". It is very small, so it should be easy to check for valididy. It is used as an interface to the Red Hat command line utilities adduser, userdel and usermod or to PAM when a user changes his SSH/POP3 password.

And again this script runs in taint-mode.