msystem/004075500016250000012000000000000573023771500132435ustar00bishopstaff00000400000012msystem/testfd.c010064400016250000012000000065340573023771500147050ustar00bishopstaff00000400000012/* * This is a module to test the le_openfd and le_closefd calls; * the tester module doesn't do that too well * * Usage: * testfd [ file ] * Here, file is any file (it will be printed on the screen, so * for your sake it should be readable!); if omitted, we use /etc/passwd. * The program tells you what's going on; it basically opend that * file, marks it as to stay open, and runs a child which reads from * that descriptor. It then marks it as closed and repeats. You should * see the file printed once, with # at the beginning of each line * (along with miscellaneous commentary from the program) * * Author information: * Matt Bishop * Department of Computer Science * University of California at Davis * Davis, CA 95616-8562 * phone (916) 752-8060 * email bishop@cs.ucdavis.edu * * This code is placed in the public domain. I do ask that * you keep my name associated with it, that you not represent * it as written by you, and that you preserve these comments. * This software is provided "as is" and without any guarantees * of any sort. * * Version information: * 1.0 May 25, 1994 Matt Bishop */ #include #include #include "env.h" /* * useful globals */ char *cmd = "sed 's/^/#/' <&%d"; /* default command */ char *deffile = "/etc/passwd"; /* default file name */ /* * error handler * just say what happened */ #ifdef __STDC__ void oops(int code) #else void oops(code) int code; #endif { switch(code){ case SE_NONE: /* no error */ return; case SE_NOMEM: /* no memory */ printf("ran out of memory\n"); return; case SE_NOPIPE: /* no pipes */ printf("can't create another pipe\n"); return; case SE_NOVAR: /* no variable */ printf("unknown environment variable\n"); return; case SE_BADFD: /* no file descriptor */ printf("unknown file descriptor\n"); return; default: /* no idea! */ printf("unknown error %d\n", code); return; } } /* * the start of the problem */ #ifdef __STDC__ void main(int argc, char **argv) #else void main(argc, argv) int argc; char **argv; #endif { int fd; /* file descriptor to be marked */ FILE *fp; /* used to open file */ char cmdbuf[1024]; /* command buffer */ FILE *fproc; /* used for mpopen/mpclose */ /* * open the relevant file */ if ((fp = fopen(argc == 1 ? deffile : argv[1], "r")) == NULL){ perror(argc == 1 ? deffile : argv[1]); exit(1); } if ((fd = fileno(fp)) < 0){ perror("fileno"); exit(1); } /* * now say what you are doing */ printf("reading from file descriptor %d\n", fileno(fp)); (void) sprintf(cmdbuf, cmd, fileno(fp)); printf("This one should print the file %s with leading # ...\n", argc == 1 ? deffile : argv[1]); /* * mark the file open and feed it to the command */ oops(le_openfd(fileno(fp))); if ((fproc = mpopen(cmdbuf, "w")) == NULL){ perror(cmdbuf); exit(1); } /* * say what happened */ printf("status is %d\n", mpclose(fproc)); /* * again, say what you are doing */ printf("reading from file descriptor %d\n", fileno(fp)); printf("This one should print nothing ...\n"); /* * mark the file open and feed it to the command */ oops(le_closefd(fd)); if ((fproc = mpopen(cmdbuf, "w")) == NULL){ perror(cmdbuf); exit(1); } /* * say what happened */ printf("status is %d\n", mpclose(fproc)); /* * say goodnight, Dick! */ exit(1); } msystem/msystem.c010064400016250000012000000507100573023771500151100ustar00bishopstaff00000400000012/* LINTLIBRARY */ /* * This is the file with all the library routines in it * * Author information: * Matt Bishop * Department of Computer Science * University of California at Davis * Davis, CA 95616-8562 * phone (916) 752-8060 * email bishop@cs.ucdavis.edu * * This code is placed in the public domain. I do ask that * you keep my name associated with it, that you not represent * it as written by you, and that you preserve these comments. * This software is provided "as is" and without any guarantees * of any sort. * * Version information: * 1.0 May 25, 1994 Matt Bishop * 1.1 July 5, 1994 Matt Bishop * added TZ to the list of environment variables to be * passed on by default; you get what the environment * gives you (as required by System V based systems) * 1.2 October 4, 1994 Matt Bishop * added mxfpopen, mxfpclose; also cleaned up le_set(), * in that before if you added a predefined environment * variable as the first variable, it would process it, * initialize the environment list (first call), and * then append the name; now if le_set() is called, it * initializes the environment and then does the checking * 1.3 October 31, 1994 Matt Bishop * made the globals static for better modularity */ /* * set, reset environment to be passed to mpopem */ #include #include #include #include #include #include #include #include #include #ifdef __STDC__ # include # include #endif #include #include #include "env.h" /* * signal type */ #ifndef SIG_TYPE # define SIG_TYPE void #endif /* * define error message printer */ #define ERMSG(x) if (le_verbose){ \ (void) fprintf(stderr, "SE internal error: ");\ (void) fprintf(stderr, "%s(%d): %s\n", \ __FILE__, __LINE__-4, x); \ } /* * define limits * * for the popen/pclose clones, we need to store PIDs in an array; * how big should it be? answer: since each popen call requires 1 * pipe, it can only be as big as the maximim number of pipes allowed * that number is MAX_MPOPEN */ #ifndef MAX_MPOPEN # define MAX_MPOPEN 20 #endif /* * all environment variable arrays are dynamically allocated; if they are * too small, they grow by PTR_INC to accommodate the new variable * changing this just causes more (or less) allocations; it's an efficiency * consideration, not a security or system one */ #define PTR_INC 1024 /* how much to increment pointer arrays */ /* * this is the maximum number of signals; we use NSIG if that's * defined, otherwise 32 (which seems to be right for most systems) */ #ifdef NSIG # define MAX_SIGNAL NSIG #else # define MAX_SIGNAL 32 #endif /* * this is the maximum number of file descriptors (NFILES if * defined, otherwise 256 (which seems to be right for most systems) */ #ifdef NFILES # define MAX_DESC NFILES #else # define MAX_DESC 256 #endif /* * in case the subprocess fails to exec the command properly */ #define EXIT_BAD -1 /* oops! */ /* * now, the environment * * the default environment; you get the bare bones here. * to add to it, just stick the new environment variables * at the end of the array; the program does the rest automatically */ char *nvfix[] = { /* these MUST be set or reset */ DEF_PATH, /* a safe path */ DEF_SHELL, /* a safe shell */ DEF_IFS, /* a safe IFS */ DEF_TZ, /* the current time zone */ NULL, /* add new ones here */ }; #define SZ_NVFIX (sizeof(nvfix)/sizeof(char *)) /* size of nvfix */ static int octmask = DEF_UMASK; /* default umask */ static int mresetgid = UID_RESET; /* reset EGID to RGID by default */ static int mresetuid = GID_RESET; /* reset EUID to RUID by default */ static int fdleave[MAX_DESC]; /* 1 to keep file descriptor open */ static char **envp = NULL; /* environment passed to child */ static int sz_envp = 0; static int nend = 0; /* # entries in envp */ static int le_verbose = 1; /* 1 to print error messages */ /* * structure for malloc */ union xyzzy { char **cpp; /* doubly-indirect pointer */ #ifdef __STDC__ void *vp; /* generic pointer */ #else char *vp; /* generic pointer */ #endif }; /* used to cast malloc properly */ /* * library functions */ #ifndef __STDC__ char *getenv(); /* get variable from environment */ #endif /************* U T I L I T Y F U N C T I O N S *******************/ /* * string duplication into private memory * on some systems, this is a library function, so define STRDUP * if it is on yours */ #ifdef STRDUP # ifndef __STDC__ char *strdup(); # endif #else # ifdef __STDC__ static char *strdup(char *str) # else static char *strdup(str) char *str; # endif { register char *p; /* temp pointer */ /* * allocate space for the string, and copy if successful */ if ((p = malloc((unsigned)((strlen(str)+1)*sizeof(char)))) != NULL) (void) strcpy(p, str); return(p); } #endif /* * allocate space for an array of pointers, OR * (if space already allocated) increase the allocation by PTR_INC */ #ifdef __STDC__ static char **c2alloc(char **old, int *sz_alloc) #else static char **c2alloc(old, sz_alloc) char **old; int *sz_alloc; #endif { register int i; /* counter in a for loop */ union xyzzy x; /* used to cast malloc properly */ /* * allocate space for the new (expanded) array */ x.vp = malloc((unsigned) ((*sz_alloc + PTR_INC) * sizeof(char *))); if (x.vp != NULL){ /* success! copy the old and free it, if appropriate */ if (old != NULL){ for(i = 0; i < *sz_alloc; i++) x.cpp[i] = old[i]; x.cpp = old; (void) free(x.vp); } /* now have PTR_INC more room */ *sz_alloc += PTR_INC; } /* * return pointer to new space */ return(x.cpp); } #ifdef __STDC__ static int initenv(void) #else static int initenv() #endif { register int i; register int rval; if (envp != NULL) le_clobber(); for(i = 0; nvfix[i] != NULL; i++) if ((rval = le_set(nvfix[i])) != SE_NONE) return(rval); return(SE_NONE); } /************* E N V I R O N M E N T C O N T R O L *******************/ /* * clobber the internal environment */ #ifdef __STDC__ void le_clobber(void) #else void le_clobber() #endif { register int i; /* counter in a for loop */ union { char **ep; char *p; } x; /* * if the environment is defined and not fixed, clobber it */ if (envp != NULL){ /* it's defined -- is it fixed? */ if (envp != nvfix){ /* no -- usual walk the list crud */ for(i = 0; envp[i] != NULL; i++) (void) free(envp[i]); x.ep = envp; (void) free(x.p); } /* say there's not anything there any more */ envp = NULL; } /* * now clobber the sizes */ nend = sz_envp = 0; } /* * get a pointer to the environment element */ #ifdef __STDC__ static int le_getenv(char *var) #else static int le_getenv(var) char *var; #endif { register int i; /* counter in a for loop */ register char *p, *q; /* used to compare two strings */ /* * check for no environment */ if (envp == NULL) return(-1); /* * there is one -- now walk the environment list */ for(i = 0; envp[i] != NULL; i++){ /* compare */ p = envp[i]; q = var; while(*p && *q && *p == *q) p++, q++; /* have we a match? */ if ((*p == '=' || *p == '\0') && (*q == '=' || *q == '\0')){ /* YES -- return its index */ return(i); } } /* * no match */ return(-1); } /* * set an environment variable */ #ifdef __STDC__ int le_set(char *env) #else int le_set(env) char *env; #endif { register char *p, *q; /* what is to be put into env */ register int n; /* where a previous definition is */ /* * seeif youneed to create the environment list */ if (sz_envp == 0){ if ((envp = c2alloc(envp, &sz_envp)) == NULL){ ERMSG("ran out of memory"); return(SE_NOMEM); } for(nend = 0; nvfix[nend] != NULL; nend++) if ((envp[nend] = strdup(nvfix[nend])) == NULL){ ERMSG("ran out of memory"); return(SE_NOMEM); } envp[nend] = NULL; } /* * if there is an = sign, * it's a redefinition; if not, * just include it from the current environment * (if not defined there, don't define it here) */ if (strchr(env, '=') == NULL){ /* is it defined locally? */ if ((q = getenv(env)) == NULL){ /* no -- don't define it here */ return(SE_NONE); } else if ((p = malloc((unsigned) (strlen(env)+strlen(q)+2))) == NULL){ ERMSG("ran out of memory"); return(SE_NOMEM); } else{ (void) strcpy(p, env); (void) strcat(p, "="); (void) strcat(p, q); } } else if ((p = strdup(env)) == NULL){ ERMSG("ran out of memory"); return(SE_NOMEM); } /* * if it isn't defined, see if you need to create the environment list */ if (nend == sz_envp && (envp = c2alloc(envp, &sz_envp)) == NULL){ ERMSG("ran out of memory"); return(SE_NOMEM); } /* * add it to the environment * if it is already defined, delete the old definition * and replace it with the new definition */ if ((n = le_getenv(env)) > -1){ (void) free(envp[n]); envp[n] = p; return(SE_NONE); } envp[nend++] = p; envp[nend] = NULL; /* * all done */ return(SE_NONE); } /* * clear a current environment variable */ #ifdef __STDC__ int le_unset(char *env) #else int le_unset(env) char *env; #endif { register int i; /* counter in a for loop */ /* * delete it from the environment */ if ((i = le_getenv(env)) > -1){ (void) free(envp[i]); for( ; envp[i] != NULL; i++) envp[i] = envp[i+1]; return(SE_NONE); } /* * no such variable */ return(SE_NOVAR); } /* * set the default umask */ #ifdef __STDC__ int le_umask(int umak) #else int le_umask(umak) int umak; #endif { /* * reset the umask */ octmask = umak; return(SE_NONE); } /* * leave a file descriptor open */ #ifdef __STDC__ int le_openfd(int fd) #else int le_openfd(fd) int fd; #endif { /* * check args */ if (0 > fd || fd >= MAX_DESC) return(SE_BADFD); /* * mark the descriptor for leaving open */ fdleave[fd] = 1; return(SE_NONE); } /* * mark a file descriptor closed */ #ifdef __STDC__ int le_closefd(int fd) #else int le_closefd(fd) int fd; #endif { /* * check args */ if (0 > fd || fd >= MAX_DESC) return(SE_BADFD); /* * mark the descriptor for closing */ fdleave[fd] = 0; return(SE_NONE); } /************* P R I V I L E G E C O N T R O L *******************/ /* * say how to handle the effective (and real) UIDs */ #ifdef __STDC__ int le_euid(int uid) #else int le_euid( uid) int uid; #endif { mresetuid = uid; return(SE_NONE); } /* * say how to handle the effective (and real) GIDs */ #ifdef __STDC__ int le_egid(int gid) #else int le_egid(gid) int gid; #endif { mresetgid = gid; return(SE_NONE); } /************* S U B C O M M A N D E X E C U T I O N *******************/ /* * get the shell to use for the subcommand */ #ifdef __STDC__ static char *shellenv(void) #else static char *shellenv() #endif { register int i; /* counter in a for loop */ register char *shptr; /* points to shell name */ /* * error check; should never happen */ if (envp == NULL && (i = initenv()) != SE_NONE) return(NULL); /* * get the shell environment variable */ for(i = 0; envp[i] != NULL; i++) if (strncmp(envp[i], "SHELL=", strlen("SHELL=")) == 0) break; /* * not defined; use the default shell */ if (envp[i] == NULL) shptr = NOSHELL; else shptr = strchr(envp[i], '=') + 1; return(shptr); } /* * like system but A LOT safer */ #ifdef __STDC__ int msystem(char *cmd) #else int msystem(cmd) char *cmd; #endif { char *argv[5]; /* argument list */ register char *p; /* temoporary pointers */ register char *shptr; /* the program to be run */ register int i; /* index number of child */ /* * if it's NULL, initialize it */ if (envp == NULL && (i = initenv()) != SE_NONE) return(i); /* * get the SHELL variable (if any) */ shptr = shellenv(); /* * set it up, just like popen */ argv[0] = ((p = strrchr(shptr, '/')) == NULL) ? shptr : p+1; argv[1] = "-c"; argv[2] = cmd; argv[3] = NULL; /* * run it */ if ((i = schild(shptr, argv, envp, (FILE **) NULL, octmask)) < 0) return(127); return(echild(i)); } /* * this structure holds the information associating * file descriptors and PIDs. It ks needed as the mpopen/mpclose interface * uses file pointers but the wait call needs a PID */ static struct popenfunc { /* association of pid, file pointer */ int pid; /* the process identifier */ FILE *fp; /* the file pointer */ } pfunc[MAX_MPOPEN]; /* * like popen but A LOT safer */ #ifdef __STDC__ FILE *mpopen(char *cmd, char *mode) #else FILE *mpopen(cmd, mode) char *cmd; char *mode; #endif { char *argv[5]; /* argument list */ register char *p; /* temoporary pointers */ register char *shptr; /* the program to be run */ FILE *fpa[3]; /* process communication descriptors */ register int indx; /* index number of child */ /* * see if anything is available */ for(indx = 0; indx < MAX_MPOPEN; indx++) if (pfunc[indx].pid == 0) break; if (indx == MAX_MPOPEN) return(NULL); /* * now get the SHELL variable (if any) */ shptr = shellenv(); /* * set it up, just like popen */ argv[0] = ((p = strrchr(shptr, '/')) == NULL) ? shptr : p+1; argv[1] = "-c"; argv[2] = cmd; argv[3] = NULL; fpa[0] = (*mode == 'w') ? stdin : NULL; fpa[1] = (*mode == 'r') ? stdout : NULL; fpa[2] = NULL; /* * run it */ if ((pfunc[indx].pid = schild(shptr, argv, envp, fpa, octmask)) < 0) return(NULL); return(pfunc[indx].fp = ((*mode == 'w') ? fpa[0] : fpa[1])); } /* * close the pipe */ #ifdef __STDC__ int mpclose(FILE *fp) #else int mpclose(fp) FILE *fp; #endif { register int indx; /* used to look for corresponding pid */ register int rstatus; /* return status of command */ /* * loop until you find the right process */ for(indx = 0; indx < MAX_MPOPEN; indx++) if (pfunc[indx].fp == fp){ /* got it ... flush and close the descriptor */ (void) fflush(fp); (void) fclose(fp); /* get the status code fo the child */ rstatus = echild(pfunc[indx].pid); /* clear the entry and return the code */ pfunc[indx].pid = 0; return(rstatus); } /* * no such process - signal no child */ return(-1); } /* * like popen but A LOT safer * uses file descriptors for all three files * (0, 1, 2) */ #ifdef __STDC__ int mfpopen(char *cmd, FILE *fpa[]) #else int mfpopen(cmd, fpa) char *cmd; FILE *fpa[]; #endif { char *argv[5]; /* argument list */ register char *p; /* temoporary pointers */ register char *shptr; /* the program to be run */ register int indx; /* index number of child */ /* * see if anything is available */ for(indx = 0; indx < MAX_MPOPEN; indx++) if (pfunc[indx].pid == 0) break; if (indx == MAX_MPOPEN) return(-1); /* * now get the SHELL variable (if any) */ shptr = shellenv(); /* * set it up, just like popen */ argv[0] = ((p = strrchr(shptr, '/')) == NULL) ? shptr : p+1; argv[1] = "-c"; argv[2] = cmd; argv[3] = NULL; /* * run it */ if ((pfunc[indx].pid = schild(shptr, argv, envp, fpa, octmask)) < 0) return(-1); return(indx); } /* * close the pipe */ #ifdef __STDC__ int mfpclose(int indx, FILE *fp[3]) #else int mfpclose(indx, fp) int indx; FILE *fp[]; #endif { register int rstatus; /* return status of command */ /* * loop until you find the right process */ if (pfunc[indx].pid == 0) return(-1); /* got it ... flush and close the descriptor */ if (fp[0] != NULL) (void) fclose(fp[0]); /* get the status code fo the child */ rstatus = echild(pfunc[indx].pid); /* clear the entry and return the code */ pfunc[indx].pid = 0; /* got it ... flush and close the descriptor */ if (fp[1] != NULL) (void) fclose(fp[1]); if (fp[2] != NULL) (void) fclose(fp[2]); return(rstatus); } /* * like popen but A LOT safer * uses arg vector, not command, and file descriptors 0, 1, 2 */ #ifdef __STDC__ int mxfpopen(char *argv[], FILE *fpa[]) #else int mxfpopen(argv, fpa) char *argv[]; FILE *fpa[]; #endif { register int indx; /* index number of child */ /* * see if anything is available */ for(indx = 0; indx < MAX_MPOPEN; indx++) if (pfunc[indx].pid == 0) break; if (indx == MAX_MPOPEN) return(-1); /* * run it */ if ((pfunc[indx].pid = schild(argv[0], argv, envp, fpa, octmask)) < 0) return(-1); return(indx); } /* * close the pipe */ #ifdef __STDC__ int mxfpclose(int indx, FILE *fp[3]) #else int mxfpclose(indx, fp) int indx; FILE *fp[]; #endif { return(mfpclose(indx, fp)); } /* * signal values */ #ifdef __STDC__ static void (*savesig[MAX_SIGNAL])(int, SIG_TYPE (*)(int)); #else static void (*savesig[MAX_SIGNAL])(); /* signal values */ #endif /* * spawn a child; the child's args and environment are as indicated, * the file descriptors 0/1/2 are redirected to the open files fp[0]/ * fp[1]/fp[2] if they are non-NULL, and the umask of the child is set * to omask */ #ifdef __STDC__ int schild(char *cmd, char **argp, char **envptr, FILE *fp[], int mask) #else int schild(cmd, argp, envptr, fp, mask) char *cmd; char **argp; char **envptr; FILE *fp[]; int mask; #endif { int p[3][2]; /* pipes to/from child */ register int i; /* counter in for loop */ register int ch_pid; /* child PID */ register int euid, egid; /* in case reset[gu]id is -1 */ /* * create 1 pipe for each of standard input, output, error */ if (fp != NULL){ if (pipe(p[0]) < 0 || pipe(p[1]) < 0 || pipe(p[2]) < 0){ ERMSG("pipes couldn't be made"); return(SE_NOPIPE); } } /* * remember the effective uid */ euid = geteuid(); egid = getegid(); /* * spawn the child and make the pipes the subprocess stdin, stdout */ if ((ch_pid = fork()) == 0){ /* now reset the uid and gid if desired */ if (mresetgid < -1) (void) setgid(getgid()); else if (mresetgid == -1) (void) setgid(egid); else if (mresetgid > -1) (void) setgid(mresetgid); if (mresetuid < -1) (void) setuid(getuid()); else if (mresetuid == -1) (void) setuid(euid); else if (mresetuid > -1) (void) setuid(mresetuid); /* reset the umask */ (void) umask(mask); /* close the unused ends of the pipe */ /* and all other files except 0, 1, 2 */ for(i = 3; i < NOFILE; i++) if (fp == NULL || (!fdleave[i] && i != p[0][0] && i != p[1][1] && i != p[2][1])) (void) close(i); /* if the parent wants to read/write to the child, */ /* dup the descriptor; we tell this if the input fp */ /* array has a NULL in the slot (no interest) */ if (fp != NULL){ if (fp[0] != NULL){ (void) dup2(p[0][0], 0); } (void) close(p[0][0]); if (fp[1] != NULL){ (void) dup2(p[1][1], 1); } (void) close(p[1][1]); if (fp[2] != NULL){ (void) dup2(p[2][1], 2); } (void) close(p[2][1]); } /* exec the command and environment */ (void) execve(cmd, argp, envptr); /* should never happen ... */ _exit(EXIT_BAD); } /* * parent process: if couldn't create child, error */ if (ch_pid != -1){ /* * ignore any signals until child dies */ for(i = 0; i < MAX_SIGNAL; i++) #ifdef SIGCLD if (i != SIGCLD) #endif savesig[i] = (SIG_TYPE (*)) signal(i, SIG_IGN); /* * close unused end of pipes */ if (fp != NULL){ (void) close(p[0][0]); (void) close(p[1][1]); (void) close(p[2][1]); } /* * use a stdio interface for uniformity */ if (fp != NULL){ if (fp[0] != NULL) fp[0] = fdopen(p[0][1], "w"); else (void) close(p[0][1]); if (fp[1] != NULL) fp[1] = fdopen(p[1][0], "r"); else (void) close(p[1][0]); if (fp[2] != NULL) fp[2] = fdopen(p[2][0], "r"); else (void) close(p[2][0]); } } /* * return child's PID */ return(ch_pid); } /* * wait for child to die */ #ifdef __STDC__ int echild(int pid) #else int echild(pid) int pid; #endif { register int r; /* PID of process just exited */ int status; /* status of wait call */ /* * done; wait for child to terminate */ while((r = wait(&status)) != pid && r != -1) ; /* * if child already dead, assume an exit status of -1 */ if (r == -1) status = -1; /* * restore signal traps */ for(r = 0; r < MAX_SIGNAL; r++) (void) signal(r, (SIG_TYPE (*)) savesig[r]); /* * return exit status */ return(status); } real) GIDs */ #ifdef __STDC__ int le_egid(int gid) #elsmsystem/README010064400016250000012000000120710573023771500141210ustar00bishopstaff00000400000012MORE SECURE SYSTEM The file msystem.c contains a version of system(3), popen(3), and pclose(3) that provide considerably more security than the standard C functions. They are named msystem, mpopen, and mpclose, respectively. While I don't guarantee them to be PERFECTLY secure, they do constrain the environment of the child quite tightly, tightly enough to close the obvious holes. By default, when you call msystem(), you get the following environment: PATH=/bin:/usr/bin:/usr/ucb:/etc SHELL=/bin/sh IFS=" \t\n" TZ=... # whatever it is set to in your environment umask 077 (no other environment variables are defined), and the EUID and EGID are reset to the RUID and RGID, respectively. All file descriptors are closed across the exec. It does NOT attempt to parse the command and determine if what you are doing should be allowed. This is because there are enough shells with different enough syntaxes so that writing one of those beasts would be a library in itself! Once you do that, though, these routines will let you execute those commands more securely than the standard libraries. Use msystem, mpopen, and mpclose exactly like system(3), popen(3), and pclose(3). ========== COMPILING Use the Makefile. Before you do anything, look in the Makefile for system- specific things to set. Then: make lib to make the libmsystem.a library make tester to build a test program make testfd to build another test program make all to make libmsystem.a, tester, and testfd make clobber clean everytWthe directory up ========== ALTERING THE ENVIRONMENT AT RUN TIME This default environment can be tailored to your liking by a series of functions: le_set("VAR=XXX") define the environment variable VAR to have value XXX in the subprocess environment le_set("VAR=") define the environment variable VAR to have an empty value in the subprocess environment le_set("VAR") define the environment variable VAR to have the same value in the subprocess environment as it does in the current environment le_unset("VAR") delete the environment variable VAR from the subprocess environment le_umask(UMASK) set the subprocess umask to UMASK (integer) le_openfd(n) do not close file descriptor n before running the subprocess le_closefd(n) close file descriptor n before running the subprocess; this is the default, but this is provided to reset things after calling le_openfd le_uid(UID) reset the effective (and real, if root) uid to UID; if uid = -1, it's not changed, if < -1, it's reset to the process effective uid le_gid(GID) reset the effective (and real, if root) gid to GID; if gid = -1, it's not changed, if < -1, it's reset to the process effective gid All return: SE_NONE no error SE_NOMEM couldn't do it; ran out of memory SE_ENVVAR couldn't do it; too many environment vars defined SE_BADUMASK umask not reset; not given a valid number SE_BADFD no such file descriptor If you want error messages tobe printed to stderr, set the global variable int le_verbose to 1. ================ CUSTOMIZING THE DEFAULTS If you don't like the default settings, you need to look in one of two places: env.h contains the macros (look towards the bottom); they can all be overridden at compile time. If you want to add new permanent environment variables (ie, the ones set by default), add a macro like the DEF_PATH one, then go into msystem.c and add the macro to the array nvfix. Presto! You've done it. I strongly recommend you do this to TZ to make it be set to your current time zone (or to delete it), because since what is in it by default is under the control of the environment, a subprocess may be able to take advantage of it. Darn System V based UNIX systems! ================ SYSTEMS IT HAS BEEN TESTED ON SunOS 4.1.3 IRIX 4.0.5 ULTRIX 4.3A If you get it running on other systems, let me know, please (ESPECIALLY if you make changes, so I can incorporate them!) ================ AUTHOR, VERSION, DISCLAIMER, ETC. Matt Bishop Department of Computer Science University of California, Davis Davis, CA 95616-8562 phone: (916) 752-8060 fax: (916) 752-4767 email: bishop@cs.ucdavis.edu This code is placed in the public domain. I do ask that you keep my name associated with it, that you not represent it as written by you, and that you preserve these comments. This software is provided "as is" and without any guarantees of any sort. ================ HISTORY Version 1.0 May 19, 1994 Matt Bishop Original version, taken and modified from passwd+ beta Version 1.1 July 5, 1994 Matt Bishop Added TZ to the default environment, value is whatever the current environment variable is set to (thanks to C. Harald Koch, chk@utcc.utoronto.ca, for this one) Version 1.2 October 4, 1994 Matt Bishop Added mxfpopen, mxfpclose; also cleaned up le_set(), in that before if you added a predefined environment variable as the first variable, it would process it, initialize the environment list (first call), and then append the name; now if le_set() is called, it initializes the environment and then does the checking Version 1.3 October 31, 1994 Matt Bishop Made global variables static the standard libraries. Use msystem, mpopen, and mpclose exactly like system(3), popen(3), and pclose(3). ========== COMPILING Use the Makefile. Before you do anything, look in the Makefile for system- specific things to set. Then: make lib to make the libmsystem.a library make tester to build a test program make testfd to build another test program make all to make libmsystem.a, tester, and testfd make clobber clean everytWthe directory upmsystem/msystem.3010064400016250000012000000236650573023771500150410ustar00bishopstaff00000400000012.\" This code is placed in the public domain. I do ask that .\" you keep my name associated with it, that you not represent .\" it as written by you, and that you preserve these comments. .\" This software is provided "as is" and without any guarantees .\" of any sort. .\" .\" Author information: .\" Matt Bishop .\" Department of Computer Science .\" University of California at Davis .\" Davis, CA 95616-8562 .\" phone (916) 752-8060 .\" email bishop@cs.ucdavis.edu .TH MSYSTEM 3 "October 31, 1994" .SH NAME msystem, mpopen, mpclose \- issue a shell command .SH SYNOPSIS .nf .B #include "env.h" .sp .B int msystem(string) .B char *string; .sp .B FILE *mpopen(cmd, mode) .B char *cmd, *mode; .sp .B int mpclose(fp) .B FILE *fp; .sp .B int mfpopen(cmd, fpa) .B char *cmd; .B FILE *fpa[3]; .sp .B int mfpclose(indx, fpa) .B int indx; .B FILE *fpa[3]; .sp .B int mxfpopen(argv, fpa) .B char *argv[]; .B FILE *fpa[3]; .sp .B int mxfpclose(indx, fpa) .B int indx; .B FILE *fpa[3]; .sp .B int le_set(env) .B char *env; .sp .B int le_unset(env) .B char *env; .sp .B le_clobber(\|) .sp .B int le_umask(umask) .B int umask; .sp .B int le_openfd(fd) .B int fd; .sp .B int le_closefd(fd) .B int fd; .sp .B int le_euid(uid) .B int uid; .sp .B int le_gid(gid) .B int gid; .fi .SH DESCRIPTION .LP The function .I msystem gives the .I string to the user's login shell as input, just as if the string had been typed as a command from a terminal. The current process performs a .IR wait (2V) system call, and waits until the shell terminates. .I Msystem then returns the exit status returned by .IR wait (2V). Unless the shell was interrupted by a signal, its termination status is contained in the 8 bits higher up from the low-order 8 bits of the value returned by .I wait . .LP The arguments to .I mpopen are pointers to null-terminated strings containing, respectively, a shell command line and an I/O mode, either .B r for reading or .B w for writing. .I Mpopen creates a pipe between the calling process and the command to be executed. The value returned is a stream pointer such that one can write to the standard input of the command, if the I/O mode is .BR w , by writing to the file stream; and one can read from the standard output of the command, if the I/O mode is .BR r , by reading from the file stream. .LP A stream opened by .I mpopen should be closed by .I mpclose , which waits for the associated process to terminate and returns the exit status of the command. .LP Because open files are shared, a type .B r command may be used as an input filter, reading its standard input (which is also the standard output of the process doing the .I mpopen ) and providing filtered input on the stream, and a type .B w command may be used as an output filter, reading a stream of output written to the stream process doing the .B mpopen and further filtering it and writing it to its standard output (which is also the standard input of the process doing the .I mpopen ). .LP The functions .I mfpopen and .I mfpclose are similar to .I mpopen and .IR mpclose , respectively. However, non-\f3\s-2NULL\s0\fP elements 0, 1, and 2 of .B fpa are connected to the standard input, output, and error of .BR cmd ; the program invoking .I mfpopen can then write to .BR fpa[0] (and .B cmd will read that as standard input) or read from .BR fpa[1] (which will be .BR cmd 's standard output) and .BR fpa [2] (which will be .BR cmd 's standard error). If any is set to .BR \s-2NULL\s0 , the appropriate file pointer refers to the same as the caller's. So, for example, to read the standard output and error of the command \&``/usr/bin/xyz -abc'' do the following: .nf .in +5n FILE *fp[3]; int ix; \&... fp[0] = NULL; /* stdin for xyz is stdin of this program */ fp[1] = stdout; /* can be anything non-NULL */ fp[2] = stderr; /* can be anything non-NULL */ if ((ix = mfpopen("/usr/bin/xyz -abc", fp) == -1) \& /* error handling and return here */ /* now fp[1] is attached to stdout of /usr/bin/xyz */ /* and fp[2] is attached to stderr of /usr/bin/xyz */ \&... status = mfpclose(ix, fp); .in .fi .LP The functions .I mxfpopen and .I mxfpclose are similar to .I mfpopen and .IR mfpclose , respectively, except that, instead of a command, they take an argument vector, and do not use the shell to execute the command. In the above example, \&``/usr/bin/xyz -abc" was executed by passing it to the appropriate shell as .ce \f2shell\fP -c "/usr/bin/xyz -abc" whereas with .IR mxfpopen , the arguments are passed directly to .IR execve (2). The same example using .I mxfpopen would be: .nf .in +5n FILE *fp[3]; int ix; char *args[3]; \&... fp[0] = NULL; /* stdin for xyz is stdin of this program */ fp[1] = stdout; /* can be anything non-NULL */ fp[2] = stderr; /* can be anything non-NULL */ args[0] = "/usr/bin/xyz"; args[1] = "-abc"; args[2] = NULL; if ((ix = mxfpopen(argx, fp) == -1) \& /* error handling and return here */ /* now fp[1] is attached to stdout of /usr/bin/xyz */ /* and fp[2] is attached to stderr of /usr/bin/xyz */ \&... status = mxfpclose(ix, fp); .in .fi .LP The .I msystem and .I mpopen functions and their variants in this library performs considerably more security-related checking than the standard .IR system (3) and .IR popen (3) functions. By default, they delete all of the caller's environment, and create a new environment composed of: .in +5n .nf \f3umask\fP set to 077 \f3uid\fP set to real UID \f3gid\fP set to real GID all file descriptors closed except for 0, 1, 2 .B PATH=/bin:/usr/bin:/usr/ucb:/etc .B SHELL=/bin/sh .B IFS= \et\en .B TZ .fi .in -5n where .B IFS is defined as a blank, tab, and newline and .B TZ is given to the subprocess as defined in your current environment. If .B SHELL is undefined or empty, the Bourne shell .IR sh (1) will be used to execute the command. The following functions allow the caller to change the environment under which the program runs; they must be called before .I msystem and .I mpopen or their variants: .LP .IR le_set ( env ) takes as its argument a shell variable setting and adds that to the .I msystem environment. If the parameter contains an equal sign `=', the text preceding the first equal sign is the name of the variable and the text following the first equal sign is the value. If the variable is already defined, the new definition replaces whatever the value of that environment variable is. If nothing follows the equal sign, the variable's value is deleted (set to nothing). If no equal sign is present, the value of that variable as defined in the user's current environment is used. For example, suppose the user's current environment has .B HOME set to ``/usr/bishop''; by default, .B HOME is undefined when .I msystem is invoked. Then .ce le_set("HOME=/") sets .B HOME to ``/'' for the command executed by .IR msystem ; and .ce le_set("PATH=") makes .B PATH have no value for the command executed by .IR msystem . .LP The function .IR le_unset ( env ) deletes the named environment variable from the environment under runs its command. For example, .ce le_unset("SHELL") deletes the variable .BR SHELL . Note that this is not the same as .ce le_set("SHELL=") because in the latter, the environment variable .B SHELL is defined (although as the empty string). .LP The function .I le_clobber erases all of the environment variables except for the preset ones (\c .BR PATH , .BR SHELL , and .BR IFS ). .LP The function .IR le_umask ( umask ) resets the .B umask value; the value of .I umask is interpreted as a C integer (so if you want octal, put in a leading 0!) For example, .ce le_umask(022) resets the .B umask variable to 022. Note this is considerably different than saying .ce le_umask(22) .LP By default, all file descriptors except the standard input, output, and error are closed before the child process is run. To force descriptor .I fd to remain open, use the function .IR le_openfd ( fd ); to force it to close, use .IR le_closefd ( fd ). The latter function is provided to counter an erroneous call to .IR le_openfd . .LP To set the real and effective UID (GID) of the environment under which .I msystem runs, use the function .IR le_euid ( uid ) (\c .IR le_gid ( gid )) which, when given a non-negative argument, cause the real and effective UID (GID) to be reset to .IR uid " (" gid ). If .IR uid " (" gid ) is \-1, they are not reset; if the value is less than \-1, the effective UID (GID) is reset to the real UID (GID). .IR .SH "SEE ALSO" .BR sh (1), .BR execve (2V), .BR wait (2V), .BR popen (3S) .BR system (3) .SH DIAGNOSTICS .LP The following error codes are returned: .nf .ta 5n 10n 15n 20n 25n 30n SE_NONE no error; function was successful .br SE_NOMEM ran out of memory .br SE_NOPIPE a pipe could not be created .br SE_NOVAR tried to delete a nonexistant environment variable .LP Exit status 127 indicates the shell could not be executed. .SH "BUGS" .PP The command passed to .I msystem is not checked for special shell metacharacters like `;'. In the author's opinion, this is a feature, as different shells use different characters for metacharacters. However, the user must check the command before calling .I msystem to be sure the command is acceptable. .PP That .B TZ must be taken from your environment opens a myriad of possible ways to attack, (the precise number depends on what the called program does with that variable), and so I .B strongly recommend it be fixed at compile time by the installer. .SH AUTHOR Matt Bishop .br Department of Computer Science .br University of California, Davis .br Davis, CA 95616-8562 .br email: bishop@cs.ucdavis.edu .SH "VERSION" .HP version 1.0, May 19, 1994 .br Initial version for distribution .HP version 1.1, July 5, 1994 .br Added TZ to the list of environment variables to be passed on by default; you get what the environment gives you (as required by System V based systems) .HP version 1.2, October 4, 1994 .br Added .IR mxfpopen , .IR mxfpclose functions; also modified .I le_set to eliminate duplicate environment variable names in the list (before, if you reset a predefined environment variable name and this was the first environment variable set, you would get two values in the list). een the calling process and the command to be executed. The value returned msystem/tester.c010064400016250000012000000135440573023771500147210ustar00bishopstaff00000400000012/* * This is the testing module -- type "h" at the prompt * If you want to test the setuid/setgid stuff, you * should make this setuid to root * * Author information: * Matt Bishop * Department of Computer Science * University of California at Davis * Davis, CA 95616-8562 * phone (916) 752-8060 * email bishop@cs.ucdavis.edu * * This code is placed in the public domain. I do ask that * you keep my name associated with it, that you not represent * it as written by you, and that you preserve these comments. * This software is provided "as is" and without any guarantees * of any sort. * * Version information: * 1.0 May 25, 1994 Matt Bishop */ #include #include #include #include #include "env.h" /* * this is the signal handler * * Why is this here? You really don't need it, but during * testing I got a SIGPIPE that I couldn't explain; turned * out there was a problem with dup'ing a file descriptor * in the child creation routine. So, I fixed it, but just * in case there's a problem with porting this thing, I've * left the code in place for future use. * * If you get a signal, quickout is set to 1 so the mpopen * loops terminate */ int quickout = 0; /* drop out of loop */ /* * primitive signal handler */ #ifdef __STDC__ void subsig(int signo) #else void subsig(signo) int signo; #endif { /* * be informative on SIGPIPE or child * termination, and cryptic on everything else */ if (signo == SIGPIPE || signo == SIGCHLD){ printf("child died; signal "); if (signo == SIGCHLD) printf("SIGCHLD"); else if (signo == SIGPIPE) printf("SIGPIPE"); else printf("%d", signo); printf(" caught\n"); } /* say you got something */ quickout = 1; /* * reset these (needed on some systems, * unnecessary on others) */ (void) signal(SIGPIPE, subsig); (void) signal(SIGCHLD, subsig); } /* * discard leading whitespace */ #ifdef __STDC__ char *eatblanks(char *x) #else char *eatblanks(x) char *x; #endif { while(isspace(*x)) x++; return(x); } /* * error handler * just say what happened */ #ifdef __STDC__ void oops(int code) #else void oops(code) int code; #endif { switch(code){ case SE_NONE: /* no error */ return; case SE_NOMEM: /* no memory */ printf("ran out of memory\n"); return; case SE_NOPIPE: /* no pipes */ printf("can't create another pipe\n"); return; case SE_NOVAR: /* no variable */ printf("unknown environment variable\n"); return; case SE_BADFD: /* no file descriptor */ printf("unknown file descriptor\n"); return; default: /* no idea! */ printf("unknown error %d\n", code); return; } } /* * the start of the problem */ #ifdef __STDC__ void main(void) #else void main() #endif { char buf[1024]; /* input buffer */ char cmd[1024]; /* command buffer */ char *p; /* used to walk command buffer */ int um; /* temporary used for a multitude of sins */ FILE *fp; /* used for mpopen/mpclose */ /* * may be paranoia, but catch these * in case there are kiddie problems */ (void) signal(SIGPIPE, subsig); (void) signal(SIGCHLD, subsig); /* * do it! */ while(printf("> "), gets(buf) != NULL){ /* eat leading blanks */ p = eatblanks(buf); /* do the command */ switch(buf[0]){ case 'i': /* initialize */ le_clobber(); break; case 'u': /* set effective user id */ p = eatblanks(++p); if (sscanf(p, "%d", &um) != 1) printf("need numeric uid\n"); else oops(le_euid(um)); break; case 'g': /* set effective group id */ p = eatblanks(++p); if (sscanf(p, "%d", &um) != 1) printf("need numeric gid\n"); else oops(le_egid(um)); break; case 'a': /* add environment variable */ p = eatblanks(++p); oops(le_set(p)); break; case 'd': /* delete environment variable */ p = eatblanks(++p); oops(le_unset(p)); break; case 'm': /* set file creation mask */ p = eatblanks(++p); if (sscanf(p, "%o", &um) != 1) printf("need octal umask\n"); else oops(le_umask(um)); break; case 'c': /* set command to run */ p = eatblanks(++p); (void) strcpy(cmd, p); break; case 'r': /* run command using msystem */ printf("status is %d\n", msystem(cmd)); quickout = 0; break; case 'P': /* run command, read from pipe */ if ((fp = mpopen(cmd, "r")) == NULL) printf("%s could not be executed\n", cmd); else{ while(!quickout && fgets(buf, sizeof(buf), fp) != NULL) printf("%s", buf); quickout = 0; printf("status is %d\n", mpclose(fp)); } break; case 'p': /* run command, write to pipe */ if ((fp = mpopen(cmd, "w")) == NULL) printf("%s could not be executed\n", cmd); else{ while(printf("input (. to stop)> "), (!quickout && fgets(buf, 1024, stdin) != NULL && strcmp(buf, ".\n") != 0)){ fprintf(fp, "%s", buf); fflush(fp); } quickout = 0; printf("status is %d\n", mpclose(fp)); } break; case '-': /* fd to be kept open */ if (scanf("%d", &um) == EOF) exit(1); oops(le_openfd(um)); break; case '|': /* fd to be kept closed */ if (scanf("%d", &um) == EOF) exit(1); oops(le_closefd(um)); break; case 'q': /* exit */ exit(1); default: /* help message */ printf("- nn\tkeep file descriptor nn open\n"); printf("| nn\tkeep file descriptor nn closed\n"); printf("a arg\tset environment variable\n"); printf("c line\trest of line is command\n"); printf("d arg\tunset environment variable\n"); printf("g nnn\tset (real & effective) gid to nnn\n"); printf("i\tinitialize environment\n"); printf("m nnn\tset umask to nnn (octal)\n"); printf("P\texecute command, pipe output\n"); printf("p\texecute command, pipe from stdin\n"); printf("q\tquit\n"); printf("r\texecute command\n"); printf("u nnn\tset (real & effective) uid to nnn\n"); break; } } /* * say good night, Dick! */ exit(0); } msystem/env.h010064400016250000012000000044700573023771500142060ustar00bishopstaff00000400000012/* * This is the header file; include it in programs that use * the more secure system call (or the more secure popen call) * It also contains error codes and such * * Author information: * Matt Bishop * Department of Computer Science * University of California at Davis * Davis, CA 95616-8562 * phone (916) 752-8060 * email bishop@cs.ucdavis.edu * * This code is placed in the public domain. I do ask that * you keep my name associated with it, that you not represent * it as written by you, and that you preserve these comments. * This software is provided "as is" and without any guarantees * of any sort. * * Version information: * 1.0 May 25, 1994 Matt Bishop */ /* * forward declarations */ #ifdef __STDC__ void le_clobber(void); int le_set(char *); int le_unset(char *); int le_umask(int); int le_openfd(int); int le_closefd(int); int le_euid(int); int le_egid(int); int msystem(char *); FILE *mpopen(char *, char *); int mpclose(FILE *); int mfpopen(char *, FILE *[]); int mfpclose(int, FILE *[]); int mxfpopen(char *[], FILE *[]); int mxfpclose(int, FILE *[]); int schild(char *, char *[], char *[], FILE *[], int); int echild(int); #else void le_clobber(); int le_set(); int le_unset(); int le_umask(); int le_openfd(); int le_closefd(); int le_euid(); int le_egid(); int msystem(); FILE *mpopen(); int mpclose(); int mfpopen(); int mfpclose(); int mxfpopen(); int mxfpclose(); int schild(); int echild(); #endif /* * define error codes */ #define SE_NONE 0 /* no error */ #define SE_NOMEM -1 /* no memory */ #define SE_NOPIPE -2 /* no pipes */ #define SE_NOVAR -3 /* variable not defined */ #define SE_BADFD -4 /* invalid file descriptor */ /* * default security settings */ #ifndef DEF_UMASK # define DEF_UMASK 077 /* only owner has privileges */ #endif #ifndef UID_RESET # define UID_RESET -2 /* reset EUID to RUID */ #endif #ifndef GID_RESET # define GID_RESET -2 /* reset EGID to RGID */ #endif #ifndef DEF_PATH # define DEF_PATH "PATH=/bin:/usr/bin:/usr/ucb" /* default search path */ #endif #ifndef DEF_SHELL # define DEF_SHELL "SHELL=/bin/sh" /* default shell */ #endif #ifndef DEF_IFS # define DEF_IFS "IFS= \t\n" /* default IFS */ #endif #ifndef DEF_TZ # define DEF_TZ "TZ" /* default TZ */ #endif #ifndef NOSHELL # define NOSHELL "/bin/sh" /* use this if no shell */ #endif msystem/testfd.1010064400016250000012000000026130573023771500146150ustar00bishopstaff00000400000012.\" This code is placed in the public domain. I do ask that .\" you keep my name associated with it, that you not represent .\" it as written by you, and that you preserve these comments. .\" This software is provided "as is" and without any guarantees .\" of any sort. .\" .\" Author information: .\" Matt Bishop .\" Department of Computer Science .\" University of California at Davis .\" Davis, CA 95616-8562 .\" phone (916) 752-8060 .\" email bishop@cs.ucdavis.edu .TH TESTFD 1 "May 18, 1994" .SH NAME testfd \- visualit the file descriptor closing and opening .SH SYNOPSIS .BR testfd " [" .IR file " ]" .SH DESCRIPTION .LP .I Testfd opens the named .I file (default ``/etc/passwd''), and marks the file descriptor to remain open during the .IR mpopen (3) function. It then uses .I mpopen to execute a command that reads from the new file descriptor, prepends a `#' character, and prints the line on the standard output. It marks the file descriptor to close during the ,IR mpopen , and repeats the test. .PP The test succeeds if the file is printed the first time with a `#' at the beginning of each line, and then the file does not print when the file descriptor is marked closed. .SH AUTHOR Matt Bishop .br Department of Computer Science .br University of California, Davis .br Davis, CA 95616-8562 .br email: bishop@cs.ucdavis.edu .SH "VERSION" .HP version 1.0, May 19, 1994 Initial version for distribution msystem/tester.1010064400016250000012000000052630573023771500146360ustar00bishopstaff00000400000012.\" This code is placed in the public domain. I do ask that .\" you keep my name associated with it, that you not represent .\" it as written by you, and that you preserve these comments. .\" This software is provided "as is" and without any guarantees .\" of any sort. .\" .\" Author information: .\" Matt Bishop .\" Department of Computer Science .\" University of California at Davis .\" Davis, CA 95616-8562 .\" phone (916) 752-8060 .\" email bishop@cs.ucdavis.edu .TH TESTER 1 "May 18, 1994" .SH NAME tester \- testing the more secure children functions .SH SYNOPSIS .B tester .SH DESCRIPTION .LP .I Tester allows the user to try various features of .IR msystem.c . The program takes no arguments; type your commands to the prompt ``>''. The commands define the subprocess environment and the command to be executed in that subprocess; they are: .TP \- \f2nnn\fP keep file descriptor \f2nnn\fP open when the subcommand is run .TP | \f2nnn\fP keep file descriptor \f2nnn\fP closed when the subcommand is run .TP a \f2arg\fP set environment variable. If .I arg is the variable name, it is defined to have the same value as in the current user's environment; if it is the variable name followed by an ``='', it is defined to have the empty value; and if it is of the form ``\f2name\fP=\f2val\fP'', it is defined to have the value .IR val . .TP c \f2line\fP .I line is the command to be run; it begins with the first non-whitespace character after ``c'' and runs to the end of the line. This does .B not execute the command, but stores it for later use. .TP d \f2arg\fP Delete the environment variable .I arg from the environment. .TP g \f2nnn\fP Set the GID of the subprocess to .IR nnn . .TP i Initialize the environment. All user-defined environment variables are deleted, and the umask, default UID, and default GID resume their initial values. .TP m \f2nnn\fP Set the umask of the subprocess to .IR nnn . .I nnn is read as an octal number. .TP P Run the stored command using .IR mpopen and pipe the output through the tester. The tester simply copies the command's output to the standard output. .TP p Run the stored command using .IR mpopen . The tester prompts the user for input, and as each line is typed passes that input to the subprocess. The command then reads that input. To terminate the subprocess, enter a line consisting only of the character ``.''. .TP q Quit; exit the program. .TP r Run the stored command usin .IR msystem . .TP u \f2nnn\fP Set the UID of the subprocess to .IR nnn . .SH AUTHOR Matt Bishop .br Department of Computer Science .br University of California, Davis .br Davis, CA 95616-8562 .br email: bishop@cs.ucdavis.edu .SH "VERSION" .HP version 1.0, May 19, 1994 Initial version for distribution msystem/CERT-TOOLS010064400016250000012000000033520573023771500146210ustar00bishopstaff00000400000012Hello, everyone! I've heard some discussion about running UNIX subcommands safely using system(3), so I thought I'd chip in. From passwd+ beta, I've taken some code which allows you to completely control the subcommand environment, and made it into a little library. By default, you get this environment: PATH=/bin:/usr/bin:/usr/ucb:/etc SHELL=/bin/sh IFS=" \t\n" umask 077 (no other environment variables are defined), and the EUID and EGID are reset to the RUID and RGID, respectively. All file descriptors are closed across the exec. There are functions to modify (add to, delete from) this as your program desires. It, along with documentation and two test programs, is available for anonymous ftp on tam.cs.ucdavis.edu; get the file pub/security/msystem.tar. Unpack the tar file, type "make all", and you're off! It works on SunOS 4.1.3, ULTRIX 4.3A, and IRIX 1.0.5. It probably works on lots more, but those were the only systems I could test it on. One warning: the msystem function (and the mpopen and mpclose functions, which are more secure versions of popen and pclose and are included), does NOT attempt to parse the command and determine if what you are doing should be allowed. This is because there are enough shells with different enough syntaxes so that writing one of those beasts would be a library in itself! Once you do that, though, these routines will let you execute those commands more securely than the standard libraries. Use msystem, mpopen, and mpclose exactly like system(3), popen(3), and pclose(3). If you do port this elsewhere and need to change something, please let me know so I can update the master sources. This is version 1.0, and should be robust. Happy hacking (I mean in the non-security sense!) Matt msystem/Makefile010064400016250000012000000031050573023771500146770ustar00bishopstaff00000400000012# # makefile for security-enhanced system # CC = gcc -ansi -pedantic #CC = cc # set -DSTRDUP if strdup is a library function on your system # set -DMAX_MPOPEN=n if you'll make more than 20 popen calls at the same time # (here, n is the maximum number you will make at once) # set -DSIG_TYPE=int if the base type of signal is an int and not a void # you can reset the following to change the default command encironment # within msystem: # DEF_UMASK 077 umask # DEF_PATH /bin:/usr/bin:/usr/ucb search path # DEF_SHELL /bin/sh shell # DEF_IFS \t\n IFS (blank, tab, newline) # UID_RESET -2 reset EUID to RUID # GID_RESET -2 reset EGID to RGID # DEFINES = -DSTRDUP CFLAGS = -g $(DEFINES) # # programs # ARCH = ar # archiver (library builder) ARFLAGS = rv # on BSD, it's rv; on System V, it's rvs LINT = lint # lint (strict K&R C checker) LINTFLAGS = -abch # on BSD, it's -abch; on System V, it's nothing RANLIB = ranlib # on BSD, it's ranlib; on System V, it's true RM = rm # file deletion command RMFLAGS = -f # options to file deletion command # # library file names # LIBSRC = msystem.c LIBOBJ = msystem.o LIB = libmsystem.a # # the rules # lib: $(LIB) all: tester testfd tester: $(LIB) tester.o $(CC) $(CFLAGS) -o tester tester.o $(LIB) testfd: $(LIB) testfd.o $(CC) $(CFLAGS) -o testfd testfd.o $(LIB) $(LIB): $(LIBOBJ) $(AR) $(ARFLAGS) $(LIB) $(LIBOBJ) $(RANLIB) $(LIB) # # support stuff # lint: lint $(LINTFLAGS) msystem.c clean: $(RM) $(RMFLAGS) tester.o testfd.o $(LIBOBJ) clobber: $(RM) $(RMFLAGS) tester.o testfd.o $(LIBOBJ) tester testfd $(LIB) a.out core ERRS