Move sariflogger.py into flawfinder.py

Flawfinder has a project-specific rule to put all code in one file.
That can be a pain for development, but the rule makes *deploying*
flawfinder really easy in some settings. Worse comes to worse, just
copy the file somewhere and you can run it!

Signed-off-by: David A. Wheeler <dwheeler@dwheeler.com>
This commit is contained in:
David A. Wheeler 2021-05-30 14:39:44 -04:00
parent f9819b48a5
commit fd50391439
2 changed files with 169 additions and 166 deletions

View File

@ -53,8 +53,7 @@ import operator # To support filename expansion on Windows
import time
import csv # To support generating CSV format
import hashlib
# import formatter
from sariflogger import SarifLogger
import json
version = "2.0.15"
@ -127,6 +126,173 @@ def print_warning(message):
sys.stderr.write("\n")
sys.stderr.flush()
def to_json(o):
return json.dumps(o, default=lambda o: o.__dict__, sort_keys=False, indent=2)
# The following implements the SarifLogger.
# We intentionally merge all of flawfinder's functionality into 1 file
# so it's trivial to copy & use elsewhere.
class SarifLogger(object):
_hitlist = None
TOOL_NAME = "Flawfinder"
TOOL_URL = "https://dwheeler.com/flawfinder/"
TOOL_VERSION = "2.0.15"
URI_BASE_ID = "SRCROOT"
SARIF_SCHEMA = "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json"
SARIF_SCHEMA_VERSION = "2.1.0"
CWE_TAXONOMY_NAME = "CWE"
CWE_TAXONOMY_URI = "https://raw.githubusercontent.com/sarif-standard/taxonomies/main/CWE_v4.4.sarif"
CWE_TAXONOMY_GUID = "FFC64C90-42B6-44CE-8BEB-F6B7DAE649E5"
def __init__ (self, hits):
self._hitlist = hits
def output_sarif(self):
tool = {
"driver": {
"name": self.TOOL_NAME,
"version": self.TOOL_VERSION,
"informationUri": self.TOOL_URL,
"rules": self._extract_rules(self._hitlist),
"supportedTaxonomies": [{
"name": self.CWE_TAXONOMY_NAME,
"guid": self.CWE_TAXONOMY_GUID,
}],
}
}
runs = [{
"tool": tool,
"columnKind": "utf16CodeUnits",
"results": self._extract_results(self._hitlist),
"externalPropertyFileReferences": {
"taxonomies": [{
"location": {
"uri": self.CWE_TAXONOMY_URI,
},
"guid": self.CWE_TAXONOMY_GUID,
}],
},
}]
report = {
"$schema": self.SARIF_SCHEMA,
"version": self.SARIF_SCHEMA_VERSION,
"runs": runs,
}
jsonstr = to_json(report)
return jsonstr
def _extract_rules(self, hitlist):
rules = {}
for hit in hitlist:
if not hit.ruleid in rules:
rules[hit.ruleid] = self._to_sarif_rule(hit)
return list(rules.values())
def _extract_results(self, hitlist):
results = []
for hit in hitlist:
results.append(self._to_sarif_result(hit))
return results
def _to_sarif_rule(self, hit):
return {
"id": hit.ruleid,
"name": "{0}/{1}".format(hit.category, hit.name),
"shortDescription": {
"text": self._append_period(hit.warning),
},
"defaultConfiguration": {
"level": self._to_sarif_level(hit.defaultlevel),
},
"helpUri": hit.helpuri(),
"relationships": self._extract_relationships(hit.cwes()),
}
def _to_sarif_result(self, hit):
return {
"ruleId": hit.ruleid,
"level": self._to_sarif_level(hit.level),
"message": {
"text": self._append_period("{0}/{1}:{2}".format(hit.category, hit.name, hit.warning)),
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": self._to_uri_path(hit.filename),
"uriBaseId": self.URI_BASE_ID,
},
"region": {
"startLine": hit.line,
"startColumn": hit.column,
"endColumn": len(hit.context_text) + 1,
"snippet": {
"text": hit.context_text,
}
}
}
}],
"fingerprints": {
"contextHash/v1": hit.fingerprint()
},
"rank": self._to_sarif_rank(hit.level),
}
def _extract_relationships(self, cwestring):
# example cwe string "CWE-119!/ CWE-120", "CWE-829, CWE-20"
relationships = []
for cwe in re.split(',|/',cwestring):
cwestr = cwe.strip()
if cwestr:
relationship = {
"target": {
"id": int(cwestr.replace("CWE-", "").replace("!", "")),
"toolComponent": {
"name": self.CWE_TAXONOMY_NAME,
"guid": self.CWE_TAXONOMY_GUID,
},
},
"kinds": [
"relevant" if cwestr[-1] != '!' else "incomparable"
],
}
relationships.append(relationship)
return relationships
@staticmethod
def _to_sarif_level(level):
# level 4 & 5
if level >= 4:
return "error"
# level 3
if level == 3:
return "warning"
# level 0 1 2
return "note"
@staticmethod
def _to_sarif_rank(level):
#SARIF rank FF Level SARIF level Default Viewer Action
#0.0 0 note Does not display by default
#0.2 1 note Does not display by default
#0.4 2 note Does not display by default
#0.6 3 warning Displays by default, does not break build / other processes
#0.8 4 error Displays by default, breaks build/ other processes
#1.0 5 error Displays by default, breaks build/ other processes
return level * 0.2
@staticmethod
def _to_uri_path(path):
return path.replace("\\", "/")
@staticmethod
def _append_period(text):
return text if text[-1] == '.' else text + "."
# The following code accepts unified diff format from both subversion (svn)
# and GNU diff, which aren't well-documented. It gets filenames from
@ -2018,6 +2184,7 @@ flawfinder [--help | -h] [--version] [--listrules]
Display as HTML output.
--immediate | -i
Immediately display hits (don't just wait until the end).
--sarif Generate output in SARIF format.
--singleline | -S
Single-line output.
--omittime Omit time to run.

View File

@ -1,164 +0,0 @@
import json
import re
def to_json(o):
return json.dumps(o, default=lambda o: o.__dict__, sort_keys=False, indent=2)
class SarifLogger(object):
_hitlist = None
TOOL_NAME = "Flawfinder"
TOOL_URL = "https://dwheeler.com/flawfinder/"
TOOL_VERSION = "2.0.15"
URI_BASE_ID = "SRCROOT"
SARIF_SCHEMA = "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json"
SARIF_SCHEMA_VERSION = "2.1.0"
CWE_TAXONOMY_NAME = "CWE"
CWE_TAXONOMY_URI = "https://raw.githubusercontent.com/sarif-standard/taxonomies/main/CWE_v4.4.sarif"
CWE_TAXONOMY_GUID = "FFC64C90-42B6-44CE-8BEB-F6B7DAE649E5"
def __init__ (self, hits):
self._hitlist = hits
def output_sarif(self):
tool = {
"driver": {
"name": self.TOOL_NAME,
"version": self.TOOL_VERSION,
"informationUri": self.TOOL_URL,
"rules": self._extract_rules(self._hitlist),
"supportedTaxonomies": [{
"name": self.CWE_TAXONOMY_NAME,
"guid": self.CWE_TAXONOMY_GUID,
}],
}
}
runs = [{
"tool": tool,
"columnKind": "utf16CodeUnits",
"results": self._extract_results(self._hitlist),
"externalPropertyFileReferences": {
"taxonomies": [{
"location": {
"uri": self.CWE_TAXONOMY_URI,
},
"guid": self.CWE_TAXONOMY_GUID,
}],
},
}]
report = {
"$schema": self.SARIF_SCHEMA,
"version": self.SARIF_SCHEMA_VERSION,
"runs": runs,
}
jsonstr = to_json(report)
return jsonstr
def _extract_rules(self, hitlist):
rules = {}
for hit in hitlist:
if not hit.ruleid in rules:
rules[hit.ruleid] = self._to_sarif_rule(hit)
return list(rules.values())
def _extract_results(self, hitlist):
results = []
for hit in hitlist:
results.append(self._to_sarif_result(hit))
return results
def _to_sarif_rule(self, hit):
return {
"id": hit.ruleid,
"name": "{0}/{1}".format(hit.category, hit.name),
"shortDescription": {
"text": self._append_period(hit.warning),
},
"defaultConfiguration": {
"level": self._to_sarif_level(hit.defaultlevel),
},
"helpUri": hit.helpuri(),
"relationships": self._extract_relationships(hit.cwes()),
}
def _to_sarif_result(self, hit):
return {
"ruleId": hit.ruleid,
"level": self._to_sarif_level(hit.level),
"message": {
"text": self._append_period("{0}/{1}:{2}".format(hit.category, hit.name, hit.warning)),
},
"locations": [{
"physicalLocation": {
"artifactLocation": {
"uri": self._to_uri_path(hit.filename),
"uriBaseId": self.URI_BASE_ID,
},
"region": {
"startLine": hit.line,
"startColumn": hit.column,
"endColumn": len(hit.context_text) + 1,
"snippet": {
"text": hit.context_text,
}
}
}
}],
"fingerprints": {
"contextHash/v1": hit.fingerprint()
},
"rank": self._to_sarif_rank(hit.level),
}
def _extract_relationships(self, cwestring):
# example cwe string "CWE-119!/ CWE-120", "CWE-829, CWE-20"
relationships = []
for cwe in re.split(',|/',cwestring):
cwestr = cwe.strip()
if cwestr:
relationship = {
"target": {
"id": int(cwestr.replace("CWE-", "").replace("!", "")),
"toolComponent": {
"name": self.CWE_TAXONOMY_NAME,
"guid": self.CWE_TAXONOMY_GUID,
},
},
"kinds": [
"relevant" if cwestr[-1] != '!' else "incomparable"
],
}
relationships.append(relationship)
return relationships
@staticmethod
def _to_sarif_level(level):
# level 4 & 5
if level >= 4:
return "error"
# level 3
if level == 3:
return "warning"
# level 0 1 2
return "note"
@staticmethod
def _to_sarif_rank(level):
#SARIF rank FF Level SARIF level Default Viewer Action
#0.0 0 note Does not display by default
#0.2 1 note Does not display by default
#0.4 2 note Does not display by default
#0.6 3 warning Displays by default, does not break build / other processes
#0.8 4 error Displays by default, breaks build/ other processes
#1.0 5 error Displays by default, breaks build/ other processes
return level * 0.2
@staticmethod
def _to_uri_path(path):
return path.replace("\\", "/")
@staticmethod
def _append_period(text):
return text if text[-1] == '.' else text + "."