/* fuzz version 0.5 11/16/1999 Copyright Ben Woodard & VA Linux Systems. All rights reserved. Licence: GPL The purpose of this program is to generate random garbage and pass that random garbage into a program to see if it can be made to crash or hang. It will pass this garbage into the program in one of two ways. The first is on the command line and the second is on through standard in. It runs the victim program a specified number of times dumping any output out to /dev/null. If it manages to get the program to crash, it stops. The reason for this is debatable but reasonable. I figured that once we have discovered one exploit it is best to stop and fix it rather than discovering literally hundreds of ways of triggering the same bug. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_READLINE #include #include #else // Little replacement for readline if it is not available char *chomp(char *str); char *readline(const char *prompt){ char *retval; printf("%s",prompt); retval=malloc(10240); retval[10239]=0; fgets(retval,10239,stdin); return chomp(retval); } #endif #define BADOPS_EXIT 1 #define CANT_CHROOT_EXIT 2 #define CANT_EXEC_EXIT 3 #define NORAND_EXIT 4 #define ARGFILE_EXIT 5 #define NOMEM_EXIT 6 #define CANT_FORK_EXIT 7 #define CHILDFD_EXIT 8 #define NORCFILE_EXIT 9 #define NOREPORT_EXIT 10 #define RESULT_PASSED 1 #define RESULT_FAILED 0 /* borrowed this from vixie-cron -Fx = set full-name of sender -odi = Option Deliverymode Interactive -oem = Option Errors Mailedtosender -or0s = Option Readtimeout -- don't time out (which doesn't seem to work for fuzz) */ #define SENDMAILARGS "-odi -oem -Ffuzz" #define DESTADDR "fuzzmonster@zgp.org" /* Probably the best way to do this is to make the RAND_DEVICE /dev/random however this requires user input and so it it really won't work when you need a large amount of data. It will literally take a couple of seconds for each couple of bytes. */ #define RAND_DEVICE "/dev/urandom" // #define RAND_DEVICE "/dev/random" #define DEFRUNS 10000 #define DEFLEN 100000 #define DEFTIMEOUT 120 #define MAXPATH 10240 #define MAXARGS 256 #define MAXARGLEN 256 /* global variables These are either needed by the signal handlers or send_report which is called by the signal handlers */ unsigned long runs=DEFRUNS; char progname[MAXPATH]; char outfilename[MAXPATH]; pid_t newpid; char rundone=0; unsigned long max_args=0; char *argfilename; int g_argc; char **g_argv; int report; char sendmail[MAXPATH]; char distribution[MAXPATH]; char dontask=0; void print_arglist(FILE*,char **,char,char); int setup_user(char *distribution, char *sendmail); void handle_sigalrm(int dummy); void handle_sigchld(int dummy); void do_child(int *progpipe, char **argv, char *execute_filename, unsigned long max_arglen,char printable_only,int nullfd, int randfd); void send_report(char result, char report, char *sendmail, char *distribution); char *chomp(char * str){ int i=strlen(str); if(str[i-1]=='\n') str[i-1]=0; return str; } void usage(){ fprintf(stderr,"usage: fuzz [-p] [-r runcount] [-p] [-n linemod] [-l length] [-m maxlinelen]\n [-t timeout] [-c] [-u user] [-x maxargs] [-y maxarglen] [-e number]\n command [arg...]\n"); exit(BADOPS_EXIT); } int main(int argc, char **argv){ char binfound=0; unsigned long max_arglen=MAXARGLEN; char *execute_filename=NULL; char printable_only=0; int curarg; unsigned long len=DEFLEN; unsigned long timeout=DEFTIMEOUT; unsigned long maxline=0; unsigned long linemod=0; char print_bytes=0; char omit_data=0; char chr=0; int prio; static const struct option longopts[]={ {"args", no_argument, NULL,'a'}, {"bytes", no_argument, NULL,'b'}, {"chroot", no_argument, NULL,'c'}, {"dontask", no_argument, NULL,'d'}, {"execute", required_argument,NULL,'e'}, {"priority", required_argument,NULL,'i'}, {"length", required_argument,NULL,'l'}, {"maxline", required_argument,NULL,'m'}, {"newlines", required_argument,NULL,'n'}, {"omitdata", no_argument, NULL,'o'}, {"printable", no_argument, NULL,'p'}, {"runcount", required_argument,NULL,'r'}, {"timeout", required_argument,NULL,'t'}, {"user", required_argument,NULL,'u'}, {"version", no_argument, NULL,'V'}, {"maxargs", required_argument,NULL,'x'}, {"maxarglen", required_argument,NULL,'y'}, NULL }; char *path; char bin_found=0; FILE *outfile; FILE *infile; struct sigaction act; struct passwd *userinfo=NULL; int nullfd; int randfd; g_argc=argc; g_argv=argv; while((curarg=getopt_long(argc,argv,"+a::bcde:i:l:m:n:opr:t:u:Vx:y:", longopts,NULL))!=EOF){ switch(curarg){ case 'r': if(sscanf(optarg,"%ul",&runs)!=1){ fprintf(stderr,"Bad number of runs.\n"); usage(); } break; case 'l': if(sscanf(optarg,"%ul",&len)!=1){ fprintf(stderr,"Bad length of data stream.\n"); usage(); } break; case 'p': printable_only=1; break; case 'n': if(sscanf(optarg,"%ul",&linemod)!=1){ fprintf(stderr,"Bad line length modifier.\n"); usage(); } break; case 'm': if(sscanf(optarg,"%ul",&maxline)!=1){ fprintf(stderr,"Bad max line length.\n"); usage(); } break; case 't': if(sscanf(optarg,"%ul",&timeout)!=1){ fprintf(stderr,"Bad max line length.\n"); usage(); } break; case 'b': print_bytes=1; break; case 'c': chr=1; break; case 'u': if((userinfo=getpwnam(optarg))==NULL){ fprintf(stderr,"Can't look up user id.\n"); perror(argv[0]); exit(BADOPS_EXIT); } break; case 'o': omit_data=0; break; case 'a': max_args=MAXARGS; break; case 'x': if(sscanf(optarg,"%ul",&max_args)!=1){ fprintf(stderr,"Bad max number of args.\n"); usage(); } break; case 'y': if(sscanf(optarg,"%ul",&max_arglen)!=1){ fprintf(stderr,"Bad max argument length.\n"); usage(); } break; case 'e': execute_filename=optarg; runs=1; break; case 'i': if(sscanf(optarg,"%i",&prio)!=1){ fprintf(stderr,"Bad priority argument.\n"); usage(); } if (setpriority(PRIO_PROCESS,0,prio)) { fprintf(stderr,"Error setting nice priority.\n"); perror(argv[0]); } break; case 'V': printf("full %s\n",VERSION); exit(0); case 'd': dontask=1; break; default: fprintf(stderr,"Bad argument.\n"); // intentionally falls through case '?': usage(); } } /* these need to be before the chroot and before the user swap so that it can get at the configuration file. This is probably a security bug. */ report=setup_user(distribution,sendmail); if(execute_filename){ if((infile=fopen(execute_filename,"r"))==NULL){ fprintf(stderr,"Can't seem to open file to read from.\n"); perror(argv[0]); exit(BADOPS_EXIT); } }else{ //open up /dev/random if((randfd=open(RAND_DEVICE,O_RDONLY))==-1){ fprintf(stderr,"Cannot open %s.\n",execute_filename); exit(NORAND_EXIT); } if((infile=fdopen(randfd,"r"))==NULL){ fprintf(stderr,"Can't open /dev/random for reading\n"); exit(NORAND_EXIT); } } if((nullfd=open("/dev/null",O_WRONLY))==-1){ perror("fuzz"); fprintf(stderr,"Can't open /dev/null.\n"); exit(CHILDFD_EXIT); } // end of stuff that must before chroot if(chr){ char curpath[MAXPATH]; if(getcwd(curpath,MAXPATH)==NULL){ fprintf(stderr,"Can't get current path name to chroot to.\n"); perror(argv[0]); usage(); } if(chroot(curpath)==-1){ fprintf(stderr,"Can't chroot.\n"); perror(argv[0]); exit(CANT_CHROOT_EXIT); } } if(userinfo && setreuid(userinfo->pw_uid,userinfo->pw_uid)==-1){ fprintf(stderr,"Can't change to user: %s\n",optarg); perror(argv[0]); exit(BADOPS_EXIT); } //make sure this isn't being run as root. if(getuid()==0){ fprintf(stderr,"*** Don't run this program as root! ***\n"); usage(); } //check that the program exists if(optind==argc) //They didn't tell me what program to run usage(); if(!(argv[optind][0]=='/' || argv[optind][0]=='.')){ //if we don't know the full path already. char *modpath,*tok; if(getenv("PATH")==NULL){ fprintf(stderr,"Warning: no path set using /bin:/usr/bin\n"); path="/bin:/usr/bin"; }else path=getenv("PATH"); modpath=strdup(path); for(tok=strtok(modpath,":");tok!=NULL;tok=strtok(NULL,":")){ struct stat statbuf; *progname=0; strncpy(progname,tok,MAXPATH); strncat(progname,"/",MAXPATH); strncat(progname,argv[optind],MAXPATH); if(stat(progname,&statbuf)==0){ binfound=1; break; } } }else{ // check that the binary is there struct stat statbuf; strcpy(progname,argv[optind]); if(stat(progname,&statbuf)==0) binfound=1; } if(!binfound){ fprintf(stderr,"Program not found.\n"); usage(); } // setup the signals act.sa_handler=handle_sigalrm; if(sigemptyset(&act.sa_mask)==-1){ fprintf(stderr,"can't clear signal mask.\n"); abort(); } act.sa_flags=0; sigaction(SIGALRM,&act,NULL); act.sa_handler=handle_sigchld; if(sigaddset(&act.sa_mask,SIGALRM)==-1){ fprintf(stderr,"Can't add SIGALRM to signal mask.\n"); abort(); } sigaction(SIGCHLD,&act,NULL); // clearerr(infile); // run the program on the data sets for(;runs;runs--){ int progpipe[2],status; char sendnewline=0; unsigned long curchar=0,linelen=0; int fd; // finish setting up files if(!execute_filename){ snprintf(outfilename,MAXPATH,"/tmp%s.%lu.XXXXXX",strrchr(progname,'/'),runs); if ((fd=mkstemp(outfilename)) < 0) { perror("Unable to create temporary file"); abort(); } close(fd); if((outfile=fopen(outfilename,"w"))==NULL){ fprintf(stderr,"Can't fopen outfile.\n"); abort(); } if((argfilename=strdup(outfilename))==NULL){ fprintf(stderr,"Failed to strdup outfilename.\n"); exit(ARGFILE_EXIT); } if((argfilename=realloc(argfilename,strlen(argfilename)+5))==0){ fprintf(stderr,"Failed to realloc outfilename.\n"); exit(ARGFILE_EXIT); } strcat(argfilename,".arg"); } printf("Run: %u %c",runs,print_bytes?'\n':'\r'); if(pipe(progpipe)==-1){ fprintf(stderr,"Can't pipe.\n"); abort(); } rundone=omit_data?1:0; // this implements the omit data feature if((newpid=fork())==-1){ fprintf(stderr,"Can't fork.\n"); abort(); } else if(newpid==0){ //childproc do_child(progpipe,argv+optind,execute_filename,max_arglen, printable_only,nullfd,randfd); } /**** handle main process */ //start timer alarm(timeout); //feed chars down the pipe #ifdef HAVE_SENDFILE if(!print_bytes && !printable_only && !execute_filename && !linemod && !rundone){ off_t offset=0; if(sendfile(progpipe[1],randfd,&offset,len)==-1){ fprintf(stderr,"sendfile failed\n"); perror("fuzz"); exit(NORAND_EXIT); } }else{ #endif for(curchar=0,linelen=0; !rundone && curchar "))==NULL){ fprintf(stderr,"Error processing ldd output -- can't find =>\n"); exit(NOREPORT_EXIT); } cur+=4; if(strtok(cur," ")==NULL){ fprintf(stderr,"Error processing ldd output -- can't find space\n"); exit(NOREPORT_EXIT); } if(lstat(cur,&statbuf)==-1){ fprintf(stderr,"Can't stat library %s.\n",cur); exit(NOREPORT_EXIT); } if(S_ISLNK(statbuf.st_mode)){ char tmpbuf[MAXPATH]; memset(tmpbuf,0,MAXPATH); if(readlink(cur,tmpbuf,MAXPATH-1)==-1){ fprintf(stderr,"Can't readlink library %s\n",cur); perror("fuzz"); exit(NOREPORT_EXIT); } fprintf(mail,"library[%d]=%s\n",i,tmpbuf); }else{ fprintf(mail,"library[%d]=%s\n",i,cur); } } pclose(version); // item 9 the results of the test fputs(" 9",stdout); fflush(stdout); fprintf(mail,"results=%s\n", result==RESULT_PASSED?"pass":"fail"); // item 10 the packaging info fputs(" 10",stdout); fflush(stdout); strbuf[0]=0; fprintf(mail,"version="); if(distribution[0]=='K' || distribution[0]=='N' || distribution[0]=='C' || distribution[0]=='R'){ snprintf(strbuf,MAXPATH-1,"rpm -qf %s",progname); if((version=popen(strbuf,"r"))==NULL){ fprintf(stderr,"Can't execute %s\n",strbuf); fprintf(mail,"UNK1\n.\n"); exit(NOREPORT_EXIT); } if(fgets(strbuf,MAXPATH-1,version)==NULL){ fprintf(stderr,"strange output from rpm -qf\n"); fprintf(mail,"UNK2\n.\n"); exit(NOREPORT_EXIT); } fputs(strbuf,mail); //consume remaining data while(!feof(version)){ fgets(strbuf,MAXPATH-1,version); } pclose(version); } else if(distribution[0]=='S' || distribution[0]=='O' || distribution[0]=='B' || distribution[0]=='D'){ snprintf(strbuf,MAXPATH-1,"dpkg -S %s",progname); if((version=popen(strbuf,"r"))==NULL){ fprintf(stderr,"Can't execute %s\n",strbuf); fprintf(mail,"UNK\n.\n"); exit(NOREPORT_EXIT); } if(fgets(strbuf,MAXPATH-1,version)==NULL){ fprintf(stderr,"Can't read dpkg -S output.\n"); fprintf(mail,"UNK2\n.\n"); exit(NOREPORT_EXIT); } //consume remaining data while(!feof(version)){ fgets(strbuf,MAXPATH-1,version); } pclose(version); if(strtok(strbuf,":")==NULL){ fprintf(stderr,"Strange output from dpkg -S.\n"); fprintf(mail,"UNK3\n.\n"); exit(NOREPORT_EXIT); } tmp=strdup(strbuf); fprintf(mail,"%s-",tmp); snprintf(strbuf,MAXPATH-1,"dpkg -s %s",tmp); free(tmp); if((version=popen(strbuf,"r"))==NULL){ fprintf(stderr,"Can't execute %s\n",strbuf); fprintf(mail,"UNK4\n.\n"); exit(NOREPORT_EXIT); } while(!feof(version)){ fgets(strbuf,MAXPATH-1,version); if(feof(version)) break; if(!strncmp(strbuf,"Version: ",9)) fputs(strbuf+9,mail); } pclose(version); } // item 11 the data set which caused the crash fputs(" 11",stdout); fflush(stdout); if(result==RESULT_PASSED){ fputs(".\n",mail); // end the mail fputs(" done\n",stdout); fflush(stdout); }else{ // send all the pertinent data int fd,len,i; fprintf(mail,"data\n"); if((fd=open(outfilename,O_RDONLY))==-1){ // this should never happen fprintf(stderr,"can't reopen outfile.\n"); perror("fuzz"); pclose(mail); exit(NOREPORT_EXIT); } while((len=read(fd,strbuf,MAXPATH))!=-1 && len!=0){ for(i=0;i