super-user exec wrapper for the lighttpd web-server

ExecWrap is a super-user exec wrapper for the lighttpd web-server, but it can be used in
any environment as long as arguments can be passed from the server to its children via the

ExecWrap is released under the BSD license, which may be found in the source file.

The different compile-time configuration options are explained below. For optimal security,
please review ALL of them, as default values are hard to provide in most cases. The
configuration is mainly concerned with security. Next, the run-time configuration options
are explained.

An overview of the security implications of each check the wrapper performs is then given,
along with some checks it doesn't perform and the reason for it.

Finally, the run-time configuration is explained and a usage example is given.


The super user and the user specified by the PARENT_UID parameter (see below) are allowed
to give command line options to the wrapper. These are only useful for humans, and not for
a web-server. 

Argument  Explanation
-v        Displays the name, i.e. ExecWrap, and version of the wrapper.
-V        Displays name, version and the compile-time configuration parameters.


After compiling, remember that the program must be owned by the super user and have its
setuid-user bit set to work! The configuration parameters are found in the file
execwrap_config.h, except for the ones that shouldn't normally be changed. Those are found
in the main file.

Config parameter      Explanation
PARENT_UID            The (numeric only) value of this parameter controls what parent UID
                      the wrapper will accept. If it is started by any other UID than this
                      or the super user, it will instantly abort.

TARGET_MIN_UID        This (numeric) value sets the _minimum_ UID the wrapper can be told
                      to switch to. If asked to switch to a lower value, it will abort.
                      It is _strongly_ advised to set this value to the _minimum_ needed.
                      Setting it to 0 would allow the server to become root, wherefore it
                      is disallowed (by compile-time security checks).

TARGET_MIN_GID        The same as TARGET_MIN_UID, but for the GID. The same reasons and
                      restrictions apply.

TARGET_PATH_PREFIX    This parameter sets the prefix of any target program the wrapper is
                      instructed to invoke. If asked to start something _not_ under this
                      path, the wrapper aborts.

DEFAULT_UID           The default (numeric) UID to become, if no UID is given via the
                      environment. It defaults to 65534, which usually maps to the nobody
                      user. This is recommended. At any rate, it must be at least as high
                      as TARGET_MIN_UID. This is ensured by compile time security checks.

DEFAULT_GID           The same as DEFAULT_UID, but for the GID. The same reasons and
                      restrictions apply.

REQUIRE_PWENT         If set, target users are reuired to have passwd-entries. These are
                      used to set the supplementary group access list. Defaults to 0.

ALLOW_CHECKGID        If set, enabled the CHECK_GID feature, q.v. Defaults to 1.

USE_SYSLOG            If set, log errors and (if enabled) debug messages to syslog.
                      Defaults to 1.

The following configuration parameters shouldn't normally be changed. In case you have a
clash with some other ENV arguments to the target program, they can be changed, however.
The configuration dump command line option, -V, will not output them.

ENV_UID               This parameter controls the name of the environment variable used to
                      tell the wrapper what UID to switch to. It defaults to "UID=" and
                      there is usually no need to change it. If you do, remember that it
                      must end in "=" like the default, or it will fail to be recognised.
                      See the run-time configuration section below.

ENV_GID               The same as ENV_UID, but controls the name of the GID parameter.
                      Defaults to "GID=".

ENV_TARGET            The same as ENV_UID, but controls the name of the TARGET parameter.
                      Defaults to "TARGET=".

ENV_CHECK_GID         The same as ENV_UID, but controls the name of the CHECK_GID
                      parameter. Defaults to "CHECK_GID=".

ENV_NON_RESIDENT      The same as ENV_UID, but controls the name of the NON_RESIDENT
                      parameter. Defaults to "NON_RESIDENT=".

ENV_DEBUG             Controls the name of environment variable used to enable debug
                      output to syslog (depends on USE_SYSLOG)


There is no Makefile right now, but compile is as simple as:

> gcc -W -Wall -O2 -o execwrap execwrap.c && strip execwrap

Or similar, depending on taste. You need something other than an ancient compiler to make
it work, preferably C99. To install, make sure the file is owned by the super user and has
the proper modes:

> chown root execwrap
> chmod og-w,+rx,u+s execwrap

Then move it to where it's supposed to reside. That's all. If you're really fanatic about
security, you can also do something like this (before the chmod u+s):

> chgrp lighttpd execwrap     # <-- Or whatever group your web-server is in.
> chmod o-rwx,g-rw execwrap

To make the command impossible to start for anyone but the super user and the server admin.
But since the first thing the wrapper does on start up is to check the parent UID, this
really isn't necessary.


The run-time configuration is provided by environment variables, and not the command line.
Currently, command line arguments are completely ignored. The run-time arguments are
subjected to the restrictions imposed by the compile-time configuration discussed above.

