Since there are a plethora of buffer overflows waiting to happen, and since the AUSCERT wrapper isn't sufficient for many people, I'm making my more generic wrapper available to all. The following URL includes the source and a short description of the wrapper: http://cegt201.bradley.edu/~im14u2c/wrapper/ A quick description of the wrapper's functionality: -- Filters argv/envp, looking for excessive length arguments and environment variable. Allowed length is tunable on a per-variable basis for environement variables. There's a single upper limit for argument lengths. -- Remaps/disallows "dangerous" characters in argv/envp. You can configure a set of remappings, and a set of disallowed characters. Additionally, you can set a threshold of remapped characters, in case you're only moderately paranoid. :-) -- Validates USER/LOGNAME environement variables against the username returned by getpwuid(). -- Passes only prechosen environment variables. -- Presets other environment variables to specific values. -- Logs exploit attempts/suspicious behavior to syslog(). In contrast, the AUSCERT wrapper only checks argument lengths and logs the error to syslog(). Incidentally, the subroutines in this code are small enough that compiling with "gcc -O6 -fomit-frame-pointer -finline-functions" will generally flatten the call graph completely, making it pretty resistant to any overflows itself (not that any should be possible within the wrapper, but I don't blindly trust my libc!). All of the functions are declared "static inline". Most of the functionality of the wrapper is configurable, so you can disable those portions which do not interest/pertain to you. ---wrapper.c--- /*****************************************************************/ /* Generic wrapper to prevent exploitation of suid/sgid programs */ /* J. Zbiciak, 5/22/97 */ /*****************************************************************/ #include #include #include #include #include #include #include #include char rcsid[]="$Id: wrapper.c,v 1.5 1997/05/22 04:40:47 jzbiciak Exp $"; /**************************************************************************/ /* To install, move wrapped executable to a different file name. (I like */ /* just appending an underscore '_' to the filename.) Then, remove the */ /* offending permission bit. Finally, place this program in the wrapped */ /* program's place with the appropriate permissions. Enjoy! */ /**************************************************************************/ /* Tunable values per program being wrapped */ /* Paths */ #define WRAPPED "./test_wrap" /* Set to full path of wrapped executable */ #define REALBIN WRAPPED"_" /* Usually can be left untouched. */ /* Wrapper behavior */ #define SYSLOG 1 /* Enable/disable SYSLOGging */ #define LOG_UIDS 1 /* Enable/disable recording uid w/syslog */ #define FACILITY LOG_LOCAL0 /* Facility to syslog() to */ #define PRIORITY LOG_ALERT /* Priority level for syslog() */ #define LOGIDENT "wrapper" /* How to identify myself to syslog() */ #define PARANOID_USER 1 /* Verify USER/LOGNAME against uid */ /* Wrapped program characteristics */ #define MAX_ARG (32) /* Maximum argv parameter length. */ typedef struct tEnvInfo { char * env; /* Environment var name with trailing '=' */ int name_len; /* Length of name (including '=') */ int max_len; /* Max length of value assignable to var */ int has_username; /* Check this variable against username */ } TEnvInfo; /* aside: trailing '=' is necessary to prevent problems with variables */ /* whose names prefix each other. */ TEnvInfo allowed_env [] = /* Environ. vars we allow program to see */ { { "ROWS=", 5, 4, 0 }, { "COLUMNS=", 8, 4, 0 }, { "LC_CTYPE=", 9, 64, 0 }, { "LC_MESSAGES=", 11, 64, 0 }, { "LC_TIME=", 8, 64, 0 }, { "LOGNAME=", 8, 16, 1 }, { "TERM=", 5, 16, 0 }, { "USER=", 5, 16, 1 }, }; #define NUM_ALLOWED_ENV (sizeof(allowed_env)/sizeof(TEnvInfo)) char * preset_env [] = /* Environ. vars we force the program to see */ { "PATH=/bin:/usr/bin:/usr/ucb:/usr/local/bin", }; #define NUM_PRESET_ENV (sizeof(preset_env)/sizeof(char *)) /* Set an entry to what a char maps to, if allowed, or 0 if it's not. */ /* This table pretty well assumes an ASCII representation is being used. */ unsigned char allowed_char [] = { /* Default table disallows all but a handful of certain "well known" */ /* ASCII control characters. Otherwise, only alpha, numeric, and */ /* punctuation characters are allowed through. By default, disallowed */ /* characters are mapped to the character defined by "NA". If a */ /* character is marked with a zero, it causes the wrapper to abort. */ /* If a character is marked with NA, it's merely remapped. To disable */ /* remapping entirely (causing any disallowed char to abort), define NA */ /* to be 0. The variable MAX_REMAP is used by the main body of the */ /* program to control how many remapped characters you'll permit per */ /* each string. (environment variable, argument, etc.) */ #define NA '_' #define MAX_REMAP (32) /* Control characters: */ /* Permits BEL, BS, TAB, NL, VF, CR, ESC, and remaps the rest. */ /* ^@ -- ^G ( 0 -- 7) */ 0, NA, NA, NA, NA, NA, NA,'\a', /* ^H -- ^O ( 8 -- 15) */ '\b','\t','\n', NA,'\v','\r', NA, NA, /* ^P -- ^W ( 16 -- 23) */ NA, NA, NA, NA, NA, NA, NA, NA, /* ^X -- ^_ ( 24 -- 31) */ NA, NA, NA, 27, NA, NA, NA, NA, /* Alphabetics, numerics, and punctuation: */ /* Permits all of them by default, except for the DEL character (127) */ /* ( 32 -- 39) */ ' ', '!', '"', '#', '$', '%', '&','\'', /* ( 40 -- 47) */ '(', ')', '*', '+', ',', '-', '.', '/', /* ( 48 -- 55) */ '0', '1', '2', '3', '4', '5', '6', '7', /* ( 56 -- 63) */ '8', '9', ':', ';', '<', '=', '>', '?', /* ( 64 -- 71) */ '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', /* ( 72 -- 79) */ 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', /* ( 80 -- 87) */ 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', /* ( 88 -- 95) */ 'X', 'Y', 'Z', '[','\\', ']', '^', '_', /* ( 96 -- 103) */ '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', /* (104 -- 111) */ 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', /* (112 -- 119) */ 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', /* (120 -- 127) */ 'x', 'y', 'z', '{', '|', '}', '~', NA, /* Extended ASCII: Flatly disallowed. */ /* (128 -- 143) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* (144 -- 159) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* (160 -- 175) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* (176 -- 191) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* (192 -- 207) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* (208 -- 223) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* (224 -- 239) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* (240 -- 255) */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }; /* Internal wrapper use only -- shouldn't need to adjust, generally */ #define MSG_LEN (192) /* Maximum output message length. */ #define MAX_LOG (64) /* Maximum length per call to syslog() */ #ifndef SYSLOG #error Define "SYSLOG" to be either 1 or 0 explicitly #endif #if SYSLOG && !defined(LOG_UIDS) #error Define "LOG_UIDS" to be either 1 or 0 explicitly #endif #ifndef PARANOID_USER #error Define "PARANOID_USER" to be either 1 or 0 explicitly #endif /* No user serviceable parts inside (End of configurable options) */ static uid_t uid,euid; static gid_t gid,egid; /* Find the last whitespace character before 'len' -- for word-wrapping */ static inline int word_wrap(char * s, int len) { int i,j; for (i=j=0; *s && i0) s+=m; } while (l>0); #if LOG_UIDS /* As a matter of paranoia, also log uid,gid,euid,egid. We log the euid/egid as well, in case user somehow managed to modify one or both aside from what our perms are set to. */ #if SYSLOG==2 printf ("syslog: uid=%.5d gid=%.5d euid=%.5d egid=%.5d\n", (int)uid,(int)gid,(int)euid,(int)egid); #else syslog (PRIORITY,"uid=%.5d gid=%.5d euid=%.5d egid=%.5d", (int)uid,(int)gid,(int)euid,(int)egid); #endif #endif closelog(); #endif exit(1); } /* Scan a string, ensuring its contents are completely safe. Remaps characters according to the "allowed_char[]" array. Places the length of the string in *length. The function returns the following: 0 if the string was not modified -1 if the string contained illegal characters -2 if the string was too long otherwise it returns the number of remapped characters (in case you want to apply a "threshold" on the number of remapped characters you allow). */ static inline int safe_str_scan(unsigned char * string, int max_length, int *length) { int i, remap_flag=0; unsigned char * s=string; unsigned char remap; for (i=0;*s && ipw_name; return username; } #endif /* The main event */ int main(int argc, char * argv[], char *envp[]) { int i,j,k; int check; int length; char buf[MSG_LEN]; /* Set our uid/gid variables */ uid = getuid (); gid = getgid (); euid= geteuid(); egid= getegid(); #if PARANOID_USER /* Look up username, so we can compare against USER=/LOGNAME= */ if (!determine_username()) { printf("Who are you?\nError: Aborting!\n" "No entry in password file for uid=%d\n",(int)uid); /* Safe since uid/gid etc. are max 5 chars apiece */ sprintf(buf, "Invalid user (no password entry)"); log(buf); exit(1); /* Safety net */ } #endif /* Check all of argv. Log and exit if any args have length > MAX_ARG */ for (i=1;iMAX_REMAP) { printf("Error: Aborting!\n%s: '%s'\n", check==-2?"Excessive argument length": check==-1|| check>0?"Invalid characters present in argument": "Internal error processing argument", argv[i]); /* Safe since uid/gid etc. are max 5 chars apiece */ sprintf(buf, "Possible overrun attempt (argv len=%.5d err=%.2d) ", length, check); log(buf); exit(1); /* safety net */ } } /* Check all of envp. Throw out any environment variables which aren't in "allowed_env[]". If any variables permitted by "allowed_env[]" are too long, log and exit. */ for (i=j=0; envp[i]!=0; i++) { for (k=0;kMAX_REMAP) { printf("Error: Aborting!\n%s: '%s'\n", check==-2?"Excessive environment variable length": check==-1|| check>0?"Invalid characters present in environment": "Internal error processing environment", envp[i]); /* Safe because we have control over allowed_env[] */ sprintf(buf, "Possible overrun attempt (env '%.32s' len=%.5d err=%.2d):", envp[i], length, check); log(buf); exit(1); /* safety net */ } #if PARANOID_USER /* Check variable value against username we looked up */ if (uid!=0 && allowed_env[k].has_username && strcmp(username,envp[i]+allowed_env[k].name_len)!=0) { printf("Error: Aborting!\n" "Environment variable '%s' doesn't match username\n", envp[i]); /* Safe because we control allowed_env[] */ sprintf(buf, "Variable didn't match username (env '%.32s'): ", envp[i]); log(buf); exit(1); /* safety net */ } #endif envp[j++]=envp[i]; } if (j>NUM_ALLOWED_ENV) { /* This should only happen if we somehow had duplicate copies of */ /* the same allowed environment variable passed to us, which */ /* shouldn't happen. */ log("Internal error to wrapper: too many allowed env vars found"); exit(1); /* safety net */ } } for (i=0;i