This patch adds a harness for invoking flawfinder: https://www.dwheeler.com/flawfinder/ returning the results in JSON format.
It runs "flawfinder", then uses firehose.parsers.flawfinder.parse_file to parse the stdout, turning it into firehose JSON. checkers/ChangeLog: * flawfinder.py: New file. --- checkers/flawfinder.py | 124 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100755 checkers/flawfinder.py diff --git a/checkers/flawfinder.py b/checkers/flawfinder.py new file mode 100755 index 0000000..475a513 --- /dev/null +++ b/checkers/flawfinder.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python +# Copyright 2012, 2013, 2015, 2017 David Malcolm <dmalc...@redhat.com> +# Copyright 2012, 2013, 2015, 2017 Red Hat, Inc. +# +# This is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see +# <http://www.gnu.org/licenses/>. + +import sys +import tempfile + +from gccinvocation import GccInvocation + +from checker import Checker, CheckerTests, make_file, make_stats, \ + tool_main + +from firehose.model import Failure, Issue +from firehose.parsers.flawfinder import parse_file + +class InvokeFlawfinder(Checker): + """ + Checker subclass that invokes "flawfinder" + """ + name = 'flawfinder' + + def raw_invoke(self, gccinv, sourcefile): + args = ['flawfinder', sourcefile] # FIXME + return self._run_subprocess(sourcefile, args) + + def handle_output(self, result): + if result.returncode: + analysis = self._make_failed_analysis(result.sourcefile, result.timer, + msgtext='Bad exit code running %s' % self.name, + failureid='bad-exit-code') + self.set_custom_fields(result, analysis) + return analysis + + if 0: + print('result.err: %r' % result.err) + print('result.out: %r' % result.out) + + # (there doesn't seem to be a way to have flawfinder directly + # save its output to a given location) + + with tempfile.NamedTemporaryFile() as outfile: + outfile.write(result.out) + outfile.flush() + + with open(outfile.name) as infile: + # Parse stderr into firehose XML format and save: + analysis = parse_file(infile) + analysis.metadata.file_ = make_file(result.sourcefile) + analysis.metadata.stats = make_stats(result.timer) + self.set_custom_fields(result, analysis) + + return analysis + + def set_custom_fields(self, result, analysis): + analysis.set_custom_field('flawfinder-invocation', + ' '.join(result.argv)) + result.set_custom_fields(analysis) + +class FlawfinderTests(CheckerTests): + def make_tool(self): + return self.make_tool_from_class(InvokeFlawfinder) + + def verify_basic_metadata(self, analysis, sourcefile): + # Verify basic metadata: + self.assert_metadata(analysis, 'flawfinder', sourcefile) + self.assert_has_custom_field(analysis, 'flawfinder-invocation') + self.assert_has_custom_field(analysis, 'stdout') + self.assert_has_custom_field(analysis, 'stderr') + + def test_file_not_found(self): + analysis = self.invoke('does-not-exist.c') + #print(analysis) + self.assertEqual(len(analysis.results), 0) + + def test_timeout(self): + sourcefile = 'test-sources/harmless.c' + tool = self.make_tool() + tool.timeout = 0 + gccinv = GccInvocation(['gcc', sourcefile]) + analysis = tool.checked_invoke(gccinv, sourcefile) + self.assert_metadata(analysis, 'flawfinder', sourcefile) + self.assertEqual(len(analysis.results), 1) + r0 = analysis.results[0] + self.assertIsInstance(r0, Failure) + self.assertEqual(r0.failureid, 'timeout') + self.assert_has_custom_field(analysis, 'timeout') + self.assert_has_custom_field(analysis, 'command-line') + + def test_harmless_file(self): + analysis = self.invoke('test-sources/harmless.c') + self.assertEqual(len(analysis.results), 0) + + def test_use_of_random(self): + analysis = self.invoke('test-sources/cpychecker-demo.c') + self.assertEqual(len(analysis.results), 1) + r0 = analysis.results[0] + self.assertIsInstance(r0, Issue) + self.assertEqual(r0.testid, 'random') + self.assertEqual(r0.location.file.givenpath, + 'test-sources/cpychecker-demo.c') + self.assertEqual(r0.location.point.line, 97) + self.assertEqual(r0.message.text, + "This function is not sufficiently random for" + " security-related functions such as key and nonce" + " creation. use a more secure technique for" + " acquiring random values.") + self.assertEqual(r0.severity, '3') + +if __name__ == '__main__': + sys.exit(tool_main(sys.argv, InvokeFlawfinder)) -- 1.8.5.3