Note that since the names of the parameters can be changed, the defaults are used in the
following list.

Config parameter      Explanation
UID                   The UID to switch to. Only numerical values are accepted currently.

GID                   The GID to switch to. Only numerical values are accepted currently.

TARGET                The target program to start. For security, it must be absolute and
                      must not contain any ~ characters or ".." sub-strings. Of course the
                      compiled-in prefix must also be a prefix of it.

CHECK_GID             If set (to anything, even the empty string), the security checks will
                      be slightly relaxed to allow targets owned by the target GID but not
                      necessarily by the target UID, as well as allowing the target to be
                      group-writable if owned by the target GID. Useful for projects where
                      several people collaborate so file ownership can vary.

NON_RESIDENT          If set (to anything), the wrapper will drop privileges and become the
                      target process directly, instead of the default behaviour where it
                      forks off before becoming the target, allowing SIGTERM to propagate
                      from the caller of the wrapper, to the target. It is not recommended
                      to set this, as it will make it impossible for the caller (usually a
                      web-server) to terminate the target process, and thus prevents it
                      from effectively managing it.

DEBUG                 If set to anything, debug output to syslog is enabled.


The security checks below are listed in the order they are performed.

Security check        Reason it's performed
Check parent UID      Done to disallow people other than root and server admins to become
                      another user. The reason for this should be obvious, but given all
                      the other checks, this one actually isn't the most important one.

Check min. UID/GID    Done to control what users the server admins can start programs as.
                      In particular, they can't start programs as root. Again, the reason
                      for checking this should be obvious. Never ever set these values to
                      anything at or below system services users and groups!

Target path checks    We check that it's absolute and doesn't contain ~ and "..". This is
                      an overly careful (but also simpler) way to make sure it doesn't
                      lead outside of the prefix.

Target path prefix    Done to prevent server admins from executing user code outside the
                      designated server root. This is not in itself a security enhancement
                      unless the system in general is kept in a tight leash, but it also
                      helps prevent a run-away server from causing problems.

Target given          Done. This isn't for security, but without a target there is no work
                      to be done in the first place :-p.

Can switch UID/GID    Done. Again, this is usually not a security enhancement, as the
                      switch should never fail. In certain environments however, additional
                      security measures such as MAC might prevent even the super user from
                      switching to certain UIDs or GIDs.

Can stat the target   Done. Otherwise, we can hardly expect to become the target anyway.
                      Not for security as such.

Target has wrong      Done to prevent users not owning the target file, or not in the same
modes or UID/GID      group, from replacing it with malicious code. The following checks
                      are performed. If any of them fails, the wrapper aborts.
                        1. Target is NOT world-writable.
                        2. Target is owned by the UID we switched to OR, when CHECK_GID is
                           given, target is owned by the GID we switched to.
                        3. Target is NOT group-writable unless CHECK_GID was given and it
                           is owned by the GID we switched to.

Also, there is one common check we do not perform:

Target dir checks     Not done. Checking the target directory adds no real security. True,
                      if someone else has write permissions he can remove the target and
                      replace it with something else, but this won't have the correct UID
                      and GID. If he can make it have the right values, he would be able to
                      overwrite the target in the first place. Care must be taken here if
                      the CHECK_GID feature is used along with the setgid bit. See below.


Even though several steps are taken to prevent abuse, care must be taken when setting up
any system involving a super-user executable. In particular, consider the following:

1. Make sure the wrapper is safe. It should at most be user-writable.
2. Make sure the group setup for the web server is sane. If the setgid bit is enabled for
   the web root, which is useful in many cases and is the reason for the CHECK_GID
   feature, make SURE that ALL members of the group in question are accounted for, since
   they might be able to replace files in any directory at and below the web root.


Compile-time configuration (the <- parts are comments, obviously):
PARENT_UID              104                   <- My lighttpd UID.
TARGET_MIN_UID          1000                  <- The first regular user UID.
TARGET_MIN_GID          100                   <- Ditto for group.
TARGET_PATH_PREFIX      "/var/www/light/"     <- Site web-roots are below this.
DEFAULT_UID             65534                 <- Nobody.
DEFAULT_GID             65534                 <- Nobody.

Run-time configuration in lighttpd:
fastcgi.server = (
  "/dispatch.fcg" =>
  ( "dispatcher" =>
      "socket" => "/tmp/fcgi.sock",
      "bin-path" => "/var/www/light/execwrap",                    <- The wrapper.
      "check-local" => "disable",
      "min-procs" => 1,
      "max-procs" => 1,
      "bin-environment" => (
        "UID" => "1000",
        "GID" => "407",
        "TARGET" => "/var/www/light/blah.tld/html/dispatch.fcg",  <- The real script.
        "CHECK_GID" => "1"