1 /**********************************************************************
2  * pscan: http://www.striker.ottawa.on.ca/~aland/pscan/
3  *
4  * Copyright (C) 2000,2007 Alan DeKok <aland@deployingradius.com>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  *
20  **********************************************************************/
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <errno.h>
26 #include <assert.h>
27 #include <unistd.h>
28 
29 #include "pscan.h"
30 
31 extern int yylex();
32 extern int yylineno;
33 extern FILE *yyout, *yyin;
34 
35 /*
36  *  This function does nothing useful.
37  */
yywrap()38 int yywrap()
39 {
40   return(1);
41 }
42 
43 static int verbose = 0;
44 static int warnings = FALSE;
45 static char *filename;
46 
47 #define FSM_MAX_STACK 8192
48 static parser_state_t fsm_stack[FSM_MAX_STACK];
49 static int stack_index = 0;
50 parser_state_t *state = NULL;
51 
52 static int total_errors = 0;
53 static int total_warnings = 0;
54 
55 /*
56  *  The statically defined list of problem functions,
57  *  common to most C libraries.
58  */
59 static problem_t problem_functions[] = {
60   { "vsprintf", 1},
61   { "vfprintf", 1},
62   { "vprintf", 0},
63   { "vsnprintf", 2},
64   { "snprintf", 2},
65   { "sprintf", 1},
66   { "fprintf", 1},
67   { "fscanf", 1},
68   { "printf", 0},
69   { "scanf", 0},
70   { "sscanf", 1},
71   { "syslog", 1},
72   { "setproctitle", 0},
73   { "err", 1},
74   { "verr", 1},
75   { "errx", 1},
76   { "verrx", 1},
77   { "warn", 0},
78   { "vwarn", 0},
79   { "warnx", 0},
80   { "vwarnx", 0},
81 
82   { NULL, 0}
83 };
84 
85 /*
86  *  User-supplied problem functions.
87  *
88  *  Yes, this is only a static buffer, but I'm too lazy to code
89  *  a proper malloc/linked list replacement.  Sue me.  This works.
90  */
91 static problem_t user_problem_functions[MAX_USER_PROBLEMS];
92 static int user_problems = 0;
93 
94 /*
95  *  Print out a usage string.
96  */
usage(void)97 static void usage(void)
98 {
99   fprintf(stderr, "Usage: pscan [-vw] [-p problem_file] <files ...>\n");
100   fprintf(stderr, "Attempts to discover a number of common security abuses in C source files.\n\n");
101   fprintf(stderr, "  -v       Verbose mode.  Can be use multiple times for more output.\n");
102   fprintf(stderr, "  -w       Show warnings when a variable is used as the format argument.\n");
103   fprintf(stderr, "  -p file  Read additional problem <function,offset> definitions from <file>.\n");
104   exit(1);
105 }
106 
107 /*
108  *  Read a problem file.  This consists of a function name,
109  *  and an offset of the format string.
110  */
read_problem_file(const char * file)111 void read_problem_file(const char *file)
112 {
113   FILE *fp;
114   char buffer[1024];
115   char name[1024];
116   int num, offset;
117   int line;
118   char *p;
119 
120   /*
121    *  Can we open the file that they gave us?
122    */
123   fp = fopen(file, "r");
124   if (!fp) {
125     fprintf(stderr, "pscan: Error opening %s: %s\n",
126 	    file, strerror(errno));
127     exit(1);
128   }
129 
130   /*
131    *  Loop over the input file
132    */
133   line = 0;
134   while (fgets(buffer, sizeof(buffer), fp)) {
135     line++;
136 
137     /*
138      *  Sanity check the input buffer.
139      */
140     if (strchr(buffer, '\n') == NULL) {
141       fprintf(stderr, "pscan: %s:%d: Input line too long\n", file, line);
142       exit(1);
143     }
144 
145     /*
146      *  Ignore leading whitespace.
147      */
148     for (p = buffer; (*p == ' ') || (*p == '\t'); p++)
149       /* nothing */;
150 
151     /*
152      *  Skip blank lines and comments
153      */
154     if ((*p == '\n') || (*p == '#')) {
155       continue;
156     }
157 
158     /*
159      *  Check for stupid buffer over-flows.
160      */
161     if (user_problems >= MAX_USER_PROBLEMS) {
162       fprintf(stderr, "pscan: Too many user-defined problem functions.\n");
163       exit(1);
164     }
165 
166     /*
167      *  Scan for the filename & line.
168      */
169     num = sscanf(buffer, "%s%d", name, &offset);
170     if (num != 2) {
171       fprintf(stderr, "pscan: %s:%d: Expected 2 parameters, got %d\n",
172 	      file, line, num);
173       exit(1);
174     }
175 
176     /*
177      *  Copy the function/offset into our data structure.
178      */
179     user_problem_functions[user_problems].function = strdup(name);
180     user_problem_functions[user_problems].fmt_arg = offset;
181     user_problems++;
182   }
183 
184   fclose(fp);
185 }
186 
187 /*
188  *  main, where everything happens.
189  */
main(int argc,char ** argv)190 int main(int argc,char **argv)
191 {
192   int i;
193   int argval;
194 
195   /*
196    *  Get command-line options.
197    */
198   while ((argval = getopt(argc, argv, "hp:vw")) != EOF) {
199     switch (argval) {
200 
201     default:
202     case 'h':
203       usage();
204       break;
205 
206     case 'v':
207       verbose++;
208       break;
209 
210     case 'w':
211       warnings = TRUE;
212       break;
213 
214     case 'p':
215       read_problem_file(optarg);
216       break;
217     }
218   }
219 
220   /*
221    *  Sanity check arguments, to be sure there's at least one file we
222    *  can open.
223    */
224   if (optind == argc) {
225     usage();
226   }
227 
228   /*
229    *  Loop over the input files, scanning them for problems.
230    */
231   for (i = optind; i < argc; i++) {
232 
233     /*
234      *  Initialize the stack, throwing away anything from the last file.
235      */
236     stack_index = 0;
237     state = &fsm_stack[0];
238     filename = argv[i];
239 
240     /*
241      *  Initialize the current state.
242      */
243     state->problem = NULL;
244     state->line = 0;
245     state->args = -1;
246     state->constant_string = -1;
247     state->last_token = NOT_PROBLEMATIC;
248     state->braces = 0;
249 
250     /* Open the source file for reading */
251     if ((yyin = fopen(filename, "r")) == NULL) {
252       fprintf(stderr, "pscan: Error opening %s: %s.\n", filename,
253 	      strerror(errno));
254       printf("%s\n", strerror(errno));
255       exit(1);
256     }
257     yyout = NULL;
258 
259     /*
260      *  Initialize our variables.
261      */
262     yylineno = 1;
263 
264     if (verbose) {
265       printf("Scanning %s ...\n", filename);
266     }
267 
268     /*
269      *  Let the lexer parse the whole file.
270      */
271     while (yylex())
272       ;
273 
274     /* close the input file */
275     fclose(yyin);
276   }
277 
278   /*
279    *  And finally, print out a summary of the total problems.
280    */
281   if (total_errors != 0) {
282     if (verbose) {
283       if (warnings) {
284 	printf("Warnings: %d\n", total_warnings);
285       }
286       printf("Total problems identified: %d\n", total_errors);
287 
288     }
289     exit(1);
290   }
291 
292   exit(0);
293 }
294 
295 /*
296  *  Check the number of arguments to the function, and was the LAST
297  *  argument a constant string?
298   */
check_function(parser_state_t * state)299 void check_function(parser_state_t *state)
300 {
301   assert(state != NULL);
302 
303   /*
304    *  This was a reference to a function WITHOUT an opening brace,
305    *  so it's not a function call.  Ignore it.
306    */
307   if (state->args < 0) {
308     return;
309   }
310 
311   if (verbose == 0) {
312     /*
313      *  The problem function has the SAME number of arguments as the
314      *  placement of the format argument.  i.e. The LAST argument of the
315      *  function is the format string.
316      *
317      *  If the last argument of the function is a constant string, then
318      *  there can't be any security problems, so don't complain.
319      *
320      *  Otherwise, print out a complaint noting the source file,
321      *  line number, and function name.
322      */
323     if ((state->problem->fmt_arg == state->args) &&
324 	(state->constant_string != state->problem->fmt_arg)) {
325       printf("%s:%d SECURITY: %s call should have \"%%s\" as argument %d\n",
326 	     filename,
327 	     state->line,
328 	     state->problem->function,
329 	     state->problem->fmt_arg);
330       total_errors++;
331 
332     } else if (warnings &&
333 	       (state->constant_string != state->problem->fmt_arg)) {
334       printf("%s:%d Warning: %s uses non-constant string for format argument %d.\n",
335 	     filename,
336 	     state->line,
337 	     state->problem->function,
338 	     state->problem->fmt_arg);
339       total_warnings++;
340     }
341 
342   } else {
343     /*
344      *  verbose = 1, print out more stuff.
345      */
346     printf("%s:%d FUNC %s ", filename, state->line,
347 	   state->problem->function);
348     if (state->problem->fmt_arg == state->args) {
349       printf("Last argument is ");
350       if (state->constant_string) {
351 	printf("constant string: OK\n");
352       } else {
353 	printf("variable or reference: BAD\n");
354 	total_errors++;
355       }
356     } else {
357       printf("format string with %d parameters: OK\n",
358 	     state->args - state->problem->fmt_arg);
359     }
360   }
361 }
362 
363 /*
364  *  Scan the current token to see if it's on the list of problem
365  *  functions that we care about.
366  */
setup_checks(const char * name,parser_state_t * state)367 parser_state_t *setup_checks(const char *name, parser_state_t *state)
368 {
369   problem_t *problem;
370   int i;
371 
372   /*
373    *  If there are user-defined problems, use them FIRST.
374    *
375    *  This allows us to handle name conflicts with internally defined
376    *  problem functions
377    */
378   if (user_problems != 0) {
379     /*
380      *  Loop over the list of problem functions, seeing if we have a match.
381      */
382     for (i = 0; i < user_problems; i++) {
383       problem = &user_problem_functions[i];
384 
385       /*
386        *  We have a match!  Set up the current stack, and return.
387        */
388       if (strcmp(problem->function, name) == 0) {
389 
390 	/*
391 	 *  Ignore user-defined problems with -1 as a format parameter.
392 	 */
393 	if (problem->fmt_arg == -1) {
394 	  return state;
395 	}
396 
397 	if (state->last_token == PROBLEMATIC) {
398 	  state = push_stack(state);
399 	}
400 
401 	state->problem = problem;
402 	state->line = yylineno;
403 	state->braces = 0;
404 	state->args = -1;
405 	state->constant_string = -1;
406 	state->last_token = PROBLEMATIC;
407 	return state;
408       }
409     } /* end of loop over user defined problems */
410   } /* end of any user defined problems */
411 
412   /*
413    *  Loop over the list of problem functions, seeing if we have a match.
414    */
415   for (problem = &problem_functions[0]; problem->function != NULL; problem++) {
416 
417     /*
418      *  We have a match!  Set up the current stack, and return.
419      */
420     if (strcmp(problem->function, name) == 0) {
421       if (state->last_token == PROBLEMATIC) {
422 	state = push_stack(state);
423       }
424 
425       state->problem = problem;
426       state->line = yylineno;
427       state->braces = 0;
428       state->args = -1;
429       state->constant_string = -1;
430       state->last_token = PROBLEMATIC;
431       return state;
432     }
433   }
434 
435   return state;
436 }
437 
438 /*
439  *  Pop an entry off of the stack, and return it to the caller.
440  */
pop_stack(void)441 parser_state_t *pop_stack(void)
442 {
443   assert(stack_index >= 0);
444 
445   /*
446    *  This works around stupid state thingies.
447    */
448   if (stack_index == 0) {
449     return &fsm_stack[stack_index];
450   }
451 
452   stack_index--;
453   return &fsm_stack[stack_index];
454 }
455 
456 /*
457  *  Push an entry onto the stack, and return a new entry to use.
458  */
push_stack(parser_state_t * state)459 parser_state_t *push_stack(parser_state_t *state)
460 {
461   assert(state == &fsm_stack[stack_index]);
462   assert(stack_index < FSM_MAX_STACK);
463 
464   stack_index++;
465   return &fsm_stack[stack_index];
466 }
467