Compare commits

...

3 Commits
v0.5 ... master

Author SHA1 Message Date
Stefan Bühler
2daef420a2 Manpage 2009-10-23 10:34:43 +02:00
Oliver Sturm
a65ce431b9 Add syslog logging 2009-10-23 10:31:22 +02:00
Stefan Bühler
b471b98d5c Fix warnings 2008-07-08 01:59:58 +02:00
4 changed files with 162 additions and 24 deletions

13
README
View File

@ -70,11 +70,13 @@ DEFAULT_UID The default (numeric) UID to become, if no UID is given vi
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.
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.
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.
@ -99,6 +101,8 @@ ENV_CHECK_GID The same as ENV_UID, but controls the name of the CHECK_GI
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)
---------------------
COMPILE AND INSTALL
@ -106,7 +110,7 @@ ENV_NON_RESIDENT The same as ENV_UID, but controls the name of the NON_RESI
There is no Makefile right now, but compile is as simple as:
> gcc -O2 -o execwrap execwrap.c && strip execwrap
> 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
@ -161,6 +165,7 @@ NON_RESIDENT If set (to anything), the wrapper will drop privileges and
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.
-----------------------------------
SECURITY CHECKS AND THEIR REASONS

51
execwrap.8 Normal file
View File

@ -0,0 +1,51 @@
.\" Hey, EMACS: -*- nroff -*-
.\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1)
.TH EXECWRAP 8 "July 8, 2008"
.\" Please adjust this date whenever revising the manpage.
.\"
.\" Some roff macros, for reference:
.\" .nh disable hyphenation
.\" .hy enable hyphenation
.\" .ad l left justify
.\" .ad b justify to both left and right margins
.\" .nf disable filling
.\" .fi enable filling
.\" .br insert line break
.\" .sp <n> insert n+1 empty lines
.\" for manpage-specific macros, see man(7)
.SH NAME
execwrap \- a super-user exec wrapper
.SH ENVIRONMENT
.IP UID
The UID to switch to. Only numerical values are accepted currently.
.IP GID
The GID to switch to. Only numerical values are accepted currently.
.IP 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.
.IP 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.
.IP 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.
.IP DEBUG
If set (to anything), execwrap will log some debug messages to
syslog (USE_SYSLOG needs to be enabled at compile time, which is
the default).
.SH AUTHOR
execwrap was written by Sune Foldager.
.PP
This manual page was written by Stefan B\"uhler <stbuehler@web.de>,
for the Debian project (but may be used by others).

View File

