Add "--error-level" option for continuous integration systems

Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com>
This commit is contained in:
David A. Wheeler 2018-01-27 18:11:52 -05:00
parent ba8e4bf6b6
commit d7ce082024
2 changed files with 54 additions and 31 deletions

View File

@ -80,7 +80,7 @@ list_rules = 0 # If true, list the rules (helpful for debugging)
patch_file = "" # File containing (unified) diff output. patch_file = "" # File containing (unified) diff output.
loadhitlist = None loadhitlist = None
savehitlist = None savehitlist = None
diffhitlist = None diffhitlist_filename = None
quiet = 0 quiet = 0
showheading = 1 # --dataonly turns this off showheading = 1 # --dataonly turns this off
output_format = 0 # 0 = normal, 1 = html. output_format = 0 # 0 = normal, 1 = html.
@ -91,6 +91,10 @@ omit_time = 0 # 1 = omit time-to-run (needed for testing)
required_regex = None # If non-None, regex that must be met to report required_regex = None # If non-None, regex that must be met to report
required_regex_compiled = None required_regex_compiled = None
ERROR_ON_DISABLED_VALUE = 999
error_level = ERROR_ON_DISABLED_VALUE # Level where we're return error code
error_level_exceeded = False
displayed_header = 0 # Have we displayed the header yet? displayed_header = 0 # Have we displayed the header yet?
num_ignored_hits = 0 # Number of ignored hits (used if never_ignore==0) num_ignored_hits = 0 # Number of ignored hits (used if never_ignore==0)
@ -242,7 +246,7 @@ def load_patch_info(input_patch_file):
hPatch = open(input_patch_file, 'r') hPatch = open(input_patch_file, 'r')
except BaseException: except BaseException:
print("Error: failed to open", h(input_patch_file)) print("Error: failed to open", h(input_patch_file))
sys.exit(1) sys.exit(10)
patched_filename = "" # Name of new file patched by current hunk. patched_filename = "" # Name of new file patched by current hunk.
@ -256,7 +260,7 @@ def load_patch_info(input_patch_file):
fn_get_filename = gnu_diff_get_filename fn_get_filename = gnu_diff_get_filename
else: else:
print("Error: Unrecognized patch format") print("Error: Unrecognized patch format")
sys.exit(1) sys.exit(11)
while True: # Loop-and-half construct. Read a line, end loop when no more while True: # Loop-and-half construct. Read a line, end loop when no more
@ -268,7 +272,7 @@ def load_patch_info(input_patch_file):
if patched_filename in patch: if patched_filename in patch:
error("filename occurs more than once in the patch: %s" % error("filename occurs more than once in the patch: %s" %
patched_filename) patched_filename)
sys.exit(1) sys.exit(12)
else: else:
patch[patched_filename] = {} patch[patched_filename] = {}
else: else:
@ -279,7 +283,7 @@ def load_patch_info(input_patch_file):
"wrong type of patch file : " + "wrong type of patch file : " +
"we have a line number without having seen a filename" "we have a line number without having seen a filename"
) )
sys.exit(1) sys.exit(13)
initial_number = hunk_match.group('linenumber') initial_number = hunk_match.group('linenumber')
line_counter = 0 line_counter = 0
else: else:
@ -1462,7 +1466,7 @@ def process_c_file(f, patch_infos):
my_input = open(f, "r") my_input = open(f, "r")
except BaseException: except BaseException:
print("Error: failed to open", h(f)) print("Error: failed to open", h(f))
sys.exit(1) sys.exit(14)
# Read ENTIRE file into memory. Use readlines() to convert \n if necessary. # Read ENTIRE file into memory. Use readlines() to convert \n if necessary.
# This turns out to be very fast in Python, even on large files, and it # This turns out to be very fast in Python, even on large files, and it
@ -1611,7 +1615,7 @@ def expand_ruleset(ruleset):
if newrule in ruleset: if newrule in ruleset:
print("Error: Rule %s, when expanded, overlaps %s" % ( print("Error: Rule %s, when expanded, overlaps %s" % (
rule, newrule)) rule, newrule))
sys.exit(1) sys.exit(15)
ruleset[newrule] = ruleset[rule] ruleset[newrule] = ruleset[rule]
del ruleset[rule] del ruleset[rule]
# To print out the set of keys in the expanded ruleset, run: # To print out the set of keys in the expanded ruleset, run:
@ -1857,6 +1861,14 @@ flawfinder [--help | -h] [--version] [--listrules]
--quiet | -Q --quiet | -Q
Don't display status information (i.e., which files are being Don't display status information (i.e., which files are being
examined) while the analysis is going on. examined) while the analysis is going on.
--error-level=LEVEL
Return a nonzero (false) error code if there is at least one
hit of LEVEL or higher. If a diffhitlist is provided,
hits noted in it are ignored.
This option can be useful within a continuous integration script,
especially if you mark known-okay lines as "flawfinder: ignore".
Usually you want level to be fairly high, such as 4 or 5.
By default, flawfinder returns 0 (true) on a successful run.
Hitlist Management: Hitlist Management:
--savehitlist=F --savehitlist=F
@ -1876,10 +1888,11 @@ def process_options():
global show_context, show_inputs, allowlink, skipdotdir, omit_time global show_context, show_inputs, allowlink, skipdotdir, omit_time
global output_format, minimum_level, show_immediately, single_line global output_format, minimum_level, show_immediately, single_line
global csv_output, csv_writer global csv_output, csv_writer
global error_level
global required_regex, required_regex_compiled global required_regex, required_regex_compiled
global falsepositive global falsepositive
global show_columns, never_ignore, quiet, showheading, list_rules global show_columns, never_ignore, quiet, showheading, list_rules
global loadhitlist, savehitlist, diffhitlist global loadhitlist, savehitlist, diffhitlist_filename
global patch_file global patch_file
try: try:
# Note - as a side-effect, this sets sys.argv[]. # Note - as a side-effect, this sets sys.argv[].
@ -1888,6 +1901,7 @@ def process_options():
"falsepositive", "falsepositives", "columns", "listrules", "falsepositive", "falsepositives", "columns", "listrules",
"omittime", "allowlink", "patch=", "followdotdir", "neverignore", "omittime", "allowlink", "patch=", "followdotdir", "neverignore",
"regex=", "quiet", "dataonly", "html", "singleline", "csv", "regex=", "quiet", "dataonly", "html", "singleline", "csv",
"error-level=",
"loadhitlist=", "savehitlist=", "diffhitlist=", "version", "help" "loadhitlist=", "savehitlist=", "diffhitlist=", "version", "help"
]) ])
for (opt, value) in optlist: for (opt, value) in optlist:
@ -1926,6 +1940,8 @@ def process_options():
quiet = 1 quiet = 1
showheading = 0 showheading = 0
csv_writer = csv.writer(sys.stdout) csv_writer = csv.writer(sys.stdout)
elif opt == "--error-level":
error_level = int(value)
elif opt == "--immediate" or opt == "-i": elif opt == "--immediate" or opt == "-i":
show_immediately = 1 show_immediately = 1
elif opt == "-n" or opt == "--neverignore": elif opt == "-n" or opt == "--neverignore":
@ -1954,7 +1970,7 @@ def process_options():
if showheading: if showheading:
print("Saving hitlist to", value) print("Saving hitlist to", value)
elif opt == "--diffhitlist": elif opt == "--diffhitlist":
diffhitlist = value diffhitlist_filename = value
display_header() display_header()
if showheading: if showheading:
print("Showing hits not in", value) print("Showing hits not in", value)
@ -1983,7 +1999,7 @@ def process_options():
except getopt.error as text: except getopt.error as text:
print("*** getopt error:", text) print("*** getopt error:", text)
usage() usage()
sys.exit(1) sys.exit(16)
def process_files(): def process_files():
@ -2010,6 +2026,7 @@ def hitlist_sort_key(hit):
def show_final_results(): def show_final_results():
global hitlist global hitlist
global error_level_exceeded
count = 0 count = 0
count_per_level = {} count_per_level = {}
count_per_level_and_up = {} count_per_level_and_up = {}
@ -2032,30 +2049,23 @@ def show_final_results():
# <ul> so that the format differentiates each entry. # <ul> so that the format differentiates each entry.
# I'm not using <ol>, because its numbers might be confused with # I'm not using <ol>, because its numbers might be confused with
# the risk levels or line numbers. # the risk levels or line numbers.
if diffhitlist: if diffhitlist_filename:
diff_file = open(diffhitlist) diff_file = open(diffhitlist_filename)
diff_hitlist = pickle.load(diff_file) diff_hitlist = pickle.load(diff_file)
if output_format: if output_format:
print("<ul>") print("<ul>")
for hit in hitlist: for hit in hitlist:
if hit not in diff_hitlist: if not diffhitlist_filename or not hit in diff_hitlist:
count_per_level[hit.level] = count_per_level[hit.level] + 1
if hit.level >= minimum_level:
hit.show()
count = count + 1
if output_format:
print("</ul>")
diff_file.close()
else:
if output_format:
print("<ul>")
for hit in hitlist:
count_per_level[hit.level] = count_per_level[hit.level] + 1 count_per_level[hit.level] = count_per_level[hit.level] + 1
if hit.level >= minimum_level: if hit.level >= minimum_level:
hit.show() hit.show()
count = count + 1 count = count + 1
if output_format: if hit.level >= error_level:
print("</ul>") error_level_exceeded = True
if output_format:
print("</ul>")
if diffhitlist_filename:
diff_file.close()
# Done with list, show the post-hitlist summary. # Done with list, show the post-hitlist summary.
if showheading: if showheading:
if output_format: if output_format:
@ -2171,10 +2181,10 @@ def flawfind():
if process_files(): if process_files():
show_final_results() show_final_results()
save_if_desired() save_if_desired()
return 1 if error_level_exceeded else 0
if __name__ == '__main__': if __name__ == '__main__':
try: try:
flawfind() sys.exit(flawfind())
except KeyboardInterrupt: except KeyboardInterrupt:
print("*** Flawfinder interrupted") print("*** Flawfinder interrupted")

