| | @@ -39,10 +39,15 @@ |
| 39 | 39 | #include <stdlib.h> |
| 40 | 40 | #include <ctype.h> |
| 41 | 41 | #include <string.h> |
| 42 | 42 | #include <assert.h> |
| 43 | 43 | |
| 44 | +/* |
| 45 | +** Debugging switch |
| 46 | +*/ |
| 47 | +static int eVerbose = 0; |
| 48 | + |
| 44 | 49 | /* |
| 45 | 50 | ** Malloc, aborting if it fails. |
| 46 | 51 | */ |
| 47 | 52 | void *safe_malloc(int nByte){ |
| 48 | 53 | void *x = malloc(nByte); |
| | @@ -198,10 +203,32 @@ |
| 198 | 203 | */ |
| 199 | 204 | static const char *skip_space(const char *z){ |
| 200 | 205 | while( isspace(z[0]) ){ z++; } |
| 201 | 206 | return z; |
| 202 | 207 | } |
| 208 | + |
| 209 | +/* |
| 210 | +** Remove excess whitespace and nested "()" from string z. |
| 211 | +*/ |
| 212 | +static char *simplify_expr(char *z){ |
| 213 | + int n = (int)strlen(z); |
| 214 | + while( n>0 ){ |
| 215 | + if( isspace(z[0]) ){ |
| 216 | + z++; |
| 217 | + n--; |
| 218 | + continue; |
| 219 | + } |
| 220 | + if( z[0]=='(' && z[n-1]==')' ){ |
| 221 | + z++; |
| 222 | + n -= 2; |
| 223 | + continue; |
| 224 | + } |
| 225 | + break; |
| 226 | + } |
| 227 | + z[n] = 0; |
| 228 | + return z; |
| 229 | +} |
| 203 | 230 | |
| 204 | 231 | /* |
| 205 | 232 | ** Return true if the input is a string literal. |
| 206 | 233 | */ |
| 207 | 234 | static int is_string_lit(const char *z){ |
| | @@ -268,11 +295,11 @@ |
| 268 | 295 | |
| 269 | 296 | /* |
| 270 | 297 | ** Return true if the input is an argument that is safe to use with %s |
| 271 | 298 | ** while building an SQL statement. |
| 272 | 299 | */ |
| 273 | | -static int is_s_safe(const char *z){ |
| 300 | +static int is_sql_safe(const char *z){ |
| 274 | 301 | int len, eType; |
| 275 | 302 | int i; |
| 276 | 303 | |
| 277 | 304 | /* A string literal is safe for use with %s */ |
| 278 | 305 | if( is_string_lit(z) ) return 1; |
| | @@ -297,15 +324,29 @@ |
| 297 | 324 | ** let it through */ |
| 298 | 325 | if( strstr(z, "/*safe-for-%s*/")!=0 ) return 1; |
| 299 | 326 | |
| 300 | 327 | return 0; |
| 301 | 328 | } |
| 329 | + |
| 330 | +/* |
| 331 | +** Return true if the input is an argument that is never safe for use |
| 332 | +** with %s. |
| 333 | +*/ |
| 334 | +static int never_safe(const char *z){ |
| 335 | + if( strstr(z,"/*safe-for-%s*/")!=0 ) return 0; |
| 336 | + if( z[0]=='P' ) return 1; /* CGI macros like P() and PD() */ |
| 337 | + if( strncmp(z,"cgi_param",9)==0 ) return 1; |
| 338 | + return 0; |
| 339 | +} |
| 302 | 340 | |
| 303 | 341 | /* |
| 304 | 342 | ** Processing flags |
| 305 | 343 | */ |
| 306 | | -#define FMT_NO_S 0x00001 /* Do not allow %s substitutions */ |
| 344 | +#define FMT_SQL 0x00001 /* Generates SQL text */ |
| 345 | +#define FMT_HTML 0x00002 /* Generates HTML text */ |
| 346 | +#define FMT_URL 0x00004 /* Generates URLs */ |
| 347 | +#define FMT_SAFE 0x00008 /* Always safe for %s */ |
| 307 | 348 | |
| 308 | 349 | /* |
| 309 | 350 | ** A list of internal Fossil interfaces that take a printf-style format |
| 310 | 351 | ** string. |
| 311 | 352 | */ |
| | @@ -313,55 +354,55 @@ |
| 313 | 354 | const char *zFName; /* Name of the function */ |
| 314 | 355 | int iFmtArg; /* Index of format argument. Leftmost is 1. */ |
| 315 | 356 | unsigned fmtFlags; /* Processing flags */ |
| 316 | 357 | } aFmtFunc[] = { |
| 317 | 358 | { "admin_log", 1, 0 }, |
| 318 | | - { "blob_append_sql", 2, FMT_NO_S }, |
| 359 | + { "blob_append_sql", 2, FMT_SQL }, |
| 319 | 360 | { "blob_appendf", 2, 0 }, |
| 320 | | - { "cgi_debug", 1, 0 }, |
| 321 | | - { "cgi_panic", 1, 0 }, |
| 322 | | - { "cgi_printf", 1, 0 }, |
| 323 | | - { "cgi_redirectf", 1, 0 }, |
| 324 | | - { "chref", 2, 0 }, |
| 325 | | - { "db_blob", 2, FMT_NO_S }, |
| 326 | | - { "db_debug", 1, FMT_NO_S }, |
| 327 | | - { "db_double", 2, FMT_NO_S }, |
| 361 | + { "cgi_debug", 1, FMT_SAFE }, |
| 362 | + { "cgi_panic", 1, FMT_SAFE }, |
| 363 | + { "cgi_printf", 1, FMT_HTML }, |
| 364 | + { "cgi_redirectf", 1, FMT_URL }, |
| 365 | + { "chref", 2, FMT_URL }, |
| 366 | + { "db_blob", 2, FMT_SQL }, |
| 367 | + { "db_debug", 1, FMT_SQL }, |
| 368 | + { "db_double", 2, FMT_SQL }, |
| 328 | 369 | { "db_err", 1, 0 }, |
| 329 | | - { "db_exists", 1, FMT_NO_S }, |
| 370 | + { "db_exists", 1, FMT_SQL }, |
| 330 | 371 | { "db_get_mprintf", 2, 0 }, |
| 331 | | - { "db_int", 2, FMT_NO_S }, |
| 332 | | - { "db_int64", 2, FMT_NO_S }, |
| 333 | | - { "db_multi_exec", 1, FMT_NO_S }, |
| 334 | | - { "db_optional_sql", 2, FMT_NO_S }, |
| 335 | | - { "db_prepare", 2, FMT_NO_S }, |
| 336 | | - { "db_prepare_ignore_error", 2, FMT_NO_S }, |
| 372 | + { "db_int", 2, FMT_SQL }, |
| 373 | + { "db_int64", 2, FMT_SQL }, |
| 374 | + { "db_multi_exec", 1, FMT_SQL }, |
| 375 | + { "db_optional_sql", 2, FMT_SQL }, |
| 376 | + { "db_prepare", 2, FMT_SQL }, |
| 377 | + { "db_prepare_ignore_error", 2, FMT_SQL }, |
| 337 | 378 | { "db_set_mprintf", 3, 0 }, |
| 338 | | - { "db_static_prepare", 2, FMT_NO_S }, |
| 339 | | - { "db_text", 2, FMT_NO_S }, |
| 379 | + { "db_static_prepare", 2, FMT_SQL }, |
| 380 | + { "db_text", 2, FMT_SQL }, |
| 340 | 381 | { "db_unset_mprintf", 2, 0 }, |
| 341 | | - { "form_begin", 2, 0 }, |
| 342 | | - { "fossil_error", 2, 0 }, |
| 343 | | - { "fossil_errorlog", 1, 0 }, |
| 344 | | - { "fossil_fatal", 1, 0 }, |
| 345 | | - { "fossil_fatal_recursive", 1, 0 }, |
| 346 | | - { "fossil_panic", 1, 0 }, |
| 347 | | - { "fossil_print", 1, 0 }, |
| 348 | | - { "fossil_trace", 1, 0 }, |
| 349 | | - { "fossil_warning", 1, 0 }, |
| 350 | | - { "href", 1, 0 }, |
| 382 | + { "form_begin", 2, FMT_URL }, |
| 383 | + { "fossil_error", 2, FMT_SAFE }, |
| 384 | + { "fossil_errorlog", 1, FMT_SAFE }, |
| 385 | + { "fossil_fatal", 1, FMT_SAFE }, |
| 386 | + { "fossil_fatal_recursive", 1, FMT_SAFE }, |
| 387 | + { "fossil_panic", 1, FMT_SAFE }, |
| 388 | + { "fossil_print", 1, FMT_SAFE }, |
| 389 | + { "fossil_trace", 1, FMT_SAFE }, |
| 390 | + { "fossil_warning", 1, FMT_SAFE }, |
| 391 | + { "href", 1, FMT_URL }, |
| 351 | 392 | { "json_new_string_f", 1, 0 }, |
| 352 | 393 | { "json_set_err", 2, 0 }, |
| 353 | 394 | { "json_warn", 2, 0 }, |
| 354 | 395 | { "mprintf", 1, 0 }, |
| 355 | 396 | { "socket_set_errmsg", 1, 0 }, |
| 356 | 397 | { "ssl_set_errmsg", 1, 0 }, |
| 357 | | - { "style_header", 1, 0 }, |
| 358 | | - { "style_set_current_page", 1, 0 }, |
| 359 | | - { "style_submenu_element", 2, 0 }, |
| 360 | | - { "style_submenu_sql", 3, 0 }, |
| 361 | | - { "webpage_error", 1, 0 }, |
| 362 | | - { "xhref", 2, 0 }, |
| 398 | + { "style_header", 1, FMT_HTML }, |
| 399 | + { "style_set_current_page", 1, FMT_URL }, |
| 400 | + { "style_submenu_element", 2, FMT_URL }, |
| 401 | + { "style_submenu_sql", 3, FMT_SQL }, |
| 402 | + { "webpage_error", 1, FMT_SAFE }, |
| 403 | + { "xhref", 2, FMT_URL }, |
| 363 | 404 | }; |
| 364 | 405 | |
| 365 | 406 | /* |
| 366 | 407 | ** Determine if the indentifier zIdent of length nIndent is a Fossil |
| 367 | 408 | ** internal interface that uses a printf-style argument. Return zero if not. |
| | @@ -464,16 +505,17 @@ |
| 464 | 505 | zCopy[len] = 0; |
| 465 | 506 | azArg = 0; |
| 466 | 507 | nArg = 0; |
| 467 | 508 | z = zCopy; |
| 468 | 509 | while( z[0] ){ |
| 510 | + char cEnd; |
| 469 | 511 | len = distance_to(z, ','); |
| 470 | | - azArg = safe_realloc((char*)azArg, (sizeof(azArg[0])+1)*(nArg+1)); |
| 471 | | - azArg[nArg++] = skip_space(z); |
| 472 | | - if( z[len]==0 ) break; |
| 512 | + cEnd = z[len]; |
| 473 | 513 | z[len] = 0; |
| 474 | | - for(i=len-1; i>0 && isspace(z[i]); i--){ z[i] = 0; } |
| 514 | + azArg = safe_realloc((char*)azArg, (sizeof(azArg[0])+1)*(nArg+1)); |
| 515 | + azArg[nArg++] = simplify_expr(z); |
| 516 | + if( cEnd==0 ) break; |
| 475 | 517 | z += len + 1; |
| 476 | 518 | } |
| 477 | 519 | acType = (char*)&azArg[nArg]; |
| 478 | 520 | if( fmtArg>nArg ){ |
| 479 | 521 | printf("%s:%d: too few arguments to %.*s()\n", |
| | @@ -492,28 +534,37 @@ |
| 492 | 534 | printf("%s:%d: too %s arguments to %.*s() " |
| 493 | 535 | "- got %d and expected %d\n", |
| 494 | 536 | zFilename, lnFCall, (nArg<fmtArg+k ? "few" : "many"), |
| 495 | 537 | szFName, zFCall, nArg, fmtArg+k); |
| 496 | 538 | nErr++; |
| 497 | | - }else if( fmtFlags & FMT_NO_S ){ |
| 539 | + }else if( (fmtFlags & FMT_SAFE)==0 ){ |
| 498 | 540 | for(i=0; i<nArg && i<k; i++){ |
| 499 | | - if( (acType[i]=='s' || acType[i]=='z' || acType[i]=='b') |
| 500 | | - && !is_s_safe(azArg[fmtArg+i]) |
| 501 | | - ){ |
| 502 | | - printf("%s:%d: Argument %d to %.*s() not safe for SQL\n", |
| 503 | | - zFilename, lnFCall, i+fmtArg, szFName, zFCall); |
| 504 | | - nErr++; |
| 541 | + if( (acType[i]=='s' || acType[i]=='z' || acType[i]=='b') ){ |
| 542 | + const char *zExpr = azArg[fmtArg+i]; |
| 543 | + if( never_safe(zExpr) ){ |
| 544 | + printf("%s:%d: Argument %d to %.*s() is not safe for" |
| 545 | + " a query parameter\n", |
| 546 | + zFilename, lnFCall, i+fmtArg, szFName, zFCall); |
| 547 | + nErr++; |
| 548 | + |
| 549 | + }else if( (fmtFlags & FMT_SQL)!=0 && !is_sql_safe(zExpr) ){ |
| 550 | + printf("%s:%d: Argument %d to %.*s() not safe for SQL\n", |
| 551 | + zFilename, lnFCall, i+fmtArg, szFName, zFCall); |
| 552 | + nErr++; |
| 553 | + } |
| 505 | 554 | } |
| 506 | 555 | } |
| 507 | 556 | } |
| 508 | 557 | } |
| 509 | 558 | if( nErr ){ |
| 510 | 559 | for(i=0; i<nArg; i++){ |
| 511 | 560 | printf(" arg[%d]: %s\n", i, azArg[i]); |
| 512 | 561 | } |
| 562 | + }else if( eVerbose>1 ){ |
| 563 | + printf("%s:%d: %.*s() ok for %d arguments\n", |
| 564 | + zFilename, lnFCall, szFName, zFCall, nArg); |
| 513 | 565 | } |
| 514 | | - |
| 515 | 566 | free((char*)azArg); |
| 516 | 567 | free(zCopy); |
| 517 | 568 | return nErr; |
| 518 | 569 | } |
| 519 | 570 | |
| | @@ -563,16 +614,24 @@ |
| 563 | 614 | } |
| 564 | 615 | |
| 565 | 616 | /* |
| 566 | 617 | ** Check for format-string design rule violations on all files listed |
| 567 | 618 | ** on the command-line. |
| 619 | +** |
| 620 | +** The eVerbose global variable is incremented with each "-v" argument. |
| 568 | 621 | */ |
| 569 | 622 | int main(int argc, char **argv){ |
| 570 | 623 | int i; |
| 571 | 624 | int nErr = 0; |
| 572 | 625 | for(i=1; i<argc; i++){ |
| 573 | | - char *zFile = read_file(argv[i]); |
| 626 | + char *zFile; |
| 627 | + if( strcmp(argv[i],"-v")==0 ){ |
| 628 | + eVerbose++; |
| 629 | + continue; |
| 630 | + } |
| 631 | + if( eVerbose>0 ) printf("Processing %s...\n", argv[i]); |
| 632 | + zFile = read_file(argv[i]); |
| 574 | 633 | nErr += scan_file(argv[i], zFile); |
| 575 | 634 | free(zFile); |
| 576 | 635 | } |
| 577 | 636 | return nErr; |
| 578 | 637 | } |
| 579 | 638 | |