
While I wanted to patch my Xfree,
I realized it would be easier to write a wrapper so I wrote a simple one.
It only works on arguments passed to the executable, no control is done
about environment variables. But its advantage over some other wrappers
is that you just have to compile it one and to maintain a reference table
which is a small file in /etc. This file is indexed by argv[0] (which could
be spoofed so be careful to what you put in!). It contains, for each entry,
the name of the real executable, desired EUID and/or EGID, maximum number of
args, maximum args length, and a log level which determines 5 possible
behaviours:
   0 - log nothing, just truncate args.
   1 - log only if too long an arg is given
   2 - also log if too many args are passed
   3 - systematically log any call to the prog
   4 - wait a few seconds and quit if any error occurs

An other interesting aspect is that it can block during a fixed amount of
time before exiting, just to slow down automatic scripts. To make these
scripts harder (but not impossible) to use, all signals are ignored during
the pause (except of course, SIGKILL/SIGSTOP).

It really needs more work, but I won't be there till next week-end, and I
wanted to post this now. You could combine it with a message sender I wrote
a few months ago, which will pop up a window on you display with immediate
information about what's happening on your system.

Addresses:
  SafeLoad (the wrapper):
     http://www-miaif.lip6.fr/willy/pub/safeload/
  Xmsg (message handler/sender):
     http://www-miaif.lip6.fr/willy/pub/xmsg/
  Buffer overflow tests:
     http://www-miaif.lip6.fr/willy/security/

That's all for the moment. Please tell me if you make significant
modifications to the wrapper, or if you find security holes in it, because
it's to be suided root, of course, and I hope it won't need itself another
wrapper  :-)

Willy

--
+---------------+-------------------------+---------------------------------+
| Willy Tarreau | tarreau@aemiaif.lip6.fr | http://www-miaif.lip6.fr/willy/ |
| Magistere d'Informatique Appliquee de l'Ile de France (MIAIF), promo 97   |
+---------------------------------------------------------------------------+

----------------------------------------------------------------------------


/* SafeLoad v0.1
 *
 * Author: Willy Tarreau <tarreau@aemiaif.lip6.fr>
 *
 * THIS PROGRAM IS DISTRIBUTED WITH NO EXPRESSED OR IMPLIED SORT OF WARANTY.
 * THE AUTHORS SHOULD NOT BE LIABLE FOR ANY DAMAGE OR LOSS CAUSED TO YOU,
 * YOUR SYSTEM OR ANYBODY.
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <time.h>


/* number of seconds to wait when an error has been detected in LLSTOP mode. */
#define WAITTIME 5

/* log levels */
#define LLNONE		0	/* don't log anything about this prog */
#define LL2LONG		1	/* log only if too long an arg is given */
#define LL2MANY		2	/* also log if too many args given */
#define LLCALL		3	/* also systematically log any call to the prog */
#define LLSTOP 		4	/* wait and stop if any error occurs */

#define toend(p)	{while(*p&&*p!=' '&&*p!='\t'&&*p!='\r'&&*p!='\n')p++;if(*p)*p++=0;}
#define tonext(p)	{while(*p==' '||*p=='\t')p++;}


int i,j,s;

/* CAUTION ! Don't make the initial string longer than 1023 chars! */
char *suidtable="/etc/safeload.tbl";
char *logfile="/var/log/safeload.log";

void usage(char *name) {
    close(1); dup(2);
    printf("SafeLoad v0.1 - 1998/03/01 - Willy Tarreau <tarreau@aemiaif.lip6.fr>\n",name);
    printf("\tSafeLoad is buffer overflow wrapper for suided programs.\n");
    printf("\tThis program should never be called directly, but only with\n");
    printf("\tthe name of the program it replaces. The simplest way to do\n");
    printf("\tthis is to rename the insecure program and to replace it by\n");
    printf("\ta symlink to safeload. SafeLoad will then look in the table file\n");
    printf("\tfor a line containing this name, and will then execute the proper\n");
    printf("\tprogram with proper euid, which must be, of course, non suided.\n");
    printf("\tAll args will be truncated to a length specified in this file,\n");
    printf("\tand all attempts to overflow any arg will be logged.\n");
    printf("\nCompile-time configuration:\n");
    printf("\ttable file: %s\n\tlog file: %s\n",suidtable,logfile);
    exit(1);
}


int log(char *name, char *error, int verbose) {
    FILE *logf;
    time_t tim;
    struct tm *tm;
    /* let's open the log file */
    if ((logf=fopen(logfile,"a+"))==NULL) {
	fprintf(stderr,"safeload: error while opening log file %s:",logfile);
	perror("");
	fprintf(stderr,"Error was: %s for %s\n",error,name);
	exit(1);
    }
    time(&tim); tm=localtime(&tim);
    fprintf(logf,"%04d/%02d/%02d %02d:%02d: uid=%d, %s for %s\n",tm->tm_year+1900,tm->tm_mon,tm->tm_mday,tm->tm_hour,tm->tm_min,
	    getuid(),error,name);
    fclose(logf);
    if (verbose)
	fprintf(stderr,"SafeLoad logged this error:\n\t%s for %s\n",error,name);
    return 1;  /* just to give a value to a direct call to exit() */
}


int longwait() {
    int s;
#ifdef LONGWAIT
    for (s=0;s<32;s++)
	signal(s,SIG_IGN);
    sleep(WAITTIME);
    for (s=0;s<32;s++)
	signal(s,SIG_DFL);
#endif
    return 1; /* give value to exit */
}