@ -63,6 +63,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#define ENV_TARGET "TARGET="
#define ENV_CHECK_GID "CHECK_GID="
#define ENV_NON_RESIDENT "NON_RESIDENT="
#define ENV_DEBUG "DEBUG"
/* Return values for various errors. */
@ -123,7 +124,28 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <unistd.h>
#include <signal.h>
#include <pwd.h>
#include <sys/types.h>
#include <grp.h>
#if USE_SYSLOG
# include <syslog.h>
# include <errno.h>
#else /* USE_SYSLOG */
/* this should be after all includes */
# undef SKIP
# undef openlog
# undef setlogmask
# undef syslog
# define SKIP do { } while (0)
# define openlog(...) SKIP
# define setlogmask(...) SKIP
# define syslog(...) SKIP
#endif /* USE_SYSLOG */
/* The global child PID and previous SIGTERM handler. */
int pid;
@ -146,9 +168,15 @@ void sigTermHandler(int signal)
/* Down to business. */
int main(int argc, char* argv[], char* envp[])
{
openlog("execwrap", LOG_PID, LOG_AUTHPRIV);
setlogmask(LOG_UPTO(LOG_NOTICE));
/* Verify parent UID. Only the super user and PARENT_UID are allowed. */
if(getuid() != 0 && getuid() != PARENT_UID) return RC_CALLER_UID;
int myuid = getuid();
if(myuid != 0 && myuid != PARENT_UID) {
syslog(LOG_ERR, "exiting with RC_CALLER_UID, UID=%d", myuid);
return RC_CALLER_UID;
}
/* Command line options. */
if(argc > 1)
@ -175,31 +203,43 @@ int main(int argc, char* argv[], char* envp[])
return RC_BAD_OPTION;
}
/* we need this check before the loop over the environment,
as we cannot rely on it to be the first env var we find */
if (NULL != getenv(ENV_DEBUG)) {
setlogmask(LOG_UPTO(LOG_DEBUG));
syslog(LOG_DEBUG, "activated debug output");
}
/* Grab stuff from environment, and set defaults. */
int uid = DEFAULT_UID;
int gid = DEFAULT_GID;
uid_t uid = DEFAULT_UID;
gid_t 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++)
while(NULL != (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;
if(uid < TARGET_MIN_UID) {
syslog(LOG_ERR, "exiting with RC_TARGET_UID, UID=%d", uid);
return RC_TARGET_UID;
}
syslog(LOG_DEBUG, "target UID is %d", 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;
if(gid < TARGET_MIN_GID) {
syslog(LOG_ERR, "exiting with RC_TARGET_GID, GID=%d", gid);
return RC_TARGET_GID;
}
syslog(LOG_DEBUG, "target GID is %d", gid);
}
/* Target script. */
@ -207,7 +247,11 @@ int main(int argc, char* argv[], char* envp[])
{
target = s + ENV_TARGET_LEN;
if((target[0] != '/') || strchr(target, '~') || strstr(target, "..") ||
strncmp(TARGET_PATH_PREFIX, target, TARGET_PATH_PREFIX_LEN)) return RC_TARGET;
strncmp(TARGET_PATH_PREFIX, target, TARGET_PATH_PREFIX_LEN)) {
syslog(LOG_ERR, "exiting with RC_TARGET, target=%s", target);
return RC_TARGET;
}
syslog(LOG_DEBUG, "TARGET is %s", target);
}
/* Check GID instead of UID. */
@ -227,14 +271,24 @@ int main(int argc, char* argv[], char* envp[])
}
/* See if we got all we need. */
if(!target) return RC_MISSING_CONFIG;
if(!target) {
syslog(LOG_ERR, "exiting with RC_MISSING_CONFIG, no TARGET");
return RC_MISSING_CONFIG;
}
/* Fetch user information from passwd. */
struct passwd *pwent = getpwuid(uid);
#if REQUIRE_PWENT
if(!pwent) return RC_MISSING_PWENT;
if(!pwent) {
syslog(LOG_ERR, "exiting with RC_MISSING_PWENT, pwent required by config, but not found");
return RC_MISSING_PWENT;
}
#endif
if (pwent) {
syslog(LOG_DEBUG, "pwent found, pw_name=%s", pwent->pw_name);
}
/* Install the SIGTERM handler. */
if(!non_resident)
{
@ -247,23 +301,41 @@ int main(int argc, char* argv[], char* envp[])
{
/* We're in the child. Drop privileges and set the group list. */
if(pwent && initgroups(pwent->pw_name, gid)) return RC_SETGID;
if(setgid(gid)) return RC_SETGID;
if(setuid(uid)) return RC_SETUID;
if(pwent && initgroups(pwent->pw_name, gid)) {
syslog(LOG_ERR, "exiting with RC_SETGID, initgroup failed, errno=%d", errno);
return RC_SETGID;
}
if(setgid(gid)) {
syslog(LOG_ERR, "exiting with RC_SETGID, setgid failed, errno=%d", errno);
return RC_SETGID;
}
if(setuid(uid)) {
syslog(LOG_ERR, "exiting with RC_SETUID, setuid failed, errno=%d", errno);
return RC_SETUID;
}
/* Stat the target script. */
char uid_ok = 1;
struct stat stat_buf;
if(stat(target, &stat_buf)) return RC_STAT;
if(stat(target, &stat_buf)) {
syslog(LOG_ERR, "exiting with RC_STAT, stat failed, errno=%d", errno);
return RC_STAT;
}
int modes = stat_buf.st_mode;
/* Never allow world-write. */
if(modes & S_IWOTH) return RC_WORLD_WRITE;
if(modes & S_IWOTH) {
syslog(LOG_ERR, "exiting with RC_WORLD_WRITE, modes=%d", modes);
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;
if(!check_gid) {
syslog(LOG_ERR, "exiting with RC_WRONG_USER");
return RC_WRONG_USER;
}
uid_ok = 0;
}
@ -271,11 +343,18 @@ int main(int argc, char* argv[], char* envp[])
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;
if(modes & S_IWGRP) {
syslog(LOG_ERR, "exiting with RC_GROUP_WRITE, modes=%d", modes);
return RC_GROUP_WRITE;
}
if(!uid_ok) {
syslog(LOG_ERR, "exiting with RC_WRONG_GROUP");
return RC_WRONG_GROUP;
}
}
/* All checks passed, let's become the target! */
syslog(LOG_NOTICE, "executing target=%s, UID=%d, GID=%d", target, uid, gid);
execl(target, target, NULL);
return RC_EXEC;

View File

@ -32,3 +32,6 @@ See the README for documentation.
/* Allow use of the CHECK_GID mode? */
#define ALLOW_CHECKGID 1
/* Use syslog to report errors */
#define USE_SYSLOG 1