Compare commits
3 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
2daef420a2 | ||
![]() |
a65ce431b9 | ||
![]() |
b471b98d5c |
13
README
13
README
@ -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
51
execwrap.8
Normal 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).
|
119
execwrap.c
119
execwrap.c
@ -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;
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user