-
Notifications
You must be signed in to change notification settings - Fork 594
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
False Positives Associated with Chainguard Images #1179
Comments
Nice. I was just bitten by the CVEs affecting the C++ version of protobuf when I'm using the Go package. Arguably, it shouldn't even be included on those CVEs in Github because it's a completely different code base... if it were amended to remove the false Go listing, would that also clear up grype? |
@alecxvs, sorry if you got bitten while using Chainguard Images. We definitely work to keep false positive scanner findings down on Chainguard Images but don't get all of them all of the time. This blog post mentions how we try to keep Chainguard Images (which play nice with But you're definitely in technical territory best covered by @wagoodman and @luhring here! |
Actually I was not using a chainguard image, I was consuming the New Relic infrastructure agent which has protobuf as a dependency. Anything using protobuf in Golang seems to be affected by this, then. But this issue seems to concisely cover the what and why regardless of which image is used. |
Hey @jspeed-meyers ! A lot has changed in grype over the last year since this issue was opened, so I wanted to circle back and see how relevant these findings still are. An initial check that all of the findings you reported have been addressed ! 🎉 🌮 compare scriptNote: you need to install grype and the legacy version of grype for this to function:
As well as the spreadsheet in CSV form as import csv
import subprocess
import os
import re
import json
def parse_csv(file_path):
with open(file_path, newline='') as csvfile:
reader = csv.DictReader(csvfile)
return list(reader)
def run_syft(image, digest, output_dir):
clean_image = re.sub('[^a-zA-Z0-9]', '_', image)
sbom_file = os.path.join(output_dir, f"{clean_image}_{digest}.json")
if not os.path.exists(sbom_file):
command = f"syft {image}@{digest} -o json={sbom_file}"
print("running command:", command)
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("Error:", result.stderr)
exit(1)
return sbom_file
def run_grype(binary_path, output_dir, sbom_file=None, image=None, digest=None):
if image and not digest or digest and not image:
raise ValueError("Both image and digest should be provided")
if sbom_file:
command = f"{binary_path} {sbom_file} -o json"
else:
command = f"{binary_path} {image}@{digest} -o json"
clean_image = re.sub('[^a-zA-Z0-9]', '_', image)
output_file = os.path.join(output_dir, f"{clean_image}_{digest}_{os.path.basename(binary_path)}.json")
if os.path.exists(output_file):
with open(output_file, 'r') as file:
output = file.read()
else:
print("running command:", command)
result = subprocess.run(command, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print("Error:", result.stderr)
exit(1)
output = result.stdout
with open(output_file, 'w') as file:
file.write(output)
return output_file
def check_cve_and_package(output_file, package_name, cve):
with open(output_file, 'r') as file:
grype_output = json.load(file)
cve_present = False
for match in grype_output.get("matches", []):
vuln = match.get("vulnerability", {})
artifact = match.get("artifact", {})
if artifact.get("name").lower() == package_name:
if vuln.get("id").lower() == cve:
cve_present = True
return cve_present
def check_package_in_syft(sbom_file, package_name):
with open(sbom_file, 'r') as file:
syft_output = json.load(file)
for artifact in syft_output.get("artifacts", []):
if artifact.get("name").lower() == package_name:
return True
return False
def main():
csv_data = parse_csv('data.csv')
old_binary_path = './bin/grype'
new_binary_path = 'grype'
output_sbom_dir = 'output/sbom'
output_old_dir = 'output/old'
output_new_dir = 'output/new'
print("old grype version:\n" + subprocess.run(f"{old_binary_path} version", shell=True, capture_output=True, text=True).stdout)
print("new grype version:\n" + subprocess.run(f"{new_binary_path} version", shell=True, capture_output=True, text=True).stdout)
os.makedirs(output_sbom_dir, exist_ok=True)
os.makedirs(output_old_dir, exist_ok=True)
os.makedirs(output_new_dir, exist_ok=True)
results = []
fixed_count = 0
reproduced_count = 0
total_count = len(csv_data)
for row in csv_data:
image = row['image'].strip()
digest = row['digest'].strip()
package_name = row['package_name'].strip().lower()
cve = row['CVE'].strip().lower()
sbom_file = run_syft(image, digest, output_sbom_dir)
bin_grype_output = run_grype(old_binary_path, output_old_dir, image=image, digest=digest) # run against image
grype_output = run_grype(new_binary_path, output_new_dir, sbom_file=sbom_file, image=image, digest=digest) # run against sbom
package_present_in_syft = check_package_in_syft(sbom_file, package_name)
cve_present_bin = check_cve_and_package(bin_grype_output, package_name, cve)
cve_present_grype = check_cve_and_package(grype_output, package_name, cve)
if cve_present_bin:
reproduced_count += 1
fixed = not cve_present_grype
if fixed:
fixed_count += 1
results.append({
'image': image,
'digest': digest,
'package_name': package_name,
'cve': cve,
'package_present': 'yes' if package_present_in_syft else 'no',
'reproduced': 'yes' if cve_present_bin else 'no',
'fixed?': 'yes' if fixed else 'no'
})
markdown = "| Image | Package Name | CVE | Package Present | Reproduced | Fixed? |\n"
markdown += "|-------|--------------|-----|-----------------|------------|--------|\n"
for result in results:
markdown += f"| {result['image']} | {result['package_name']} | {result['cve']} | {result['package_present']} | {result['reproduced']} | {result['fixed?']} |\n"
print(markdown)
print("Reproduced :", reproduced_count)
print("Fixed :", fixed_count)
print("Total :", total_count)
if __name__ == "__main__":
main() script output
Thank you @jspeed-meyers for the work on getting this data together + the detailed blog post! Since there are no more FPs from this data set, I'll close this issue for now, but shout out if I'm missing anything here. |
@wagoodman --- OMG. This is my favorite GH issue ever now. Great work to the |
What happened:
This is a list of false positives associated with using
grype
to scan Chainguard Images. The table below provides detailed information on each false positive, including the scanner version, database build date, and image. I have shortened the image digests to make the table readable for a GitHub issue. More context on the activities that led to this reporting of false positives can be found in this blog post. A public Google Sheets with the data below (and the full digests) can be found here. I should note that this data was collected largely in January 2023.What you expected to happen:
I expected these false positives not to occur.
How to reproduce it:
Environment:
grype version
: See table above.cat /etc/os-release
or similar): Apple M1 Pro, Ventura 13.2.1cc @wagoodman @luhring
The text was updated successfully, but these errors were encountered: