Initial commit of ExecWrap 0.4 from tarball.

master v0.4
Sune Foldager 14 years ago
commit d2ea4e1057
  1. 266
      README
  2. 268
      execwrap.c
  3. 28
      execwrap_config.h

266
README

@ -0,0 +1,266 @@
----------
OVERVIEW
----------
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
environment.
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.
-------------------
COMMAND ARGUMENTS
-------------------
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.
----------------------------
COMPILE-TIME CONFIGURATION
----------------------------
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.
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=".
---------------------
COMPILE AND INSTALL
---------------------
There is no Makefile right now, but compile is as simple as:
> gcc -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.
------------------------
RUN TIME CONFIGURATION
------------------------
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.
-----------------------------------
SECURITY CHECKS AND THEIR REASONS
-----------------------------------
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.
-------------------------------
OTHER SECURITY CONSIDERATIONS
-------------------------------
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.
-----------------------
CONFIGURATION EXAMPLE
-----------------------
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"
)
)
)
)

@ -0,0 +1,268 @@
/*
Superuser-exec wrapper for HTTP serves and other needs (made especially for lighttpd).
Allows programs to be run with configurable uid/gid.
Version 0.4 (2006-06-09)
For documentation on how to configure the wrapper, see the README.
Command line option -v displays version, while -V displays compile-time configuration.
These options are only available for the super user and the server admin.
Brief version history:
Vers Date Changes
-----------------------------------------------------------------------------------------
0.4 2006-06-09 Added a BSD license.
0.3 2005-09-27 Changed the wrapper to stay resident and propagate SIGTERM to the
target. Not doing so will prevent the server from managing the target.
The old behaviour can be restored with a run-time option. Fixed a bug
in the call to execl, which could cause it to fail or crash. No
security risk. Added custom return codes on errors. Added some command
line options.
0.2 2005-09-14 Cleaned up source a bit, and added compile-time security checks. Fixed
a few minor issues. Compiles under C89 now (e.g. gcc -std=c89).
0.1 2005-09-08 Initial version.
License:
Copyright (c) 2006, Sune Foldager
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this list of
conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Configuration. Shouldn't be changed. */
#define ENV_UID "UID="
#define ENV_GID "GID="
#define ENV_TARGET "TARGET="
#define ENV_CHECK_GID "CHECK_GID="
#define ENV_NON_RESIDENT "NON_RESIDENT="
/* Return values for various errors. */
#define RC_CALLER_UID 10
#define RC_TARGET_UID 11
#define RC_TARGET_GID 12
#define RC_TARGET 13
#define RC_MISSING_CONFIG 14
#define RC_SIGNAL_HANDLER 15
#define RC_SETGID 16
#define RC_SETUID 17
#define RC_STAT 18
#define RC_WORLD_WRITE 19
#define RC_WRONG_USER 20
#define RC_GROUP_WRITE 21
#define RC_WRONG_GROUP 22
#define RC_EXEC 23
#define RC_BAD_OPTION 24
/* User configuration. */
#include "execwrap_config.h"
/* Compile-time security checks. */
#if !TARGET_MIN_UID
#error SECURITY: TARGET_MIN_UID set to 0. See README for details.
#endif
#if !TARGET_MIN_GID
#error SECURITY: TARGET_MIN_GID set to 0. See README for details.
#endif
#if DEFAULT_UID < TARGET_MIN_UID
#error SECURITY: DEFAULT_UID set to a lower value than TARGET_MIN_UID. See README for details.
#endif
#if DEFAULT_GID < TARGET_MIN_GID
#error SECURITY: DEFAULT_GID set to a lower value than TARGET_MIN_GID. See README for details.
#endif
/* Useful macro and other stuff. */
#define VERSION_STRING "ExecWrap v0.3 by Sune Foldager."
#define STRLEN(a) (sizeof(a)-1)
/* Shortcuts. */
#define TARGET_PATH_PREFIX_LEN STRLEN(TARGET_PATH_PREFIX)
#define ENV_UID_LEN STRLEN(ENV_UID)
#define ENV_GID_LEN STRLEN(ENV_GID)
#define ENV_TARGET_LEN STRLEN(ENV_TARGET)
#define ENV_CHECK_GID_LEN STRLEN(ENV_CHECK_GID)
#define ENV_NON_RESIDENT_LEN STRLEN(ENV_NON_RESIDENT)
/* Stuff we need. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
/* The global child PID and previous SIGTERM handler. */
int pid;
void (*oldHandler)(int);
/* SIGTERM handler. */
void sigTermHandler(int signal)
{
/* If we're in the parent, kill the child as well. */
if(pid) kill(pid, SIGTERM);
/* Call the default handler. */
oldHandler(signal);
}
/* Down to business. */
int main(int argc, char* argv[], char* envp[])
{
/* Verify parent UID. Only the super user and PARENT_UID are allowed. */
if(getuid() != 0 && getuid() != PARENT_UID) return RC_CALLER_UID;
/* Command line options. */
if(argc > 1)
{
if(argv[1][0] == '-') switch(argv[1][1])
{
case 'v': puts(VERSION_STRING);
return 0;
case 'V': puts(VERSION_STRING);
puts("Compile-time configuration:");
printf("PARENT_UID : %d\n", PARENT_UID);
printf("TARGET_MIN_UID : %d\n", TARGET_MIN_UID);
printf("TARGET_MIN_GID : %d\n", TARGET_MIN_GID);
printf("TARGET_PATH_PREFIX : %s\n", TARGET_PATH_PREFIX);
printf("DEFAULT_UID : %d\n", DEFAULT_UID);
printf("DEFAULT_GID : %d\n", DEFAULT_GID);
return 0;
}
/* Fail on unknown option. Known options return quietly above. */
return RC_BAD_OPTION;
}
/* Grab stuff from environment, and set defaults. */
int uid = DEFAULT_UID;
int gid = DEFAULT_GID;
char* target = 0;
char* args = 0;
char check_gid = 0;
char non_resident = 0;
char** p = envp;
char* s;
char* t;
while(s = *p++)
{
/* Target UID. */
if(!strncmp(ENV_UID, s, ENV_UID_LEN))
{
uid = atoi(s + ENV_UID_LEN);
if(uid < TARGET_MIN_UID) return RC_TARGET_UID;
}
/* Target GID. */
if(!strncmp(ENV_GID, s, ENV_GID_LEN))
{
gid = atoi(s + ENV_GID_LEN);
if(gid < TARGET_MIN_GID) return RC_TARGET_GID;
}
/* Target script. */
if(!strncmp(ENV_TARGET, s, ENV_TARGET_LEN))
{
target = s + ENV_TARGET_LEN;
if((target[0] != '/') || strchr(target, '~') || strstr(target, "..") ||
strncmp(TARGET_PATH_PREFIX, target, TARGET_PATH_PREFIX_LEN)) return RC_TARGET;
}
/* Check GID instead of UID. */
if(!strncmp(ENV_CHECK_GID, s, ENV_CHECK_GID_LEN))
{
check_gid = 1;
}
/* Use non-resident wrapping style. */
if(!strncmp(ENV_NON_RESIDENT, s, ENV_NON_RESIDENT_LEN))
{
non_resident = 1;
}
}
/* See if we got all we need. */
if(!target) return RC_MISSING_CONFIG;
/* Install the SIGTERM handler. */
if(!non_resident)
{
oldHandler = signal(SIGTERM, sigTermHandler);
if(oldHandler == SIG_ERR) return RC_SIGNAL_HANDLER;
}
/* Fork off (or, if we are a non-resident wrapper, just carry on). */
if(non_resident || !(pid = fork()))
{
/* We're in the child. Drop privileges. */
if(setgid(gid)) return RC_SETGID;
if(setuid(uid)) return RC_SETUID;
/* Stat the target script. */
char uid_ok = 1;
struct stat stat_buf;
if(stat(target, &stat_buf)) return RC_STAT;
int modes = stat_buf.st_mode;
/* Never allow world-write. */
if(modes & S_IWOTH) return RC_WORLD_WRITE;
/* Only allow user miss-match if check_gid is set. */
if(uid != stat_buf.st_uid)
{
if(!check_gid) return RC_WRONG_USER;
uid_ok = 0;
}
/* If group doesn't match, don't allow group-write.
Also, don't allow if neither user or group match. */
if(gid != stat_buf.st_gid)
{
if(modes & S_IWGRP) return RC_GROUP_WRITE;
if(!uid_ok) return RC_WRONG_GROUP;
}
/* All checks passed, let's become the target! */
execl(target, target, NULL);
return RC_EXEC;
}
/* Here we're in the parent. Wait for the child to be done, and return. */
int status;
wait(&status);
return status;
}

@ -0,0 +1,28 @@
/*
User configuration for ExecWrap. Please review ALL items in this file, before you compile.
Remember, the security of your system depends on getting these right!
See the README for documentation.
*/
/* Our parent must have this UID, or we will abort. */
#define PARENT_UID 104
/* Minimum UID we can switch to. */
#define TARGET_MIN_UID 1000
/* Minimum GID we can switch to. */
#define TARGET_MIN_GID 100
/* Path prefix all targets must start with. */
#define TARGET_PATH_PREFIX "/var/www/light/"
/* Default UID to switch to, if none given. */
#define DEFAULT_UID 65534
/* Default GID to switch to, if none given. */
#define DEFAULT_GID 65534
Loading…
Cancel
Save