Add "--error-level" option for continuous integration systems
Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com>
This commit is contained in:
parent
ba8e4bf6b6
commit
d7ce082024
60
flawfinder
60
flawfinder
|
@ -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
|
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 hit.level >= error_level:
|
||||||
|
error_level_exceeded = True
|
||||||
if output_format:
|
if output_format:
|
||||||
print("</ul>")
|
print("</ul>")
|
||||||
|
if diffhitlist_filename:
|
||||||
diff_file.close()
|
diff_file.close()
|
||||||
else:
|
|
||||||
if output_format:
|
|
||||||
print("<ul>")
|
|
||||||
for hit in 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>")
|
|
||||||
# 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")
|
||||||
|
|
13
flawfinder.1
13
flawfinder.1
|
@ -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"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue