1# 2# Copyright OpenEmbedded Contributors 3# 4# SPDX-License-Identifier: MIT 5# 6 7import json 8import os 9from oeqa.selftest.case import OESelftestTestCase 10from oeqa.utils.commands import bitbake, get_bb_vars 11 12class CVECheck(OESelftestTestCase): 13 14 def test_version_compare(self): 15 from oe.cve_check import Version 16 17 result = Version("100") > Version("99") 18 self.assertTrue( result, msg="Failed to compare version '100' > '99'") 19 result = Version("2.3.1") > Version("2.2.3") 20 self.assertTrue( result, msg="Failed to compare version '2.3.1' > '2.2.3'") 21 result = Version("2021-01-21") > Version("2020-12-25") 22 self.assertTrue( result, msg="Failed to compare version '2021-01-21' > '2020-12-25'") 23 result = Version("1.2-20200910") < Version("1.2-20200920") 24 self.assertTrue( result, msg="Failed to compare version '1.2-20200910' < '1.2-20200920'") 25 26 result = Version("1.0") >= Version("1.0beta") 27 self.assertTrue( result, msg="Failed to compare version '1.0' >= '1.0beta'") 28 result = Version("1.0-rc2") > Version("1.0-rc1") 29 self.assertTrue( result, msg="Failed to compare version '1.0-rc2' > '1.0-rc1'") 30 result = Version("1.0.alpha1") < Version("1.0") 31 self.assertTrue( result, msg="Failed to compare version '1.0.alpha1' < '1.0'") 32 result = Version("1.0_dev") <= Version("1.0") 33 self.assertTrue( result, msg="Failed to compare version '1.0_dev' <= '1.0'") 34 35 # ignore "p1" and "p2", so these should be equal 36 result = Version("1.0p2") == Version("1.0p1") 37 self.assertTrue( result ,msg="Failed to compare version '1.0p2' to '1.0p1'") 38 # ignore the "b" and "r" 39 result = Version("1.0b") == Version("1.0r") 40 self.assertTrue( result ,msg="Failed to compare version '1.0b' to '1.0r'") 41 42 # consider the trailing alphabet as patched level when comparing 43 result = Version("1.0b","alphabetical") < Version("1.0r","alphabetical") 44 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' < '1.0r'") 45 result = Version("1.0b","alphabetical") > Version("1.0","alphabetical") 46 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0b' > '1.0'") 47 48 # consider the trailing "p" and "patch" as patched released when comparing 49 result = Version("1.0","patch") < Version("1.0p1","patch") 50 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0' < '1.0p1'") 51 result = Version("1.0p2","patch") > Version("1.0p1","patch") 52 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0p2' > '1.0p1'") 53 result = Version("1.0_patch2","patch") < Version("1.0_patch3","patch") 54 self.assertTrue( result ,msg="Failed to compare version with suffix '1.0_patch2' < '1.0_patch3'") 55 56 57 def test_convert_cve_version(self): 58 from oe.cve_check import convert_cve_version 59 60 # Default format 61 self.assertEqual(convert_cve_version("8.3"), "8.3") 62 self.assertEqual(convert_cve_version(""), "") 63 64 # OpenSSL format version 65 self.assertEqual(convert_cve_version("1.1.1t"), "1.1.1t") 66 67 # OpenSSH format 68 self.assertEqual(convert_cve_version("8.3_p1"), "8.3p1") 69 self.assertEqual(convert_cve_version("8.3_p22"), "8.3p22") 70 71 # Linux kernel format 72 self.assertEqual(convert_cve_version("6.2_rc8"), "6.2-rc8") 73 self.assertEqual(convert_cve_version("6.2_rc31"), "6.2-rc31") 74 75 76 def test_recipe_report_json(self): 77 config = """ 78INHERIT += "cve-check" 79CVE_CHECK_FORMAT_JSON = "1" 80""" 81 self.write_config(config) 82 83 vars = get_bb_vars(["CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 84 summary_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 85 recipe_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], "m4-native_cve.json") 86 87 try: 88 os.remove(summary_json) 89 os.remove(recipe_json) 90 except FileNotFoundError: 91 pass 92 93 bitbake("m4-native -c cve_check") 94 95 def check_m4_json(filename): 96 with open(filename) as f: 97 report = json.load(f) 98 self.assertEqual(report["version"], "1") 99 self.assertEqual(len(report["package"]), 1) 100 package = report["package"][0] 101 self.assertEqual(package["name"], "m4-native") 102 found_cves = { issue["id"]: issue["status"] for issue in package["issue"]} 103 self.assertIn("CVE-2008-1687", found_cves) 104 self.assertEqual(found_cves["CVE-2008-1687"], "Patched") 105 106 self.assertExists(summary_json) 107 check_m4_json(summary_json) 108 self.assertExists(recipe_json) 109 check_m4_json(recipe_json) 110 111 112 def test_image_json(self): 113 config = """ 114INHERIT += "cve-check" 115CVE_CHECK_FORMAT_JSON = "1" 116""" 117 self.write_config(config) 118 119 vars = get_bb_vars(["CVE_CHECK_DIR", "CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 120 report_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 121 print(report_json) 122 try: 123 os.remove(report_json) 124 except FileNotFoundError: 125 pass 126 127 bitbake("core-image-minimal-initramfs") 128 self.assertExists(report_json) 129 130 # Check that the summary report lists at least one package 131 with open(report_json) as f: 132 report = json.load(f) 133 self.assertEqual(report["version"], "1") 134 self.assertGreater(len(report["package"]), 1) 135 136 # Check that a random recipe wrote a recipe report to deploy/cve/ 137 recipename = report["package"][0]["name"] 138 recipe_report = os.path.join(vars["CVE_CHECK_DIR"], recipename + "_cve.json") 139 self.assertExists(recipe_report) 140 with open(recipe_report) as f: 141 report = json.load(f) 142 self.assertEqual(report["version"], "1") 143 self.assertEqual(len(report["package"]), 1) 144 self.assertEqual(report["package"][0]["name"], recipename) 145 146 147 def test_recipe_report_json_unpatched(self): 148 config = """ 149INHERIT += "cve-check" 150CVE_CHECK_FORMAT_JSON = "1" 151CVE_CHECK_REPORT_PATCHED = "0" 152""" 153 self.write_config(config) 154 155 vars = get_bb_vars(["CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 156 summary_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 157 recipe_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], "m4-native_cve.json") 158 159 try: 160 os.remove(summary_json) 161 os.remove(recipe_json) 162 except FileNotFoundError: 163 pass 164 165 bitbake("m4-native -c cve_check") 166 167 def check_m4_json(filename): 168 with open(filename) as f: 169 report = json.load(f) 170 self.assertEqual(report["version"], "1") 171 self.assertEqual(len(report["package"]), 1) 172 package = report["package"][0] 173 self.assertEqual(package["name"], "m4-native") 174 #m4 had only Patched CVEs, so the issues array will be empty 175 self.assertEqual(package["issue"], []) 176 177 self.assertExists(summary_json) 178 check_m4_json(summary_json) 179 self.assertExists(recipe_json) 180 check_m4_json(recipe_json) 181 182 183 def test_recipe_report_json_ignored(self): 184 config = """ 185INHERIT += "cve-check" 186CVE_CHECK_FORMAT_JSON = "1" 187CVE_CHECK_REPORT_PATCHED = "1" 188""" 189 self.write_config(config) 190 191 vars = get_bb_vars(["CVE_CHECK_SUMMARY_DIR", "CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 192 summary_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], vars["CVE_CHECK_SUMMARY_FILE_NAME_JSON"]) 193 recipe_json = os.path.join(vars["CVE_CHECK_SUMMARY_DIR"], "logrotate_cve.json") 194 195 try: 196 os.remove(summary_json) 197 os.remove(recipe_json) 198 except FileNotFoundError: 199 pass 200 201 bitbake("logrotate -c cve_check") 202 203 def check_m4_json(filename): 204 with open(filename) as f: 205 report = json.load(f) 206 self.assertEqual(report["version"], "1") 207 self.assertEqual(len(report["package"]), 1) 208 package = report["package"][0] 209 self.assertEqual(package["name"], "logrotate") 210 found_cves = {} 211 for issue in package["issue"]: 212 found_cves[issue["id"]] = { 213 "status" : issue["status"], 214 "detail" : issue["detail"] if "detail" in issue else "", 215 "description" : issue["description"] if "description" in issue else "" 216 } 217 # m4 CVE should not be in logrotate 218 self.assertNotIn("CVE-2008-1687", found_cves) 219 # logrotate has both Patched and Ignored CVEs 220 self.assertIn("CVE-2011-1098", found_cves) 221 self.assertEqual(found_cves["CVE-2011-1098"]["status"], "Patched") 222 self.assertEqual(len(found_cves["CVE-2011-1098"]["detail"]), 0) 223 self.assertEqual(len(found_cves["CVE-2011-1098"]["description"]), 0) 224 detail = "not-applicable-platform" 225 description = "CVE is debian, gentoo or SUSE specific on the way logrotate was installed/used" 226 self.assertIn("CVE-2011-1548", found_cves) 227 self.assertEqual(found_cves["CVE-2011-1548"]["status"], "Ignored") 228 self.assertEqual(found_cves["CVE-2011-1548"]["detail"], detail) 229 self.assertEqual(found_cves["CVE-2011-1548"]["description"], description) 230 self.assertIn("CVE-2011-1549", found_cves) 231 self.assertEqual(found_cves["CVE-2011-1549"]["status"], "Ignored") 232 self.assertEqual(found_cves["CVE-2011-1549"]["detail"], detail) 233 self.assertEqual(found_cves["CVE-2011-1549"]["description"], description) 234 self.assertIn("CVE-2011-1550", found_cves) 235 self.assertEqual(found_cves["CVE-2011-1550"]["status"], "Ignored") 236 self.assertEqual(found_cves["CVE-2011-1550"]["detail"], detail) 237 self.assertEqual(found_cves["CVE-2011-1550"]["description"], description) 238 239 self.assertExists(summary_json) 240 check_m4_json(summary_json) 241 self.assertExists(recipe_json) 242 check_m4_json(recipe_json) 243