main(int argc, char **argv) {
    FILE *tbl;
    char *line, *myname;
    char *p, *callname, *realname, *np, *nerr;
    int euid, egid, nbargs, lmax, loglevel;

    int i,j,s;

    /* Make myname point to the name under which safeload was called, without the directory */
    if (myname=strrchr(argv[0],'/'))
	myname++;
    else
	myname=argv[0];

    /* if safeload is called as-is it's an error so we tell the user how to use it */
    if (!strcmp(myname,"safeload"))
	usage(myname);

    /* let's open the table */
    if ((tbl=fopen(suidtable,"r"))==NULL) {
	fprintf(stderr,"safeload: error while opening table file %s:",suidtable);
	perror("");
	exit(1);
    }

    /* now, search in the table for a line beginning with myname */
    line=(char *)malloc(1024);
    while (fgets(line,1024,tbl)) {
	callname=p=line; toend(p);
	if (*callname=='#')
	    continue;
	if (!strcmp(myname,callname))	/* not the right name. We go on. */
	    break;
    }
    if (strcmp(myname,callname)) {
	/* if we get here, this means that safeload has been called with a wrong
	   name. We have to log this because it can be the source of an attack. */
	
	log(myname,"missing entry",1);
	exit(1);
    }

    /* Ok, we got the right name. Let's discover what we have to do now. */

    tonext(p); realname=p; toend(p);
    if (!*realname) exit(log(myname,"incomplete entry",0));

    tonext(p); np=p; toend(p);
    if (!isalpha(*np)) {
	euid=strtol(np,&nerr,0);
	if (*nerr) exit(log(myname,"incorrect char in euid field",0));
	if (getpwuid(euid)==NULL)
	    log(myname,"Warning: non-existing euid used",0);
    }
    else {
	struct passwd *pw;
	if ((pw=getpwnam(np))==NULL)
	    exit(log(myname,"Error: user unknown",0));
	euid=pw->pw_uid;
    }
	

    tonext(p); np=p; toend(p);
    if (!isalpha(*np)) {
	egid=strtol(np,&nerr,0);
	if (*nerr) exit(log(myname,"incorrect char in egid field",0));
	if (getgrgid(egid)==NULL)
	    log(myname,"Warning: non-existing egid used",0);
    }
    else {
	struct group *gr;
	if ((gr=getgrnam(np))==NULL)
	    exit(log(myname,"Error: group unknown",0));
	egid=gr->gr_gid;
    }
	
    tonext(p); np=p; toend(p); nbargs=strtol(np,&nerr,0)+1;
    if (*nerr) exit(log(myname,"incorrect char in nbargs field",0));

    tonext(p); np=p; toend(p); lmax=strtol(np,&nerr,0);
    if (*nerr) exit(log(myname,"incorrect char in lmax field",0));

    tonext(p); np=p; toend(p); loglevel=strtol(np,&nerr,0);
    if (*nerr) exit(log(myname,"incorrect char in loglevel field",0));

    /* Ouf !! */
    /* First of all, we'll reduce the number of args if required */
    if ((nbargs>=0) && (argc>nbargs)) {
	if (loglevel>=LL2MANY)
	    log(myname,"too many args",1);
	if (loglevel>=LLSTOP)
	    exit(longwait());

	while (argc>nbargs)
	    argv[--argc]=NULL;  /* we also make them disappear from the stack */
    }

    /* now, we'll truncate all args if required */
    if (lmax>=0) {
	int arg;
	for (arg=1;arg<argc;arg++)
	    if ((strlen(argv[arg]))>lmax) {
		argv[arg][lmax]=0;
		if (loglevel>=LL2LONG)
		    log(myname,"argument too long",1);
		if (loglevel>=LLSTOP)
		    exit(longwait());
	    }
    }


    /* Now, for each arg, */

    if (loglevel>=LLCALL)
	log(myname,"ready to go",0);

    /* First, we'll set the egid to the required value if it's >=0. Only then, the euid */
    if (egid>=0) setegid(egid);
    if (euid>=0)
	seteuid(euid);
    else
	seteuid(getuid());  /* restore the right uid so that the user no longer stays root :-) */

    argv[0]=realname;
    execvp(realname,argv);
    fprintf(stderr,"execvp(%s):");
    perror("");
    exit(log(myname,"execution not completed",1));
}




-------------------------------------------------------------------------------------------



# Format of this file:
# Name is the name by which the wrapper is called
# Wrapped_exec is the full path to the "sensible" executable to launch
# EUID is the UID or name of the user which is to be simulated setuid exec
# EGID: same, but for setgid exec.
# if EUID or EGID is -1, then it will be respectively set to UID or GID.
# NbArg is the maximum number of args allowed.
# Lmax is the maximum length an argument can be.
# LogLvl is the log level which determines the wrapper's behaviour:
#  0 - log nothing, just truncate args.
#  1 - log only if too long an arg is given
#  2 - also log if too many args are passed
#  3 - systematically log any call to the prog
#  4 - wait a few seconds and quit if any error occurs
#
#Name	Full_Path_To_Wrapped_Executable	EUID	EGID	NbArg	Lmax	LogLvl
test	/bin/sh				-1	-1	4	5	1
X	/usr/X11R6/bin/XF86_SVGA_	root	-1	32	256	2

