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