View File

@ -55,6 +55,7 @@ flawfinder \- lexically find potential security flaws ("hits") in source code
.RB [ \-\-singleline | \-S ] .RB [ \-\-singleline | \-S ]
.RB [ \-\-omittime ] .RB [ \-\-omittime ]
.RB [ \-\-quiet | \-Q ] .RB [ \-\-quiet | \-Q ]
.RB [ \-\-error-level=\fRLEVEL\fR ]
.br .br
.\" Managing hit list. .\" Managing hit list.
[\fB\-\-loadhitlist=\fR\fIF\fR] [\fB\-\-loadhitlist=\fR\fIF\fR]
@ -219,6 +220,9 @@ The summary ends with important reminders:
Not every hit is necessarily a security vulnerability, and Not every hit is necessarily a security vulnerability, and
there may be other security vulnerabilities not reported by the tool. there may be other security vulnerabilities not reported by the tool.
.PP .PP
Flawfinder can easily integrate into a continuous integration system.
You might want to check out the \-\-error\-level option to help do that.
.PP
Flawfinder is released under the GNU GPL license version 2 or later (GPLv2+). Flawfinder is released under the GNU GPL license version 2 or later (GPLv2+).
.PP .PP
Flawfinder works similarly to another program, ITS4, which is not Flawfinder works similarly to another program, ITS4, which is not
@ -550,6 +554,15 @@ the output doesn't vary depending on how long the analysis takes.
Don't display status information (i.e., which files are being examined) Don't display status information (i.e., which files are being examined)
while the analysis is going on. while the analysis is going on.
.TP
.BI "\-\-error-level=LEVEL"
Return a nonzero (false) error code if there is at least one
hit of LEVEL or higher. If a diffhitlist is provided,
hits noted in it are ignored.
This option can be useful within a continuous integration script,
especially if you mark known-okay lines as "flawfinder: ignore".
Usually you want level to be fairly high, such as 4 or 5.
By default, flawfinder returns 0 (true) on a successful run.
.SS "Hitlist Management" .SS "Hitlist Management"