From d2ea4e105737ad7f72359faefc51946add890f01 Mon Sep 17 00:00:00 2001 From: Sune Foldager Date: Mon, 7 Jul 2008 18:44:14 +0200 Subject: [PATCH] Initial commit of ExecWrap 0.4 from tarball. --- README | 266 +++++++++++++++++++++++++++++++++++++++++++++ execwrap.c | 268 ++++++++++++++++++++++++++++++++++++++++++++++ execwrap_config.h | 28 +++++ 3 files changed, 562 insertions(+) create mode 100644 README create mode 100644 execwrap.c create mode 100644 execwrap_config.h diff --git a/README b/README new file mode 100644 index 0000000..7aeab77 --- /dev/null +++ b/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" + ) + ) + ) +) + diff --git a/execwrap.c b/execwrap.c new file mode 100644 index 0000000..8fb5dfa --- /dev/null +++ b/execwrap.c @@ -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 +#include +#include +#include +#include +#include + + +/* 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; + +} + diff --git a/execwrap_config.h b/execwrap_config.h new file mode 100644 index 0000000..4682165 --- /dev/null +++ b/execwrap_config.h @@ -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 +