From d3dff22a2979cccbf6a1041fbb544080312ffedf Mon Sep 17 00:00:00 2001 From: Sergey Popovich Date: Wed, 20 Jan 2016 09:07:33 +0000 Subject: [PATCH 01/25] checksec: Add explicit runtime dependency on binutils Since checksec.sh functionality relies on readelf(1) utility, which is part of binutils package, we should add runtime dependencies on binutils explicitly. Signed-off-by: Sergey Popovich --- recipes-devtools/checksec/checksec_1.5.bb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes-devtools/checksec/checksec_1.5.bb b/recipes-devtools/checksec/checksec_1.5.bb index 4ebb825..2fe2aba 100644 --- a/recipes-devtools/checksec/checksec_1.5.bb +++ b/recipes-devtools/checksec/checksec_1.5.bb @@ -15,6 +15,6 @@ do_install() { install -m 0755 ${WORKDIR}/checksec.sh ${D}${bindir} } -RDEPENDS_${PN} = "bash" +RDEPENDS_${PN} = "bash binutils" BBCLASSEXTEND = "native" From b180439e78ef5a639b03a9af670661a1308b13d1 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Mon, 25 Jan 2016 16:00:20 +0200 Subject: [PATCH 02/25] Making junit xml output work for every plugin --- lib/isafw/isaplugins/ISA_cfa_plugin.py | 13 +++----- lib/isafw/isaplugins/ISA_cve_plugin.py | 41 +++++++++++++++++++------- lib/isafw/isaplugins/ISA_fsa_plugin.py | 13 +++----- lib/isafw/isaplugins/ISA_kca_plugin.py | 21 +++++-------- lib/isafw/isaplugins/ISA_la_plugin.py | 29 ++++++++++++++++-- 5 files changed, 74 insertions(+), 43 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index 0e58c65..0b91354 100644 --- a/lib/isafw/isaplugins/ISA_cfa_plugin.py +++ b/lib/isafw/isaplugins/ISA_cfa_plugin.py @@ -115,29 +115,24 @@ def write_report_xml(self, ISA_filesystem): root = etree.Element('testsuite', name='CFA_Plugin', tests='4') tcase1 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_RELO') if self.no_relo: - failrs1 = etree.SubElement(tcase1, 'failure', msg='Non-compliant files found', type='violation') for item in self.no_relo: item = item.replace(ISA_filesystem.path_to_fs, "") - etree.SubElement(failrs1, 'value').text = item + failrs1 = etree.SubElement(tcase1, 'failure', message=item, type='violation') tcase2 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_canary') if self.no_canary: - failrs2 = etree.SubElement(tcase2, 'failure', msg='Non-compliant files found', type='violation') for item in self.no_canary: item = item.replace(ISA_filesystem.path_to_fs, "") - etree.SubElement(failrs2, 'value').text = item + failrs2 = etree.SubElement(tcase2, 'failure', message=item, type='violation') tcase3 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_PIE') if self.no_pie: - failrs3 = etree.SubElement(tcase3, 'failure', msg='Non-compliant files found', type='violation') for item in self.no_pie: item = item.replace(ISA_filesystem.path_to_fs, "") - etree.SubElement(failrs3, 'value').text = item + failrs3 = etree.SubElement(tcase3, 'failure', message=item, type='violation') tcase4 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_NX') if self.no_nx: - failrs4 = etree.SubElement(tcase4, 'failure', msg='Non-compliant files found', type='violation') for item in self.no_nx: item = item.replace(ISA_filesystem.path_to_fs, "") - etree.SubElement(failrs4, 'value').text = item - print (etree.tostring(root, pretty_print=True)) + failrs4 = etree.SubElement(tcase4, 'failure', message=item, type='violation') tree = etree.ElementTree(root) output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) diff --git a/lib/isafw/isaplugins/ISA_cve_plugin.py b/lib/isafw/isaplugins/ISA_cve_plugin.py index 1691482..3d4e302 100644 --- a/lib/isafw/isaplugins/ISA_cve_plugin.py +++ b/lib/isafw/isaplugins/ISA_cve_plugin.py @@ -78,6 +78,7 @@ def process_package(self, ISA_pkg): else: print("Mandatory arguments such as pkg name, version and list of patches are not provided!") print("Not performing the call.") + self.initialized = False with open(self.logdir + log, 'a') as flog: flog.write("Mandatory arguments such as pkg name, version and list of patches are not provided!\n") flog.write("Not performing the call.\n") @@ -87,18 +88,38 @@ def process_package(self, ISA_pkg): flog.write("Plugin hasn't initialized! Not performing the call.\n") def process_report(self): - print("Creating report in HTML format.") - with open(self.logdir + log, 'a') as flog: - flog.write("Creating report in HTML format.\n") - self.process_report_type("html") + if (self.initialized == True): + print("Creating report in HTML format.") + with open(self.logdir + log, 'a') as flog: + flog.write("Creating report in HTML format.\n") + self.process_report_type("html") - print("Creating report in CSV format.") - with open(self.logdir + log, 'a') as flog: - flog.write("Creating report in CSV format.\n") - self.process_report_type("csv") + print("Creating report in CSV format.") + with open(self.logdir + log, 'a') as flog: + flog.write("Creating report in CSV format.\n") + self.process_report_type("csv") - pkglist_faux = pkglist + "_" + self.timestamp + ".faux" - os.remove(self.reportdir + pkglist_faux) + pkglist_faux = pkglist + "_" + self.timestamp + ".faux" + os.remove(self.reportdir + pkglist_faux) + + print("Creating report in XML format.") + with open(self.logdir + log, 'a') as flog: + flog.write("Creating report in XML format.\n") + self.write_report_xml() + + def write_report_xml(self): + from lxml import etree + root = etree.Element('testsuite', name='CVE_Plugin', tests='1') + tcase1 = etree.SubElement(root, 'testcase', classname='ISA_CVEChecker', name='found_CVEs') + with open(self.reportdir + cve_report + "_" + self.timestamp + ".csv", 'r') as f: + for line in f: + line = line.strip() + line2 = line.split(',', 2) + if line2[2].startswith('CVE'): + failrs1 = etree.SubElement(tcase1, 'failure', message=line, type='violation') + tree = etree.ElementTree(root) + output = self.reportdir + cve_report + "_" + self.timestamp + '.xml' + tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) def process_report_type(self, rtype): # now faux file is ready and we can process it diff --git a/lib/isafw/isaplugins/ISA_fsa_plugin.py b/lib/isafw/isaplugins/ISA_fsa_plugin.py index 40860c9..e67535d 100644 --- a/lib/isafw/isaplugins/ISA_fsa_plugin.py +++ b/lib/isafw/isaplugins/ISA_fsa_plugin.py @@ -110,25 +110,20 @@ def write_problems_report_xml(self, ISA_filesystem): root = etree.Element('testsuite', name = 'FSA_Plugin', tests = '4') tcase1 = etree.SubElement(root, 'testcase', classname = 'ISA_FSChecker', name = 'Files_with_SETUID_bit_set') if self.setuid_files: - failrs1 = etree.SubElement(tcase1, 'failure', msg = 'Non-compliant files found', type = 'violation') for item in self.setuid_files: - etree.SubElement(failrs1, 'value').text = item + failrs1 = etree.SubElement(tcase1, 'failure', message = item, type = 'violation') tcase2 = etree.SubElement(root, 'testacase', classname = 'ISA_FSChecker', name = 'Files_with_SETGID_bit_set') if self.setgid_files: - failrs2 = etree.SubElement(tcase2, 'failure', msg = 'Non-compliant files found', type = 'violation') for item in self.setgid_files: - etree.SubElement(failrs2, 'value').text = item + failrs2 = etree.SubElement(tcase2, 'failure', message = item, type = 'violation') tcase3 = etree.SubElement(root, 'testase', classname = 'ISA_FSChecker', name = 'World-writable_files') if self.ww_files: - failrs3 = etree.SubElement(tcase3, 'failure', msg = 'Non-compliant files found', type = 'violation') for item in self.ww_files: - etree.SubElement(failrs3, 'value').text = item + failrs3 = etree.SubElement(tcase3, 'failure', message = item, type = 'violation') tcase4 = etree.SubElement(root, 'testcase', classname = 'ISA_FSChecker', name = 'World-writable_dirs_with_no_sticky_bit') if self.no_sticky_bit_ww_dirs: - failrs4 = etree.SubElement(tcase4, 'failure', msg = 'Non-compliant directories found', type = 'violation') for item in self.no_sticky_bit_ww_dirs: - etree.SubElement(failrs4, 'value').text = item - print (etree.tostring(root, pretty_print = True)) + failrs4 = etree.SubElement(tcase4, 'failure', message = item, type = 'violation') tree = etree.ElementTree(root) output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' tree.write(output, encoding = 'UTF-8', pretty_print = True, xml_declaration = True) diff --git a/lib/isafw/isaplugins/ISA_kca_plugin.py b/lib/isafw/isaplugins/ISA_kca_plugin.py index 837dfe7..00df7ff 100644 --- a/lib/isafw/isaplugins/ISA_kca_plugin.py +++ b/lib/isafw/isaplugins/ISA_kca_plugin.py @@ -285,7 +285,6 @@ def write_problems_report(self, ISA_kernel): # write_problems_report_xml root = etree.Element('testsuite', name = 'KCA_Plugin', tests='4') tcase1 = etree.SubElement(root, 'testcase', classname ='ISA_KernelChecker', name = 'Hardening_options_that_need_improvement') - failrs1 = etree.SubElement(tcase1, 'failure', msg = 'Non-compliant kernel settings found', type = 'violation') for key in sorted(self.hardening_kco) : if (self.hardening_kco[key] != self.hardening_kco_ref[key]) : valid == False @@ -299,16 +298,14 @@ def write_problems_report(self, ISA_kernel): valid = True break if valid == False: - msg1 = 'current="' + key + ':' + str(self.hardening_kco[key]) + '"' + ' recommended="' + key + ':' + str(self.hardening_kco_ref[key] + '"') - value1 = etree.SubElement(failrs1, 'value').text = msg1 + msg1 = 'current=' + key + ' is ' + str(self.hardening_kco[key]) + ', recommended=' + key + ' is ' + str(self.hardening_kco_ref[key]) + failrs1 = etree.SubElement(tcase1, 'failure', message = msg1, type = 'violation') tcase2 = etree.SubElement(root, 'testcase', classname = 'ISA_KernelChecker', name = 'Key-related_options_that_need_improvement') - failrs2 = etree.SubElement(tcase2, 'failure', msg = 'Non-compliant kernel settings found', type = 'violation') for key in sorted(self.keys_kco): if (self.keys_kco[key] != self.keys_kco_ref[key]) : - msg2 = 'current="' + key + ':' + str(self.keys_kco[key] + '"' + ' recommended="' + key + ':' + str(self.keys_kco_ref[key] + '"')) - value2 = etree.SubElement(failrs2, 'value').text= msg2 + msg2 = 'current=' + key + ' is ' + str(self.keys_kco[key] + ', recommended=' + key + ' is ' + str(self.keys_kco_ref[key])) + failrs2 = etree.SubElement(tcase2, 'failure', message = msg2, type = 'violation') tcase3 = etree.SubElement(root, 'testcase', classname = 'ISA_KernelChecker', name = 'Security_options_that_need_improvement') - failrs3 = etree.SubElement(tcase3, 'failure', msg = 'Non-compliant kernel settings found', type ='violation') for key in sorted(self.security_kco): if (self.security_kco[key] != self.security_kco_ref[key]) : valid = False @@ -328,10 +325,9 @@ def write_problems_report(self, ISA_kernel): (self.security_kco['CONFIG_SECURITY_TOMOYO'] == 'y')): valid = True if valid == False: - msg3 = 'current="' + key + ':' + str(self.security_kco[key]) + '"' + ' recommended="' + key + ':' + str(self.security_kco_ref[key] + '"') - value3 = etree.SubElement(failrs3, 'value').text = msg3 + msg3 = 'current=' + key + ' is ' + str(self.security_kco[key]) + ', recommended=' + key + ' is ' + str(self.security_kco_ref[key]) + failrs3 = etree.SubElement(tcase3, 'failure', message = msg3, type ='violation') tcase4 = etree.SubElement(root, 'testcase', classname = 'ISA_KernelChecker', name = 'Integrity_options_that_need_improvement') - failrs4 = etree.SubElement(tcase4, 'failure', msg = 'Non-compliant kernel settings found', type='violation') for key in sorted(self.integrity_kco): if (self.integrity_kco[key] != self.integrity_kco_ref[key]) : valid = False @@ -343,9 +339,8 @@ def write_problems_report(self, ISA_kernel): (self.integrity_kco['CONFIG_IMA_DEFAULT_HASH_SHA512'] == 'y')): valid = True if valid == False : - msg4 = 'current="' + key + ':' + str(self.integrity_kco[key]) + '"' + ' recommended="' + key + ':' + str(self.integrity_kco_ref[key] + '"') - value4 = etree.SubElement(failrs4, 'value').text = msg4 - print (etree.tostring(root, pretty_print = True)) + msg4 = 'current=' + key + ' is ' + str(self.integrity_kco[key]) + ', recommended=' + key + ' is ' + str(self.integrity_kco_ref[key]) + failrs4 = etree.SubElement(tcase4, 'failure', message = msg4, type='violation') tree = etree.ElementTree(root) output = self.reportdir + problemsreport + ISA_kernel.img_name + "_" + self.timestamp + '.xml' tree.write(output, encoding = 'UTF-8', pretty_print = True, xml_declaration = True) diff --git a/lib/isafw/isaplugins/ISA_la_plugin.py b/lib/isafw/isaplugins/ISA_la_plugin.py index d56acea..4dabca5 100644 --- a/lib/isafw/isaplugins/ISA_la_plugin.py +++ b/lib/isafw/isaplugins/ISA_la_plugin.py @@ -67,6 +67,7 @@ def process_package(self, ISA_pkg): if (not ISA_pkg.path_to_sources): print("No path to sources or source file list is provided!") print("Not able to determine licenses for package: ", ISA_pkg.name) + self.initialized = False with open(self.logdir + log, 'a') as flog: flog.write("No path to sources or source file list is provided!") flog.write("\nNot able to determine licenses for package: " + ISA_pkg.name) @@ -83,6 +84,7 @@ def process_package(self, ISA_pkg): except: print("Error in executing rpm query: ", sys.exc_info()) print("Not able to process package: ", ISA_pkg.name) + self.initialized = False with open(self.logdir + log, 'a') as flog: flog.write("Error in executing rpm query: " + sys.exc_info()) flog.write("\nNot able to process package: " + ISA_pkg.name) @@ -92,12 +94,12 @@ def process_package(self, ISA_pkg): and not self.check_license(l, fapproved_non_osi) and not self.check_exceptions(ISA_pkg.name, l, fexceptions)): # log the package as not following correct license - report = self.reportdir + "/license_report" - with open(report + "_" + self.timestamp, 'a') as freport: + with open(self.reportdir + "/la_problems_report_" + self.timestamp, 'a') as freport: freport.write(ISA_pkg.name + ": " + l + "\n") else: print("Mandatory argument package name is not provided!") print("Not performing the call.") + self.initialized = False with open(self.logdir + log, 'a') as flog: flog.write("Mandatory argument package name is not provided!\n") flog.write("Not performing the call.\n") @@ -106,6 +108,26 @@ def process_package(self, ISA_pkg): with open(self.logdir + log, 'a') as flog: flog.write("Plugin hasn't initialized! Not performing the call.") + def process_report(self): + if (self.initialized == True): + print("Creating report in XML format.") + with open(self.logdir + log, 'a') as flog: + flog.write("Creating report in XML format.\n") + self.write_report_xml() + + def write_report_xml(self): + from lxml import etree + root = etree.Element('testsuite', name='LA_Plugin', tests='1') + tcase1 = etree.SubElement(root, 'testcase', classname='ISA_LAChecker', name='license_violations') + with open(self.reportdir + "/la_problems_report_" + self.timestamp, 'r') as f: + for line in f: + line = line.strip() + failrs1 = etree.SubElement(tcase1, 'failure', message=line, type='violation') + tree = etree.ElementTree(root) + output = self.reportdir + "/la_problems_report_" + self.timestamp + '.xml' + tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) + + def find_files(self, init_path): list_of_files = [] for (dirpath, dirnames, filenames) in os.walk(init_path): @@ -140,5 +162,8 @@ def getPluginName(): def process_package(ISA_pkg): global LicenseChecker return LicenseChecker.process_package(ISA_pkg) +def process_report(): + global LicenseChecker + return LicenseChecker.process_report() #====================================================# From 74bcd9d925140208742bd90a0c183fd86e20f341 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Thu, 28 Jan 2016 13:36:47 +0200 Subject: [PATCH 03/25] Adding checks for executable stack to CFA plugin --- classes/isafw.bbclass | 1 + lib/isafw/isaplugins/ISA_cfa_plugin.py | 62 +++++++++++++++++++++----- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/classes/isafw.bbclass b/classes/isafw.bbclass index bf533d0..8de2573 100644 --- a/classes/isafw.bbclass +++ b/classes/isafw.bbclass @@ -181,6 +181,7 @@ python analyse_image() { } do_rootfs[depends] += "checksec-native:do_populate_sysroot" +do_rootfs[depends] += "prelink-native:do_populate_sysroot" analyse_image[fakeroot] = "1" def isafw_init(isafw, d): diff --git a/lib/isafw/isaplugins/ISA_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index 0b91354..8dc0c96 100644 --- a/lib/isafw/isaplugins/ISA_cfa_plugin.py +++ b/lib/isafw/isaplugins/ISA_cfa_plugin.py @@ -45,6 +45,8 @@ class ISA_CFChecker(): no_canary = [] no_pie = [] no_nx = [] + execstack = [] + execstack_not_defined = [] def __init__(self, ISA_config): self.proxy = ISA_config.proxy @@ -54,16 +56,21 @@ def __init__(self, ISA_config): # check that checksec is installed rc = subprocess.call(["which", "checksec.sh"]) if rc == 0: - self.initialized = True - print("Plugin ISA_CFChecker initialized!") - with open(self.logdir + log, 'w') as flog: - flog.write("\nPlugin ISA_CFChecker initialized!\n") - else: - print("checksec tool is missing!") - print("Please install it from http://www.trapkit.de/tools/checksec.html") - with open(self.logdir + log, 'w') as flog: - flog.write("checksec tool is missing!\n") - flog.write("Please install it from http://www.trapkit.de/tools/checksec.html\n") + # check that execstack is installed + rc = subprocess.call(["which", "execstack"]) + if rc == 0: + self.initialized = True + print("Plugin ISA_CFChecker initialized!") + with open(self.logdir + log, 'w') as flog: + flog.write("\nPlugin ISA_CFChecker initialized!\n") + return + print("checksec or execstack tools are missing!") + print("Please install checksec from http://www.trapkit.de/tools/checksec.html") + print("Please install execstack from prelink package") + with open(self.logdir + log, 'w') as flog: + flog.write("checksec or execstack tools are missing!\n") + flog.write("Please install checksec from http://www.trapkit.de/tools/checksec.html\n") + flog.write("Please install execstack from prelink package\n") def process_filesystem(self, ISA_filesystem): if (self.initialized == True): @@ -110,9 +117,17 @@ def write_report(self, ISA_filesystem): for item in self.no_nx: item = item.replace(ISA_filesystem.path_to_fs, "") fproblems_report.write(item + '\n') + fproblems_report.write("\n\nFiles with executable stack enabled:\n") + for item in self.execstack: + item = item.replace(ISA_filesystem.path_to_fs, "") + fproblems_report.write(item + '\n') + fproblems_report.write("\n\nFiles with no ability to fetch executable stack status:\n") + for item in self.execstack_not_defined: + item = item.replace(ISA_filesystem.path_to_fs, "") + fproblems_report.write(item + '\n') def write_report_xml(self, ISA_filesystem): - root = etree.Element('testsuite', name='CFA_Plugin', tests='4') + root = etree.Element('testsuite', name='CFA_Plugin', tests='6') tcase1 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_RELO') if self.no_relo: for item in self.no_relo: @@ -133,6 +148,16 @@ def write_report_xml(self, ISA_filesystem): for item in self.no_nx: item = item.replace(ISA_filesystem.path_to_fs, "") failrs4 = etree.SubElement(tcase4, 'failure', message=item, type='violation') + tcase5 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_execstack') + if self.execstack: + for item in self.execstack: + item = item.replace(ISA_filesystem.path_to_fs, "") + failrs4 = etree.SubElement(tcase5, 'failure', message=item, type='violation') + tcase6 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_execstack_not_defined') + if self.execstack_not_defined: + for item in self.execstack_not_defined: + item = item.replace(ISA_filesystem.path_to_fs, "") + failrs4 = etree.SubElement(tcase6, 'failure', message=item, type='violation') tree = etree.ElementTree(root) output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) @@ -144,6 +169,19 @@ def find_files(self, init_path): list_of_files.append(str(dirpath+"/"+f)[:]) return list_of_files + def get_execstack(self, file_name): + cmd = ['execstack', '-q', file_name] + try: + result = subprocess.check_output(cmd).decode("utf-8") + except: + return "Not able to fetch execstack status" + else: + if result.startswith("X "): + self.execstack.append(file_name[:]) + if result.startswith("? "): + self.execstack_not_defined.append(file_name[:]) + return result + def get_security_flags(self, file_name): SF = { 'No RELRO' : 0, @@ -228,12 +266,14 @@ def process_files(self, img_name, path_to_fs): sec_field = "File is pdf" else: sec_field = self.get_security_flags(real_file) + execstack = self.get_execstack(real_file) with open(self.reportdir + full_report + img_name + "_" + self.timestamp, 'a') as ffull_report: real_file = real_file.replace(path_to_fs, "") ffull_report.write(real_file + ": ") for s in sec_field: line = ' '.join(str(x) for x in s) ffull_report.write(line + ' ') + ffull_report.write('\nexecstack: ' + execstack +' ') ffull_report.write('\n') else: continue From 40716d0ee9f055ad01ddc58d01c0c22191da67f8 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Fri, 29 Jan 2016 11:14:15 +0200 Subject: [PATCH 04/25] Adding checks for executables that don't drop groups while calling setuid/setgid to CFA plugin --- lib/isafw/isaplugins/ISA_cfa_plugin.py | 56 +++++++++++++++++++------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index 8dc0c96..4cea7b7 100644 --- a/lib/isafw/isaplugins/ISA_cfa_plugin.py +++ b/lib/isafw/isaplugins/ISA_cfa_plugin.py @@ -47,6 +47,7 @@ class ISA_CFChecker(): no_nx = [] execstack = [] execstack_not_defined = [] + nodrop_groups = [] def __init__(self, ISA_config): self.proxy = ISA_config.proxy @@ -59,16 +60,19 @@ def __init__(self, ISA_config): # check that execstack is installed rc = subprocess.call(["which", "execstack"]) if rc == 0: - self.initialized = True - print("Plugin ISA_CFChecker initialized!") - with open(self.logdir + log, 'w') as flog: - flog.write("\nPlugin ISA_CFChecker initialized!\n") - return - print("checksec or execstack tools are missing!") + # check that execstack is installed + rc = subprocess.call(["which", "readelf"]) + if rc == 0: + self.initialized = True + print("Plugin ISA_CFChecker initialized!") + with open(self.logdir + log, 'w') as flog: + flog.write("\nPlugin ISA_CFChecker initialized!\n") + return + print("checksec, execstack or readelf tools are missing!") print("Please install checksec from http://www.trapkit.de/tools/checksec.html") print("Please install execstack from prelink package") with open(self.logdir + log, 'w') as flog: - flog.write("checksec or execstack tools are missing!\n") + flog.write("checksec, execstack or readelf tools are missing!\n") flog.write("Please install checksec from http://www.trapkit.de/tools/checksec.html\n") flog.write("Please install execstack from prelink package\n") @@ -125,39 +129,48 @@ def write_report(self, ISA_filesystem): for item in self.execstack_not_defined: item = item.replace(ISA_filesystem.path_to_fs, "") fproblems_report.write(item + '\n') + fproblems_report.write("\n\nFiles that don't initialize groups while using setuid/setgid:\n") + for item in self.nodrop_groups: + item = item.replace(ISA_filesystem.path_to_fs, "") + fproblems_report.write(item + '\n') def write_report_xml(self, ISA_filesystem): - root = etree.Element('testsuite', name='CFA_Plugin', tests='6') + root = etree.Element('testsuite', name='CFA_Plugin', tests='7') tcase1 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_RELO') if self.no_relo: for item in self.no_relo: item = item.replace(ISA_filesystem.path_to_fs, "") - failrs1 = etree.SubElement(tcase1, 'failure', message=item, type='violation') + etree.SubElement(tcase1, 'failure', message=item, type='violation') tcase2 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_canary') if self.no_canary: for item in self.no_canary: item = item.replace(ISA_filesystem.path_to_fs, "") - failrs2 = etree.SubElement(tcase2, 'failure', message=item, type='violation') + etree.SubElement(tcase2, 'failure', message=item, type='violation') tcase3 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_PIE') if self.no_pie: for item in self.no_pie: item = item.replace(ISA_filesystem.path_to_fs, "") - failrs3 = etree.SubElement(tcase3, 'failure', message=item, type='violation') + etree.SubElement(tcase3, 'failure', message=item, type='violation') tcase4 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_NX') if self.no_nx: for item in self.no_nx: item = item.replace(ISA_filesystem.path_to_fs, "") - failrs4 = etree.SubElement(tcase4, 'failure', message=item, type='violation') + etree.SubElement(tcase4, 'failure', message=item, type='violation') tcase5 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_execstack') if self.execstack: for item in self.execstack: item = item.replace(ISA_filesystem.path_to_fs, "") - failrs4 = etree.SubElement(tcase5, 'failure', message=item, type='violation') + etree.SubElement(tcase5, 'failure', message=item, type='violation') tcase6 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_execstack_not_defined') if self.execstack_not_defined: for item in self.execstack_not_defined: item = item.replace(ISA_filesystem.path_to_fs, "") - failrs4 = etree.SubElement(tcase6, 'failure', message=item, type='violation') + etree.SubElement(tcase6, 'failure', message=item, type='violation') + tcase7 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_nodrop_groups') + if self.nodrop_groups: + for item in self.nodrop_groups: + item = item.replace(ISA_filesystem.path_to_fs, "") + etree.SubElement(tcase7, 'failure', message=item, type='violation') tree = etree.ElementTree(root) output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) @@ -182,6 +195,19 @@ def get_execstack(self, file_name): self.execstack_not_defined.append(file_name[:]) return result + def get_nodrop_groups(self, file_name): + cmd = ['readelf', '-s', file_name] + try: + result = subprocess.check_output(cmd).decode("utf-8") + except: + return "Not able to fetch nodrop groups status" + else: + if ("setgid@GLIBC" in result) or ("setegid@GLIBC" in result) or ("setresgid@GLIBC" in result): + if ("setuid@GLIBC" in result) or ("seteuid@GLIBC" in result) or ("setresuid@GLIBC" in result): + if ("setgroups@GLIBC" not in result) and ("initgroups@GLIBC" not in result): + self.nodrop_groups.append(file_name[:]) + return result + def get_security_flags(self, file_name): SF = { 'No RELRO' : 0, @@ -267,6 +293,7 @@ def process_files(self, img_name, path_to_fs): else: sec_field = self.get_security_flags(real_file) execstack = self.get_execstack(real_file) + nodrop_groups = self.get_nodrop_groups(real_file) with open(self.reportdir + full_report + img_name + "_" + self.timestamp, 'a') as ffull_report: real_file = real_file.replace(path_to_fs, "") ffull_report.write(real_file + ": ") @@ -274,6 +301,7 @@ def process_files(self, img_name, path_to_fs): line = ' '.join(str(x) for x in s) ffull_report.write(line + ' ') ffull_report.write('\nexecstack: ' + execstack +' ') + ffull_report.write('\nnodrop_groups: ' + nodrop_groups +' ') ffull_report.write('\n') else: continue From 161654c85825579f9fe37ea088f581705cd0a5c4 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Mon, 1 Feb 2016 13:05:40 +0200 Subject: [PATCH 05/25] Adding a check for CONFIG_X86_INTEL_MPX to KCA plugin --- lib/isafw/isaplugins/ISA_kca_plugin.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_kca_plugin.py b/lib/isafw/isaplugins/ISA_kca_plugin.py index 00df7ff..faa09bf 100644 --- a/lib/isafw/isaplugins/ISA_kca_plugin.py +++ b/lib/isafw/isaplugins/ISA_kca_plugin.py @@ -54,8 +54,9 @@ class ISA_KernelChecker(): 'CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE' : 'not set', 'CONFIG_DEBUG_KERNEL' : 'not set', 'CONFIG_DEBUG_FS' : 'not set', - 'CONFIG_MODULE_SIG_FORCE' : 'not set' - } + 'CONFIG_MODULE_SIG_FORCE' : 'not set', + 'CONFIG_X86_INTEL_MPX' : 'not set' + } hardening_kco_ref={'CONFIG_CC_STACKPROTECTOR' : 'y', 'CONFIG_DEFAULT_MMAP_MIN_ADDR' : '65536', # x86 specific @@ -75,7 +76,8 @@ class ISA_KernelChecker(): 'CONFIG_ARCH_BINFMT_ELF_RANDOMIZE_PIE' : 'y', 'CONFIG_DEBUG_KERNEL' : 'not set', 'CONFIG_DEBUG_FS' : 'not set', - 'CONFIG_MODULE_SIG_FORCE' : 'y' + 'CONFIG_MODULE_SIG_FORCE' : 'y', + 'CONFIG_X86_INTEL_MPX' : 'y' # x86 and certain HW variants specific } keys_kco = { 'CONFIG_KEYS' : 'not set', @@ -287,7 +289,7 @@ def write_problems_report(self, ISA_kernel): tcase1 = etree.SubElement(root, 'testcase', classname ='ISA_KernelChecker', name = 'Hardening_options_that_need_improvement') for key in sorted(self.hardening_kco) : if (self.hardening_kco[key] != self.hardening_kco_ref[key]) : - valid == False + valid = False if (key == "CONFIG_DEBUG_STRICT_USER_COPY_CHECKS") : if (self.hardening_kco['CONFIG_ARCH_HAS_DEBUG_STRICT_USER_COPY_CHECKS'] == 'y'): valid = True From 6885a77b2bd2dbc359a752344706499cdb810d1a Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Mon, 1 Feb 2016 13:42:17 +0200 Subject: [PATCH 06/25] Adding checks for mpx for executables in CFA plugin --- lib/isafw/isaplugins/ISA_cfa_plugin.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/isafw/isaplugins/ISA_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index 4cea7b7..cddeaff 100644 --- a/lib/isafw/isaplugins/ISA_cfa_plugin.py +++ b/lib/isafw/isaplugins/ISA_cfa_plugin.py @@ -48,6 +48,7 @@ class ISA_CFChecker(): execstack = [] execstack_not_defined = [] nodrop_groups = [] + no_mpx = [] def __init__(self, ISA_config): self.proxy = ISA_config.proxy @@ -133,9 +134,13 @@ def write_report(self, ISA_filesystem): for item in self.nodrop_groups: item = item.replace(ISA_filesystem.path_to_fs, "") fproblems_report.write(item + '\n') + fproblems_report.write("\n\nFiles that don't have MPX protection enabled:\n") + for item in self.no_mpx: + item = item.replace(ISA_filesystem.path_to_fs, "") + fproblems_report.write(item + '\n') def write_report_xml(self, ISA_filesystem): - root = etree.Element('testsuite', name='CFA_Plugin', tests='7') + root = etree.Element('testsuite', name='CFA_Plugin', tests='8') tcase1 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_RELO') if self.no_relo: for item in self.no_relo: @@ -171,6 +176,11 @@ def write_report_xml(self, ISA_filesystem): for item in self.nodrop_groups: item = item.replace(ISA_filesystem.path_to_fs, "") etree.SubElement(tcase7, 'failure', message=item, type='violation') + tcase8 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_mpx') + if self.no_mpx: + for item in self.no_mpx: + item = item.replace(ISA_filesystem.path_to_fs, "") + etree.SubElement(tcase8, 'failure', message=item, type='violation') tree = etree.ElementTree(root) output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) @@ -208,6 +218,17 @@ def get_nodrop_groups(self, file_name): self.nodrop_groups.append(file_name[:]) return result + def get_mpx(self, file_name): + cmd = ['objdump', '-d', file_name] + try: + result = subprocess.check_output(cmd).decode("utf-8") + except: + return "Not able to fetch mpx status" + else: + if ("bndcu" not in result) and ("bndcl" not in result) and ("bndmov" not in result): + self.no_mpx.append(file_name[:]) + return result + def get_security_flags(self, file_name): SF = { 'No RELRO' : 0, @@ -294,6 +315,7 @@ def process_files(self, img_name, path_to_fs): sec_field = self.get_security_flags(real_file) execstack = self.get_execstack(real_file) nodrop_groups = self.get_nodrop_groups(real_file) + no_mpx = self.get_mpx(real_file) with open(self.reportdir + full_report + img_name + "_" + self.timestamp, 'a') as ffull_report: real_file = real_file.replace(path_to_fs, "") ffull_report.write(real_file + ": ") @@ -302,6 +324,7 @@ def process_files(self, img_name, path_to_fs): ffull_report.write(line + ' ') ffull_report.write('\nexecstack: ' + execstack +' ') ffull_report.write('\nnodrop_groups: ' + nodrop_groups +' ') + ffull_report.write('\nno mpx: ' + no_mpx +' ') ffull_report.write('\n') else: continue From 82d56b8595a0c7191b86b5f4a2064a3b558b381c Mon Sep 17 00:00:00 2001 From: Sergey Popovich Date: Thu, 4 Feb 2016 09:50:42 +0000 Subject: [PATCH 07/25] isafw.bbclass: Use Base Package Name (BPN) instead of just Package Name (PN) While we skipping analysesource task for most of native, nativesdk, cross, crosssdk and other stuff we still have packages like glibc-initial in our report. This is because such initial packages are not belonging to any specific target/image class and thus not subject to the analysesource task removal. To address this issue use Base Package Name (BPN) since it just version of Package Name (PN) with stripped suffixes like -initial, -native, -cross etc. Signed-off-by: Sergey Popovich --- classes/isafw.bbclass | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/isafw.bbclass b/classes/isafw.bbclass index 8de2573..c065ea4 100644 --- a/classes/isafw.bbclass +++ b/classes/isafw.bbclass @@ -46,7 +46,7 @@ python do_analysesource() { fetch.unpack(workdir, (url,)) recipe = isafw.ISA_package() - recipe.name = d.getVar('PN', True) + recipe.name = d.getVar('BPN', True) recipe.version = d.getVar('PV', True) recipe.version = recipe.version.split('+git', 1)[0] From 359d4204cf3075340660cf7017a29635c5ae19da Mon Sep 17 00:00:00 2001 From: Sergey Popovich Date: Thu, 4 Feb 2016 10:54:02 +0000 Subject: [PATCH 08/25] isafw.bbclass: Add documentation strings describing common layer tasks Two common and public tasks exposed by the class are analyse_sources and analyse_sources_all. Both used to generate ISAFW reports against given package(s) without building them. Add descriptive text strings to the task definitions within class to give more information on them to the user in the listtasks bitbake task output. $ bitbake -c listtasks do_analyse_sources Produce ISAFW reports based ... do_analyse_sources_all Produce ISAFW reports for all ... ... TODO: find a way to hide class private tasks do_analysesource and do_process_reports. Signed-off-by: Sergey Popovich --- classes/isafw.bbclass | 2 ++ 1 file changed, 2 insertions(+) diff --git a/classes/isafw.bbclass b/classes/isafw.bbclass index c065ea4..d3cb494 100644 --- a/classes/isafw.bbclass +++ b/classes/isafw.bbclass @@ -116,6 +116,7 @@ addtask do_process_reports after do_${PR_ORIG_TASK} # These tasks are intended to be called directly by the user (e.g. bitbake -c) addtask do_analyse_sources after do_analysesource +do_analyse_sources[doc] = "Produce ISAFW reports based on given package without building it" do_analyse_sources[nostamp] = "1" do_analyse_sources[postfuncs] = "do_process_reports" do_analyse_sources() { @@ -123,6 +124,7 @@ do_analyse_sources() { } addtask do_analyse_sources_all after do_analysesource +do_analyse_sources_all[doc] = "Produce ISAFW reports for all packages in given target without building them" do_analyse_sources_all[recrdeptask] = "do_analyse_sources_all do_analysesource" do_analyse_sources_all[recideptask] = "do_${PR_ORIG_TASK}" do_analyse_sources_all[nostamp] = "1" From ba16be9ea4388a3ac4419b86f6157f30697477d7 Mon Sep 17 00:00:00 2001 From: Sergey Popovich Date: Thu, 4 Feb 2016 11:05:06 +0000 Subject: [PATCH 09/25] README.md: Add more Markdown styles to the text Signed-off-by: Sergey Popovich --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 49027ce..16b2eb9 100644 --- a/README.md +++ b/README.md @@ -31,16 +31,20 @@ Usage In order to enable the isafw during the image build, please add the following line to your build/conf/local.conf file: +```python INHERIT += "isafw" +``` Next you need to update your build/conf/bblayers.conf file with the location of meta-security-isafw layer on your filesystem along with any other layers needed. e.g.: +```python BBLAYERS ?= " \ /OE/oe-core/meta \ /OE/meta-security-isafw \ " +``` Also, some isafw plugins require network connection, so in case of a proxy setup please make sure to export http_proxy variable into your @@ -49,12 +53,16 @@ environment. In order to produce image reports, you can execute image build normally. For example: +```shell bitbake core-image-minimal +``` If you are only interested to produce a report based on packages and without building an image, please use: +```shell bitbake -c analyse_sources_all core-image-minimal +``` Logs From bfe3ad8a4d6173842d5b3e97fc10a6093f2e3dce Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Thu, 4 Feb 2016 17:49:29 +0200 Subject: [PATCH 10/25] Additional fixes for xml output --- lib/isafw/isaplugins/ISA_cfa_plugin.py | 19 ++++++++++--------- lib/isafw/isaplugins/ISA_cve_plugin.py | 12 ++++++++---- lib/isafw/isaplugins/ISA_fsa_plugin.py | 11 ++++++----- lib/isafw/isaplugins/ISA_kca_plugin.py | 11 ++++++----- lib/isafw/isaplugins/ISA_la_plugin.py | 17 ++++++++++++----- 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index cddeaff..1a366b1 100644 --- a/lib/isafw/isaplugins/ISA_cfa_plugin.py +++ b/lib/isafw/isaplugins/ISA_cfa_plugin.py @@ -140,46 +140,47 @@ def write_report(self, ISA_filesystem): fproblems_report.write(item + '\n') def write_report_xml(self, ISA_filesystem): - root = etree.Element('testsuite', name='CFA_Plugin', tests='8') - tcase1 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_RELO') + numTests = len(self.no_relo) + len(self.no_canary) + len(self.no_pie) + len(self.no_nx) + len(self.execstack) + len(self.execstack_not_defined) + len(self.nodrop_groups) + len(self.no_mpx) + root = etree.Element('testsuite', name='ISA_CFChecker', tests=str(numTests)) if self.no_relo: for item in self.no_relo: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase1 = etree.SubElement(root, 'testcase', classname='files_with_no_RELO', name=item) etree.SubElement(tcase1, 'failure', message=item, type='violation') - tcase2 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_canary') if self.no_canary: for item in self.no_canary: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase2 = etree.SubElement(root, 'testcase', classname='files_with_no_canary', name=item) etree.SubElement(tcase2, 'failure', message=item, type='violation') - tcase3 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_PIE') if self.no_pie: for item in self.no_pie: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase3 = etree.SubElement(root, 'testcase', classname='files_with_no_PIE', name=item) etree.SubElement(tcase3, 'failure', message=item, type='violation') - tcase4 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_NX') if self.no_nx: for item in self.no_nx: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase4 = etree.SubElement(root, 'testcase', classname='files_with_no_NX', name=item) etree.SubElement(tcase4, 'failure', message=item, type='violation') - tcase5 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_execstack') if self.execstack: for item in self.execstack: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase5 = etree.SubElement(root, 'testcase', classname='files_with_execstack', name=item) etree.SubElement(tcase5, 'failure', message=item, type='violation') - tcase6 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_execstack_not_defined') if self.execstack_not_defined: for item in self.execstack_not_defined: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase6 = etree.SubElement(root, 'testcase', classname='files_with_execstack_not_defined', name=item) etree.SubElement(tcase6, 'failure', message=item, type='violation') - tcase7 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_nodrop_groups') if self.nodrop_groups: for item in self.nodrop_groups: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase7 = etree.SubElement(root, 'testcase', classname='files_with_nodrop_groups', name=item) etree.SubElement(tcase7, 'failure', message=item, type='violation') - tcase8 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_mpx') if self.no_mpx: for item in self.no_mpx: item = item.replace(ISA_filesystem.path_to_fs, "") + tcase8 = etree.SubElement(root, 'testcase', classname='files_with_no_mpx', name=item) etree.SubElement(tcase8, 'failure', message=item, type='violation') tree = etree.ElementTree(root) output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' diff --git a/lib/isafw/isaplugins/ISA_cve_plugin.py b/lib/isafw/isaplugins/ISA_cve_plugin.py index 3d4e302..655f2d1 100644 --- a/lib/isafw/isaplugins/ISA_cve_plugin.py +++ b/lib/isafw/isaplugins/ISA_cve_plugin.py @@ -109,14 +109,18 @@ def process_report(self): def write_report_xml(self): from lxml import etree + numTests = 0 root = etree.Element('testsuite', name='CVE_Plugin', tests='1') - tcase1 = etree.SubElement(root, 'testcase', classname='ISA_CVEChecker', name='found_CVEs') with open(self.reportdir + cve_report + "_" + self.timestamp + ".csv", 'r') as f: for line in f: + numTests += 1 line = line.strip() - line2 = line.split(',', 2) - if line2[2].startswith('CVE'): - failrs1 = etree.SubElement(tcase1, 'failure', message=line, type='violation') + if (line.split(',', 2))[2].startswith('CVE'): + tcase = etree.SubElement(root, 'testcase', classname='ISA_CVEChecker', name=line.split(',',1)[0]) + failrs1 = etree.SubElement(tcase, 'failure', message=line, type='violation') + else: + tcase = etree.SubElement(root, 'testcase', classname='ISA_CVEChecker', name=line.split(',',1)[0]) + root.set('tests', str(numTests)) tree = etree.ElementTree(root) output = self.reportdir + cve_report + "_" + self.timestamp + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) diff --git a/lib/isafw/isaplugins/ISA_fsa_plugin.py b/lib/isafw/isaplugins/ISA_fsa_plugin.py index e67535d..358f158 100644 --- a/lib/isafw/isaplugins/ISA_fsa_plugin.py +++ b/lib/isafw/isaplugins/ISA_fsa_plugin.py @@ -107,22 +107,23 @@ def write_problems_report(self, ISA_filesystem): fproblems_report.write(item + '\n') def write_problems_report_xml(self, ISA_filesystem): - root = etree.Element('testsuite', name = 'FSA_Plugin', tests = '4') - tcase1 = etree.SubElement(root, 'testcase', classname = 'ISA_FSChecker', name = 'Files_with_SETUID_bit_set') + numTests = len(self.setuid_files) + len(self.setgid_files) + len(self.ww_files) + len(self.no_sticky_bit_ww_dirs) + root = etree.Element('testsuite', name = 'FSA_Plugin', tests = str(numTests)) if self.setuid_files: for item in self.setuid_files: + tcase1 = etree.SubElement(root, 'testcase', classname = 'Files_with_SETUID_bit_set', name = item) failrs1 = etree.SubElement(tcase1, 'failure', message = item, type = 'violation') - tcase2 = etree.SubElement(root, 'testacase', classname = 'ISA_FSChecker', name = 'Files_with_SETGID_bit_set') if self.setgid_files: for item in self.setgid_files: + tcase2 = etree.SubElement(root, 'testacase', classname = 'Files_with_SETGID_bit_set', name = item) failrs2 = etree.SubElement(tcase2, 'failure', message = item, type = 'violation') - tcase3 = etree.SubElement(root, 'testase', classname = 'ISA_FSChecker', name = 'World-writable_files') if self.ww_files: for item in self.ww_files: + tcase3 = etree.SubElement(root, 'testase', classname = 'World-writable_files', name = item) failrs3 = etree.SubElement(tcase3, 'failure', message = item, type = 'violation') - tcase4 = etree.SubElement(root, 'testcase', classname = 'ISA_FSChecker', name = 'World-writable_dirs_with_no_sticky_bit') if self.no_sticky_bit_ww_dirs: for item in self.no_sticky_bit_ww_dirs: + tcase4 = etree.SubElement(root, 'testcase', classname = 'World-writable_dirs_with_no_sticky_bit', name = item) failrs4 = etree.SubElement(tcase4, 'failure', message = item, type = 'violation') tree = etree.ElementTree(root) output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' diff --git a/lib/isafw/isaplugins/ISA_kca_plugin.py b/lib/isafw/isaplugins/ISA_kca_plugin.py index faa09bf..15cb94b 100644 --- a/lib/isafw/isaplugins/ISA_kca_plugin.py +++ b/lib/isafw/isaplugins/ISA_kca_plugin.py @@ -285,9 +285,10 @@ def write_problems_report(self, ISA_kernel): freport.write("Recommended value:\n") freport.write(key + ' : ' + str(self.integrity_kco_ref[key]) + '\n') # write_problems_report_xml - root = etree.Element('testsuite', name = 'KCA_Plugin', tests='4') - tcase1 = etree.SubElement(root, 'testcase', classname ='ISA_KernelChecker', name = 'Hardening_options_that_need_improvement') + numTests = len(self.hardening_kco) + len(self.keys_kco) + len(self.security_kco) + len(self.integrity_kco) + root = etree.Element('testsuite', name = 'KCA_Plugin', tests=str(numTests)) for key in sorted(self.hardening_kco) : + tcase1 = etree.SubElement(root, 'testcase', classname ='Hardening options', name = key) if (self.hardening_kco[key] != self.hardening_kco_ref[key]) : valid = False if (key == "CONFIG_DEBUG_STRICT_USER_COPY_CHECKS") : @@ -302,13 +303,13 @@ def write_problems_report(self, ISA_kernel): if valid == False: msg1 = 'current=' + key + ' is ' + str(self.hardening_kco[key]) + ', recommended=' + key + ' is ' + str(self.hardening_kco_ref[key]) failrs1 = etree.SubElement(tcase1, 'failure', message = msg1, type = 'violation') - tcase2 = etree.SubElement(root, 'testcase', classname = 'ISA_KernelChecker', name = 'Key-related_options_that_need_improvement') for key in sorted(self.keys_kco): + tcase2 = etree.SubElement(root, 'testcase', classname = 'Key-related options', name = key) if (self.keys_kco[key] != self.keys_kco_ref[key]) : msg2 = 'current=' + key + ' is ' + str(self.keys_kco[key] + ', recommended=' + key + ' is ' + str(self.keys_kco_ref[key])) failrs2 = etree.SubElement(tcase2, 'failure', message = msg2, type = 'violation') - tcase3 = etree.SubElement(root, 'testcase', classname = 'ISA_KernelChecker', name = 'Security_options_that_need_improvement') for key in sorted(self.security_kco): + tcase3 = etree.SubElement(root, 'testcase', classname = 'Security options', name = key) if (self.security_kco[key] != self.security_kco_ref[key]) : valid = False if (key == "CONFIG_DEFAULT_SECURITY"): @@ -329,8 +330,8 @@ def write_problems_report(self, ISA_kernel): if valid == False: msg3 = 'current=' + key + ' is ' + str(self.security_kco[key]) + ', recommended=' + key + ' is ' + str(self.security_kco_ref[key]) failrs3 = etree.SubElement(tcase3, 'failure', message = msg3, type ='violation') - tcase4 = etree.SubElement(root, 'testcase', classname = 'ISA_KernelChecker', name = 'Integrity_options_that_need_improvement') for key in sorted(self.integrity_kco): + tcase4 = etree.SubElement(root, 'testcase', classname = 'Integrity options', name = key) if (self.integrity_kco[key] != self.integrity_kco_ref[key]) : valid = False if ((key == "CONFIG_IMA_DEFAULT_HASH_SHA1") or diff --git a/lib/isafw/isaplugins/ISA_la_plugin.py b/lib/isafw/isaplugins/ISA_la_plugin.py index 4dabca5..1b88ad3 100644 --- a/lib/isafw/isaplugins/ISA_la_plugin.py +++ b/lib/isafw/isaplugins/ISA_la_plugin.py @@ -117,12 +117,19 @@ def process_report(self): def write_report_xml(self): from lxml import etree + numTests = 0 root = etree.Element('testsuite', name='LA_Plugin', tests='1') - tcase1 = etree.SubElement(root, 'testcase', classname='ISA_LAChecker', name='license_violations') - with open(self.reportdir + "/la_problems_report_" + self.timestamp, 'r') as f: - for line in f: - line = line.strip() - failrs1 = etree.SubElement(tcase1, 'failure', message=line, type='violation') + if os.path.isfile (self.reportdir + "/la_problems_report_" + self.timestamp): + with open(self.reportdir + "/la_problems_report_" + self.timestamp, 'r') as f: + for line in f: + numTests += 1 + line = line.strip() + tcase1 = etree.SubElement(root, 'testcase', classname='ISA_LAChecker', name=line.split(':',1)[0]) + failrs1 = etree.SubElement(tcase1, 'failure', message=line, type='violation') + else: + tcase1 = etree.SubElement(root, 'testcase', classname='ISA_LAChecker', name='none') + numTests = 1 + root.set('tests', str(numTests)) tree = etree.ElementTree(root) output = self.reportdir + "/la_problems_report_" + self.timestamp + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) From 859837e8309a33a9870ceab3a8a41c5a203b15b3 Mon Sep 17 00:00:00 2001 From: Marco Dallagiacoma Date: Thu, 4 Feb 2016 20:02:27 +0200 Subject: [PATCH 11/25] Add ISA_dep_plugin for dependency checking and visualization This plugin currently builds a dependency graph internally and puts it in the log file. Temporary data is stored in the report directory, and automatically deleted after a succesfull build. --- lib/isafw/isaplugins/ISA_dep_plugin.py | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 lib/isafw/isaplugins/ISA_dep_plugin.py diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py new file mode 100644 index 0000000..a474c47 --- /dev/null +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -0,0 +1,78 @@ +import os +try: + import cPickle as pickle +except ImportError: + import pickle + +log = '/isafw_deplog' +temp_file = '/isafw_dep_tmp' +DEPChecker = None + + +class ISA_DEPChecker: + initialized = False + + def __init__(self, ISA_config): + self.logdir = ISA_config.logdir + self.reportdir = ISA_config.reportdir + self.initialized = True + + with open(self.logdir + log, 'a') as flog: + flog.write("\nPlugin ISA_DEPChecker initialized!\n") + + def process_package(self, ISA_pkg): + b_deps = ISA_pkg.b_deps + + with open(self.reportdir + temp_file, 'a+b') as tmp: + obj = (ISA_pkg.name, b_deps) + pickle.dump(obj, tmp) + + def process_report(self): + tmp = open(self.reportdir + temp_file, 'rb') + + depgraph = {} + while True: + try: + pkg, deps = pickle.load(tmp) + + # update dependencies if node already exists in depgraph, + # add new empty node otherwise + node = depgraph.setdefault(pkg, set()) + node.update(deps) + + except EOFError: + break + + tmp.close() + + try: # remove temp file + os.remove(self.reportdir + temp_file) + except OSError: + pass + + # put dependency graph in log file. todo: remove me + with open(self.logdir + log, 'a') as flog: + flog.write(str(depgraph)) + + +# ======== supported callbacks from ISA ============= # + +def init(ISA_config): + global DEPChecker + DEPChecker = DEPChecker or ISA_DEPChecker(ISA_config) + + +def getPluginName(): + return "ISA_DEPChecker" + + +def process_package(ISA_pkg): + global DEPChecker + return DEPChecker.process_package(ISA_pkg) + + +def process_report(): + global DEPChecker + return DEPChecker.process_report() + +# ==================================================== # From f0e5ed6c512f87737a3e8d6bc2817cef6f6952f5 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Fri, 5 Feb 2016 13:16:07 +0200 Subject: [PATCH 12/25] Fetch checksec instead of storing the sript --- .../checksec/checksec/checksec.sh | 882 ------------------ recipes-devtools/checksec/checksec_1.5.bb | 5 +- 2 files changed, 4 insertions(+), 883 deletions(-) delete mode 100644 recipes-devtools/checksec/checksec/checksec.sh diff --git a/recipes-devtools/checksec/checksec/checksec.sh b/recipes-devtools/checksec/checksec/checksec.sh deleted file mode 100644 index dd1f72e..0000000 --- a/recipes-devtools/checksec/checksec/checksec.sh +++ /dev/null @@ -1,882 +0,0 @@ -#!/bin/bash -# -# The BSD License (http://www.opensource.org/licenses/bsd-license.php) -# specifies the terms and conditions of use for checksec.sh: -# -# Copyright (c) 2009-2011, Tobias Klein. -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in -# the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Tobias Klein nor the name of trapkit.de may be -# used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS -# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH -# DAMAGE. -# -# Name : checksec.sh -# Version : 1.5 -# Author : Tobias Klein -# Date : November 2011 -# Download: http://www.trapkit.de/tools/checksec.html -# Changes : http://www.trapkit.de/tools/checksec_changes.txt -# -# Description: -# -# Modern Linux distributions offer some mitigation techniques to make it -# harder to exploit software vulnerabilities reliably. Mitigations such -# as RELRO, NoExecute (NX), Stack Canaries, Address Space Layout -# Randomization (ASLR) and Position Independent Executables (PIE) have -# made reliably exploiting any vulnerabilities that do exist far more -# challenging. The checksec.sh script is designed to test what *standard* -# Linux OS and PaX (http://pax.grsecurity.net/) security features are being -# used. -# -# As of version 1.3 the script also lists the status of various Linux kernel -# protection mechanisms. -# -# Credits: -# -# Thanks to Brad Spengler (grsecurity.net) for the PaX support. -# Thanks to Jon Oberheide (jon.oberheide.org) for the kernel support. -# Thanks to Ollie Whitehouse (Research In Motion) for rpath/runpath support. -# -# Others that contributed to checksec.sh (in no particular order): -# -# Simon Ruderich, Denis Scherbakov, Stefan Kuttler, Radoslaw Madej, -# Anthony G. Basile, Martin Vaeth and Brian Davis. -# - -# global vars -have_readelf=1 -verbose=false - -# FORTIFY_SOURCE vars -FS_end=_chk -FS_cnt_total=0 -FS_cnt_checked=0 -FS_cnt_unchecked=0 -FS_chk_func_libc=0 -FS_functions=0 -FS_libc=0 - -# version information -version() { - echo "checksec v1.5, Tobias Klein, www.trapkit.de, November 2011" - echo -} - -# help -help() { - echo "Usage: checksec [OPTION]" - echo - echo "Options:" - echo - echo " --file " - echo " --dir [-v]" - echo " --proc " - echo " --proc-all" - echo " --proc-libs " - echo " --kernel" - echo " --fortify-file " - echo " --fortify-proc " - echo " --version" - echo " --help" - echo - echo "For more information, see:" - echo " http://www.trapkit.de/tools/checksec.html" - echo -} - -# check if command exists -command_exists () { - type $1 > /dev/null 2>&1; -} - -# check if directory exists -dir_exists () { - if [ -d $1 ] ; then - return 0 - else - return 1 - fi -} - -# check user privileges -root_privs () { - if [ $(/usr/bin/id -u) -eq 0 ] ; then - return 0 - else - return 1 - fi -} - -# check if input is numeric -isNumeric () { - echo "$@" | grep -q -v "[^0-9]" -} - -# check if input is a string -isString () { - echo "$@" | grep -q -v "[^A-Za-z]" -} - -# check file(s) -filecheck() { - # check for RELRO support - if readelf -l $1 2>/dev/null | grep -q 'GNU_RELRO'; then - if readelf -d $1 2>/dev/null | grep -q 'BIND_NOW'; then - echo -n -e '\033[32mFull RELRO \033[m ' - else - echo -n -e '\033[33mPartial RELRO\033[m ' - fi - else - echo -n -e '\033[31mNo RELRO \033[m ' - fi - - # check for stack canary support - if readelf -s $1 2>/dev/null | grep -q '__stack_chk_fail'; then - echo -n -e '\033[32mCanary found \033[m ' - else - echo -n -e '\033[31mNo canary found\033[m ' - fi - - # check for NX support - if readelf -W -l $1 2>/dev/null | grep 'GNU_STACK' | grep -q 'RWE'; then - echo -n -e '\033[31mNX disabled\033[m ' - else - echo -n -e '\033[32mNX enabled \033[m ' - fi - - # check for PIE support - if readelf -h $1 2>/dev/null | grep -q 'Type:[[:space:]]*EXEC'; then - echo -n -e '\033[31mNo PIE \033[m ' - elif readelf -h $1 2>/dev/null | grep -q 'Type:[[:space:]]*DYN'; then - if readelf -d $1 2>/dev/null | grep -q '(DEBUG)'; then - echo -n -e '\033[32mPIE enabled \033[m ' - else - echo -n -e '\033[33mDSO \033[m ' - fi - else - echo -n -e '\033[33mNot an ELF file\033[m ' - fi - - # check for rpath / run path - if readelf -d $1 2>/dev/null | grep -q 'rpath'; then - echo -n -e '\033[31mRPATH \033[m ' - else - echo -n -e '\033[32mNo RPATH \033[m ' - fi - - if readelf -d $1 2>/dev/null | grep -q 'runpath'; then - echo -n -e '\033[31mRUNPATH \033[m ' - else - echo -n -e '\033[32mNo RUNPATH \033[m ' - fi -} - -# check process(es) -proccheck() { - # check for RELRO support - if readelf -l $1/exe 2>/dev/null | grep -q 'Program Headers'; then - if readelf -l $1/exe 2>/dev/null | grep -q 'GNU_RELRO'; then - if readelf -d $1/exe 2>/dev/null | grep -q 'BIND_NOW'; then - echo -n -e '\033[32mFull RELRO \033[m ' - else - echo -n -e '\033[33mPartial RELRO \033[m ' - fi - else - echo -n -e '\033[31mNo RELRO \033[m ' - fi - else - echo -n -e '\033[31mPermission denied (please run as root)\033[m\n' - exit 1 - fi - - # check for stack canary support - if readelf -s $1/exe 2>/dev/null | grep -q 'Symbol table'; then - if readelf -s $1/exe 2>/dev/null | grep -q '__stack_chk_fail'; then - echo -n -e '\033[32mCanary found \033[m ' - else - echo -n -e '\033[31mNo canary found \033[m ' - fi - else - if [ "$1" != "1" ] ; then - echo -n -e '\033[33mPermission denied \033[m ' - else - echo -n -e '\033[33mNo symbol table found\033[m ' - fi - fi - - # first check for PaX support - if cat $1/status 2> /dev/null | grep -q 'PaX:'; then - pageexec=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b6) ) - segmexec=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b10) ) - mprotect=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b8) ) - randmmap=( $(cat $1/status 2> /dev/null | grep 'PaX:' | cut -b9) ) - if [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "M" && "$randmmap" = "R" ]] ; then - echo -n -e '\033[32mPaX enabled\033[m ' - elif [[ "$pageexec" = "p" && "$segmexec" = "s" && "$randmmap" = "R" ]] ; then - echo -n -e '\033[33mPaX ASLR only\033[m ' - elif [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "m" && "$randmmap" = "R" ]] ; then - echo -n -e '\033[33mPaX mprot off \033[m' - elif [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "M" && "$randmmap" = "r" ]] ; then - echo -n -e '\033[33mPaX ASLR off\033[m ' - elif [[ "$pageexec" = "P" || "$segmexec" = "S" ]] && [[ "$mprotect" = "m" && "$randmmap" = "r" ]] ; then - echo -n -e '\033[33mPaX NX only\033[m ' - else - echo -n -e '\033[31mPaX disabled\033[m ' - fi - # fallback check for NX support - elif readelf -W -l $1/exe 2>/dev/null | grep 'GNU_STACK' | grep -q 'RWE'; then - echo -n -e '\033[31mNX disabled\033[m ' - else - echo -n -e '\033[32mNX enabled \033[m ' - fi - - # check for PIE support - if readelf -h $1/exe 2>/dev/null | grep -q 'Type:[[:space:]]*EXEC'; then - echo -n -e '\033[31mNo PIE \033[m ' - elif readelf -h $1/exe 2>/dev/null | grep -q 'Type:[[:space:]]*DYN'; then - if readelf -d $1/exe 2>/dev/null | grep -q '(DEBUG)'; then - echo -n -e '\033[32mPIE enabled \033[m ' - else - echo -n -e '\033[33mDynamic Shared Object\033[m ' - fi - else - echo -n -e '\033[33mNot an ELF file \033[m ' - fi -} - -# check mapped libraries -libcheck() { - libs=( $(awk '{ print $6 }' /proc/$1/maps | grep '/' | sort -u | xargs file | grep ELF | awk '{ print $1 }' | sed 's/:/ /') ) - - printf "\n* Loaded libraries (file information, # of mapped files: ${#libs[@]}):\n\n" - - for element in $(seq 0 $((${#libs[@]} - 1))) - do - echo " ${libs[$element]}:" - echo -n " " - filecheck ${libs[$element]} - printf "\n\n" - done -} - -# check for system-wide ASLR support -aslrcheck() { - # PaX ASLR support - if !(cat /proc/1/status 2> /dev/null | grep -q 'Name:') ; then - echo -n -e ':\033[33m insufficient privileges for PaX ASLR checks\033[m\n' - echo -n -e ' Fallback to standard Linux ASLR check' - fi - - if cat /proc/1/status 2> /dev/null | grep -q 'PaX:'; then - printf ": " - if cat /proc/1/status 2> /dev/null | grep 'PaX:' | grep -q 'R'; then - echo -n -e '\033[32mPaX ASLR enabled\033[m\n\n' - else - echo -n -e '\033[31mPaX ASLR disabled\033[m\n\n' - fi - else - # standard Linux 'kernel.randomize_va_space' ASLR support - # (see the kernel file 'Documentation/sysctl/kernel.txt' for a detailed description) - printf " (kernel.randomize_va_space): " - if /sbin/sysctl -a 2>/dev/null | grep -q 'kernel.randomize_va_space = 1'; then - echo -n -e '\033[33mOn (Setting: 1)\033[m\n\n' - printf " Description - Make the addresses of mmap base, stack and VDSO page randomized.\n" - printf " This, among other things, implies that shared libraries will be loaded to \n" - printf " random addresses. Also for PIE-linked binaries, the location of code start\n" - printf " is randomized. Heap addresses are *not* randomized.\n\n" - elif /sbin/sysctl -a 2>/dev/null | grep -q 'kernel.randomize_va_space = 2'; then - echo -n -e '\033[32mOn (Setting: 2)\033[m\n\n' - printf " Description - Make the addresses of mmap base, heap, stack and VDSO page randomized.\n" - printf " This, among other things, implies that shared libraries will be loaded to random \n" - printf " addresses. Also for PIE-linked binaries, the location of code start is randomized.\n\n" - elif /sbin/sysctl -a 2>/dev/null | grep -q 'kernel.randomize_va_space = 0'; then - echo -n -e '\033[31mOff (Setting: 0)\033[m\n' - else - echo -n -e '\033[31mNot supported\033[m\n' - fi - printf " See the kernel file 'Documentation/sysctl/kernel.txt' for more details.\n\n" - fi -} - -# check cpu nx flag -nxcheck() { - if grep -q nx /proc/cpuinfo; then - echo -n -e '\033[32mYes\033[m\n\n' - else - echo -n -e '\033[31mNo\033[m\n\n' - fi -} - -# check for kernel protection mechanisms -kernelcheck() { - printf " Description - List the status of kernel protection mechanisms. Rather than\n" - printf " inspect kernel mechanisms that may aid in the prevention of exploitation of\n" - printf " userspace processes, this option lists the status of kernel configuration\n" - printf " options that harden the kernel itself against attack.\n\n" - printf " Kernel config: " - - if [ -f /proc/config.gz ] ; then - kconfig="zcat /proc/config.gz" - printf "\033[32m/proc/config.gz\033[m\n\n" - elif [ -f /boot/config-`uname -r` ] ; then - kconfig="cat /boot/config-`uname -r`" - printf "\033[33m/boot/config-`uname -r`\033[m\n\n" - printf " Warning: The config on disk may not represent running kernel config!\n\n"; - elif [ -f "${KBUILD_OUTPUT:-/usr/src/linux}"/.config ] ; then - kconfig="cat ${KBUILD_OUTPUT:-/usr/src/linux}/.config" - printf "\033[33m%s\033[m\n\n" "${KBUILD_OUTPUT:-/usr/src/linux}/.config" - printf " Warning: The config on disk may not represent running kernel config!\n\n"; - else - printf "\033[31mNOT FOUND\033[m\n\n" - exit 0 - fi - - printf " GCC stack protector support: " - if $kconfig | grep -qi 'CONFIG_CC_STACKPROTECTOR=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Strict user copy checks: " - if $kconfig | grep -qi 'CONFIG_DEBUG_STRICT_USER_COPY_CHECKS=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Enforce read-only kernel data: " - if $kconfig | grep -qi 'CONFIG_DEBUG_RODATA=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - printf " Restrict /dev/mem access: " - if $kconfig | grep -qi 'CONFIG_STRICT_DEVMEM=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Restrict /dev/kmem access: " - if $kconfig | grep -qi 'CONFIG_DEVKMEM=y'; then - printf "\033[31mDisabled\033[m\n" - else - printf "\033[32mEnabled\033[m\n" - fi - - printf "\n" - printf "* grsecurity / PaX: " - - if $kconfig | grep -qi 'CONFIG_GRKERNSEC=y'; then - if $kconfig | grep -qi 'CONFIG_GRKERNSEC_HIGH=y'; then - printf "\033[32mHigh GRKERNSEC\033[m\n\n" - elif $kconfig | grep -qi 'CONFIG_GRKERNSEC_MEDIUM=y'; then - printf "\033[33mMedium GRKERNSEC\033[m\n\n" - elif $kconfig | grep -qi 'CONFIG_GRKERNSEC_LOW=y'; then - printf "\033[31mLow GRKERNSEC\033[m\n\n" - else - printf "\033[33mCustom GRKERNSEC\033[m\n\n" - fi - - printf " Non-executable kernel pages: " - if $kconfig | grep -qi 'CONFIG_PAX_KERNEXEC=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Prevent userspace pointer deref: " - if $kconfig | grep -qi 'CONFIG_PAX_MEMORY_UDEREF=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Prevent kobject refcount overflow: " - if $kconfig | grep -qi 'CONFIG_PAX_REFCOUNT=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Bounds check heap object copies: " - if $kconfig | grep -qi 'CONFIG_PAX_USERCOPY=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Disable writing to kmem/mem/port: " - if $kconfig | grep -qi 'CONFIG_GRKERNSEC_KMEM=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Disable privileged I/O: " - if $kconfig | grep -qi 'CONFIG_GRKERNSEC_IO=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Harden module auto-loading: " - if $kconfig | grep -qi 'CONFIG_GRKERNSEC_MODHARDEN=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - - printf " Hide kernel symbols: " - if $kconfig | grep -qi 'CONFIG_GRKERNSEC_HIDESYM=y'; then - printf "\033[32mEnabled\033[m\n" - else - printf "\033[31mDisabled\033[m\n" - fi - else - printf "\033[31mNo GRKERNSEC\033[m\n\n" - printf " The grsecurity / PaX patchset is available here:\n" - printf " http://grsecurity.net/\n" - fi - - printf "\n" - printf "* Kernel Heap Hardening: " - - if $kconfig | grep -qi 'CONFIG_KERNHEAP=y'; then - if $kconfig | grep -qi 'CONFIG_KERNHEAP_FULLPOISON=y'; then - printf "\033[32mFull KERNHEAP\033[m\n\n" - else - printf "\033[33mPartial KERNHEAP\033[m\n\n" - fi - else - printf "\033[31mNo KERNHEAP\033[m\n\n" - printf " The KERNHEAP hardening patchset is available here:\n" - printf " https://www.subreption.com/kernheap/\n\n" - fi -} - -# --- FORTIFY_SOURCE subfunctions (start) --- - -# is FORTIFY_SOURCE supported by libc? -FS_libc_check() { - printf "* FORTIFY_SOURCE support available (libc) : " - - if [ "${#FS_chk_func_libc[@]}" != "0" ] ; then - printf "\033[32mYes\033[m\n" - else - printf "\033[31mNo\033[m\n" - exit 1 - fi -} - -# was the binary compiled with FORTIFY_SOURCE? -FS_binary_check() { - printf "* Binary compiled with FORTIFY_SOURCE support: " - - for FS_elem_functions in $(seq 0 $((${#FS_functions[@]} - 1))) - do - if [[ ${FS_functions[$FS_elem_functions]} =~ _chk ]] ; then - printf "\033[32mYes\033[m\n" - return - fi - done - printf "\033[31mNo\033[m\n" - exit 1 -} - -FS_comparison() { - echo - printf " ------ EXECUTABLE-FILE ------- . -------- LIBC --------\n" - printf " FORTIFY-able library functions | Checked function names\n" - printf " -------------------------------------------------------\n" - - for FS_elem_libc in $(seq 0 $((${#FS_chk_func_libc[@]} - 1))) - do - for FS_elem_functions in $(seq 0 $((${#FS_functions[@]} - 1))) - do - FS_tmp_func=${FS_functions[$FS_elem_functions]} - FS_tmp_libc=${FS_chk_func_libc[$FS_elem_libc]} - - if [[ $FS_tmp_func =~ ^$FS_tmp_libc$ ]] ; then - printf " \033[31m%-30s\033[m | __%s%s\n" $FS_tmp_func $FS_tmp_libc $FS_end - let FS_cnt_total++ - let FS_cnt_unchecked++ - elif [[ $FS_tmp_func =~ ^$FS_tmp_libc(_chk) ]] ; then - printf " \033[32m%-30s\033[m | __%s%s\n" $FS_tmp_func $FS_tmp_libc $FS_end - let FS_cnt_total++ - let FS_cnt_checked++ - fi - - done - done -} - -FS_summary() { - echo - printf "SUMMARY:\n\n" - printf "* Number of checked functions in libc : ${#FS_chk_func_libc[@]}\n" - printf "* Total number of library functions in the executable: ${#FS_functions[@]}\n" - printf "* Number of FORTIFY-able functions in the executable : %s\n" $FS_cnt_total - printf "* Number of checked functions in the executable : \033[32m%s\033[m\n" $FS_cnt_checked - printf "* Number of unchecked functions in the executable : \033[31m%s\033[m\n" $FS_cnt_unchecked - echo -} - -# --- FORTIFY_SOURCE subfunctions (end) --- - -if !(command_exists readelf) ; then - printf "\033[31mWarning: 'readelf' not found! It's required for most checks.\033[m\n\n" - have_readelf=0 -fi - -# parse command-line arguments -case "$1" in - - --version) - version - exit 0 - ;; - - --help) - help - exit 0 - ;; - - --dir) - if [ "$3" = "-v" ] ; then - verbose=true - fi - if [ $have_readelf -eq 0 ] ; then - exit 1 - fi - if [ -z "$2" ] ; then - printf "\033[31mError: Please provide a valid directory.\033[m\n\n" - exit 1 - fi - # remove trailing slashes - tempdir=`echo $2 | sed -e "s/\/*$//"` - if [ ! -d $tempdir ] ; then - printf "\033[31mError: The directory '$tempdir' does not exist.\033[m\n\n" - exit 1 - fi - cd $tempdir - printf "RELRO STACK CANARY NX PIE RPATH RUNPATH FILE\n" - for N in [A-Za-z]*; do - if [ "$N" != "[A-Za-z]*" ]; then - # read permissions? - if [ ! -r $N ]; then - printf "\033[31mError: No read permissions for '$tempdir/$N' (run as root).\033[m\n" - else - # ELF executable? - out=`file $N` - if [[ ! $out =~ ELF ]] ; then - if [ "$verbose" = "true" ] ; then - printf "\033[34m*** Not an ELF file: $tempdir/" - file $N - printf "\033[m" - fi - else - filecheck $N - if [ `find $tempdir/$N \( -perm -004000 -o -perm -002000 \) -type f -print` ]; then - printf "\033[37;41m%s%s\033[m" $2 $N - else - printf "%s%s" $tempdir/ $N - fi - echo - fi - fi - fi - done - exit 0 - ;; - - --file) - if [ $have_readelf -eq 0 ] ; then - exit 1 - fi - if [ -z "$2" ] ; then - printf "\033[31mError: Please provide a valid file.\033[m\n\n" - exit 1 - fi - # does the file exist? - if [ ! -e $2 ] ; then - printf "\033[31mError: The file '$2' does not exist.\033[m\n\n" - exit 1 - fi - # read permissions? - if [ ! -r $2 ] ; then - printf "\033[31mError: No read permissions for '$2' (run as root).\033[m\n\n" - exit 1 - fi - # ELF executable? - out=`file $2` - if [[ ! $out =~ ELF ]] ; then - printf "\033[31mError: Not an ELF file: " - file $2 - printf "\033[m\n" - exit 1 - fi - printf "RELRO STACK CANARY NX PIE RPATH RUNPATH FILE\n" - filecheck $2 - if [ `find $2 \( -perm -004000 -o -perm -002000 \) -type f -print` ] ; then - printf "\033[37;41m%s%s\033[m" $2 $N - else - printf "%s" $2 - fi - echo - exit 0 - ;; - - --proc-all) - if [ $have_readelf -eq 0 ] ; then - exit 1 - fi - cd /proc - printf "* System-wide ASLR" - aslrcheck - printf "* Does the CPU support NX: " - nxcheck - printf " COMMAND PID RELRO STACK CANARY NX/PaX PIE\n" - for N in [1-9]*; do - if [ $N != $$ ] && readlink -q $N/exe > /dev/null; then - printf "%16s" `head -1 $N/status | cut -b 7-` - printf "%7d " $N - proccheck $N - echo - fi - done - if [ ! -e /usr/bin/id ] ; then - printf "\n\033[33mNote: If you are running 'checksec.sh' as an unprivileged user, you\n" - printf " will not see all processes. Please run the script as root.\033[m\n\n" - else - if !(root_privs) ; then - printf "\n\033[33mNote: You are running 'checksec.sh' as an unprivileged user.\n" - printf " Too see all processes, please run the script as root.\033[m\n\n" - fi - fi - exit 0 - ;; - - --proc) - if [ $have_readelf -eq 0 ] ; then - exit 1 - fi - if [ -z "$2" ] ; then - printf "\033[31mError: Please provide a valid process name.\033[m\n\n" - exit 1 - fi - if !(isString "$2") ; then - printf "\033[31mError: Please provide a valid process name.\033[m\n\n" - exit 1 - fi - cd /proc - printf "* System-wide ASLR" - aslrcheck - printf "* Does the CPU support NX: " - nxcheck - printf " COMMAND PID RELRO STACK CANARY NX/PaX PIE\n" - for N in `ps -Ao pid,comm | grep $2 | cut -b1-6`; do - if [ -d $N ] ; then - printf "%16s" `head -1 $N/status | cut -b 7-` - printf "%7d " $N - # read permissions? - if [ ! -r $N/exe ] ; then - if !(root_privs) ; then - printf "\033[31mNo read permissions for '/proc/$N/exe' (run as root).\033[m\n\n" - exit 1 - fi - if [ ! `readlink $N/exe` ] ; then - printf "\033[31mPermission denied. Requested process ID belongs to a kernel thread.\033[m\n\n" - exit 1 - fi - exit 1 - fi - proccheck $N - echo - fi - done - exit 0 - ;; - - --proc-libs) - if [ $have_readelf -eq 0 ] ; then - exit 1 - fi - if [ -z "$2" ] ; then - printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" - exit 1 - fi - if !(isNumeric "$2") ; then - printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" - exit 1 - fi - cd /proc - printf "* System-wide ASLR" - aslrcheck - printf "* Does the CPU support NX: " - nxcheck - printf "* Process information:\n\n" - printf " COMMAND PID RELRO STACK CANARY NX/PaX PIE\n" - N=$2 - if [ -d $N ] ; then - printf "%16s" `head -1 $N/status | cut -b 7-` - printf "%7d " $N - # read permissions? - if [ ! -r $N/exe ] ; then - if !(root_privs) ; then - printf "\033[31mNo read permissions for '/proc/$N/exe' (run as root).\033[m\n\n" - exit 1 - fi - if [ ! `readlink $N/exe` ] ; then - printf "\033[31mPermission denied. Requested process ID belongs to a kernel thread.\033[m\n\n" - exit 1 - fi - exit 1 - fi - proccheck $N - echo - libcheck $N - fi - exit 0 - ;; - - --kernel) - cd /proc - printf "* Kernel protection information:\n\n" - kernelcheck - exit 0 - ;; - - --fortify-file) - if [ $have_readelf -eq 0 ] ; then - exit 1 - fi - if [ -z "$2" ] ; then - printf "\033[31mError: Please provide a valid file.\033[m\n\n" - exit 1 - fi - # does the file exist? - if [ ! -e $2 ] ; then - printf "\033[31mError: The file '$2' does not exist.\033[m\n\n" - exit 1 - fi - # read permissions? - if [ ! -r $2 ] ; then - printf "\033[31mError: No read permissions for '$2' (run as root).\033[m\n\n" - exit 1 - fi - # ELF executable? - out=`file $2` - if [[ ! $out =~ ELF ]] ; then - printf "\033[31mError: Not an ELF file: " - file $2 - printf "\033[m\n" - exit 1 - fi - if [ -e /lib/libc.so.6 ] ; then - FS_libc=/lib/libc.so.6 - elif [ -e /lib64/libc.so.6 ] ; then - FS_libc=/lib64/libc.so.6 - elif [ -e /lib/i386-linux-gnu/libc.so.6 ] ; then - FS_libc=/lib/i386-linux-gnu/libc.so.6 - elif [ -e /lib/x86_64-linux-gnu/libc.so.6 ] ; then - FS_libc=/lib/x86_64-linux-gnu/libc.so.6 - else - printf "\033[31mError: libc not found.\033[m\n\n" - exit 1 - fi - - FS_chk_func_libc=( $(readelf -s $FS_libc | grep _chk@@ | awk '{ print $8 }' | cut -c 3- | sed -e 's/_chk@.*//') ) - FS_functions=( $(readelf -s $2 | awk '{ print $8 }' | sed 's/_*//' | sed -e 's/@.*//') ) - - FS_libc_check - FS_binary_check - FS_comparison - FS_summary - - exit 0 - ;; - - --fortify-proc) - if [ $have_readelf -eq 0 ] ; then - exit 1 - fi - if [ -z "$2" ] ; then - printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" - exit 1 - fi - if !(isNumeric "$2") ; then - printf "\033[31mError: Please provide a valid process ID.\033[m\n\n" - exit 1 - fi - cd /proc - N=$2 - if [ -d $N ] ; then - # read permissions? - if [ ! -r $N/exe ] ; then - if !(root_privs) ; then - printf "\033[31mNo read permissions for '/proc/$N/exe' (run as root).\033[m\n\n" - exit 1 - fi - if [ ! `readlink $N/exe` ] ; then - printf "\033[31mPermission denied. Requested process ID belongs to a kernel thread.\033[m\n\n" - exit 1 - fi - exit 1 - fi - if [ -e /lib/libc.so.6 ] ; then - FS_libc=/lib/libc.so.6 - elif [ -e /lib64/libc.so.6 ] ; then - FS_libc=/lib64/libc.so.6 - elif [ -e /lib/i386-linux-gnu/libc.so.6 ] ; then - FS_libc=/lib/i386-linux-gnu/libc.so.6 - elif [ -e /lib/x86_64-linux-gnu/libc.so.6 ] ; then - FS_libc=/lib/x86_64-linux-gnu/libc.so.6 - else - printf "\033[31mError: libc not found.\033[m\n\n" - exit 1 - fi - printf "* Process name (PID) : %s (%d)\n" `head -1 $N/status | cut -b 7-` $N - FS_chk_func_libc=( $(readelf -s $FS_libc | grep _chk@@ | awk '{ print $8 }' | cut -c 3- | sed -e 's/_chk@.*//') ) - FS_functions=( $(readelf -s $2/exe | awk '{ print $8 }' | sed 's/_*//' | sed -e 's/@.*//') ) - - FS_libc_check - FS_binary_check - FS_comparison - FS_summary - fi - exit 0 - ;; - - *) - if [ "$#" != "0" ] ; then - printf "\033[31mError: Unknown option '$1'.\033[m\n\n" - fi - help - exit 1 - ;; -esac diff --git a/recipes-devtools/checksec/checksec_1.5.bb b/recipes-devtools/checksec/checksec_1.5.bb index 2fe2aba..6b0f375 100644 --- a/recipes-devtools/checksec/checksec_1.5.bb +++ b/recipes-devtools/checksec/checksec_1.5.bb @@ -6,7 +6,10 @@ HOMEPAGE="http://www.trapkit.de/tools/checksec.html" LIC_FILES_CHKSUM = "file://checksec.sh;beginline=3;endline=34;md5=c1bd90129ce3bb5519cfcaea794ab515" -SRC_URI = "file://checksec.sh" +SRC_URI = "http://www.trapkit.de/tools/checksec.sh" + +SRC_URI[md5sum] = "075996be339ab16ad7b94d6de3ee07bd" +SRC_URI[sha256sum] = "77b8a7fd9393d10def665658a41176ee745d5c7969a4a0f43cefcc8a4cd90947" S = "${WORKDIR}" From 4a97ed973867b79d5ab8d7d3a99e1f2f547e1b3f Mon Sep 17 00:00:00 2001 From: Marco Dallagiacoma Date: Mon, 8 Feb 2016 11:22:28 +0200 Subject: [PATCH 13/25] Render dependency graph After the dependency graph has been internally created, generate a .dot file representing the graph and call graphviz to render it. Transitive reduction is applied to the graph definition. --- lib/isafw/isaplugins/ISA_dep_plugin.py | 49 +++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index a474c47..fc17ba3 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -1,4 +1,7 @@ import os +import textwrap +import subprocess + try: import cPickle as pickle except ImportError: @@ -6,6 +9,7 @@ log = '/isafw_deplog' temp_file = '/isafw_dep_tmp' +report_file = '/dep_report' DEPChecker = None @@ -13,10 +17,12 @@ class ISA_DEPChecker: initialized = False def __init__(self, ISA_config): + self.depgraph = {} self.logdir = ISA_config.logdir self.reportdir = ISA_config.reportdir - self.initialized = True + self.timestamp = ISA_config.timestamp + self.initialized = True with open(self.logdir + log, 'a') as flog: flog.write("\nPlugin ISA_DEPChecker initialized!\n") @@ -30,14 +36,13 @@ def process_package(self, ISA_pkg): def process_report(self): tmp = open(self.reportdir + temp_file, 'rb') - depgraph = {} while True: try: pkg, deps = pickle.load(tmp) # update dependencies if node already exists in depgraph, # add new empty node otherwise - node = depgraph.setdefault(pkg, set()) + node = self.depgraph.setdefault(pkg, set()) node.update(deps) except EOFError: @@ -50,9 +55,45 @@ def process_report(self): except OSError: pass + dot_graph = self.generate_dot() + report_path = self.reportdir + report_file + '_' + self.timestamp + '.dot' + with open(report_path, 'w') as f: + f.write(dot_graph) + + # render the graph + rc = subprocess.call(['which', 'dot']) + if rc == 0: + # remove transient redundancy from graph before rendering + ps = subprocess.Popen(('tred', report_path), stdout=subprocess.PIPE) + subprocess.call( + ('dot', '-Tpng', '-o', report_path[:-3] + 'png'), + stdin=ps.stdout + ) + ps.wait() + else: + with open(self.logdir + log, 'a') as flog: + flog.write('Graphviz is missing, the graph will not be rendered.\n') + # put dependency graph in log file. todo: remove me with open(self.logdir + log, 'a') as flog: - flog.write(str(depgraph)) + flog.write(str(self.depgraph)) + + def generate_dot(self): + """ Generate a digraph definition in the DOT language """ + graph_template = textwrap.dedent('''\ + digraph dependency_graph {{ + \tranksep=3; + {edges} + }} + ''') + edge_template = '\t"{a}" -> "{b}";' + + edges = [] + for node, deps in self.depgraph.iteritems(): + for dep in deps: + edges.append(edge_template.format(a=node, b=dep)) + + return graph_template.format(edges='\n'.join(edges)) # ======== supported callbacks from ISA ============= # From d2479e2b434e301639bcc078999458ed7305ed71 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Mon, 22 Feb 2016 10:38:55 +0200 Subject: [PATCH 14/25] Making plugins full reports produced only on request --- classes/isafw.bbclass | 1 - lib/isafw/isafw.py | 1 + lib/isafw/isaplugins/ISA_cfa_plugin.py | 29 +++++++++++--------- lib/isafw/isaplugins/ISA_fsa_plugin.py | 38 ++++++++++++++------------ lib/isafw/isaplugins/ISA_kca_plugin.py | 37 ++++++++++++++----------- 5 files changed, 59 insertions(+), 47 deletions(-) diff --git a/classes/isafw.bbclass b/classes/isafw.bbclass index d3cb494..551f0f4 100644 --- a/classes/isafw.bbclass +++ b/classes/isafw.bbclass @@ -190,7 +190,6 @@ def isafw_init(isafw, d): import re, errno isafw_config = isafw.ISA_config() - isafw_config.proxy = d.getVar('HTTP_PROXY', True) if not isafw_config.proxy : isafw_config.proxy = d.getVar('http_proxy', True) diff --git a/lib/isafw/isafw.py b/lib/isafw/isafw.py index e0a727e..c7e717c 100644 --- a/lib/isafw/isafw.py +++ b/lib/isafw/isafw.py @@ -79,6 +79,7 @@ class ISA_config: reportdir = "" # location of produced reports logdir = "" # location of produced logs timestamp = "" # timestamp of the build provided by build system + full_reports = False # produce full reports for plugins, False by default class ISA: diff --git a/lib/isafw/isaplugins/ISA_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index 1a366b1..79fa198 100644 --- a/lib/isafw/isaplugins/ISA_cfa_plugin.py +++ b/lib/isafw/isaplugins/ISA_cfa_plugin.py @@ -55,6 +55,7 @@ def __init__(self, ISA_config): self.reportdir = ISA_config.reportdir self.logdir = ISA_config.logdir self.timestamp = ISA_config.timestamp + self.full_reports = ISA_config.full_reports # check that checksec is installed rc = subprocess.call(["which", "checksec.sh"]) if rc == 0: @@ -82,9 +83,10 @@ def process_filesystem(self, ISA_filesystem): if (ISA_filesystem.img_name and ISA_filesystem.path_to_fs): with open(self.logdir + log, 'a') as flog: flog.write("\n\nFilesystem path is: " + ISA_filesystem.path_to_fs) - with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as ffull_report: - ffull_report.write("Security-relevant flags for executables for image: " + ISA_filesystem.img_name + '\n') - ffull_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") + if self.full_reports : + with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as ffull_report: + ffull_report.write("Security-relevant flags for executables for image: " + ISA_filesystem.img_name + '\n') + ffull_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") self.files = self.find_files(ISA_filesystem.path_to_fs) with open(self.logdir + log, 'a') as flog: flog.write("\n\nFile list is: " + str(self.files)) @@ -317,16 +319,17 @@ def process_files(self, img_name, path_to_fs): execstack = self.get_execstack(real_file) nodrop_groups = self.get_nodrop_groups(real_file) no_mpx = self.get_mpx(real_file) - with open(self.reportdir + full_report + img_name + "_" + self.timestamp, 'a') as ffull_report: - real_file = real_file.replace(path_to_fs, "") - ffull_report.write(real_file + ": ") - for s in sec_field: - line = ' '.join(str(x) for x in s) - ffull_report.write(line + ' ') - ffull_report.write('\nexecstack: ' + execstack +' ') - ffull_report.write('\nnodrop_groups: ' + nodrop_groups +' ') - ffull_report.write('\nno mpx: ' + no_mpx +' ') - ffull_report.write('\n') + if self.full_reports : + with open(self.reportdir + full_report + img_name + "_" + self.timestamp, 'a') as ffull_report: + real_file = real_file.replace(path_to_fs, "") + ffull_report.write(real_file + ": ") + for s in sec_field: + line = ' '.join(str(x) for x in s) + ffull_report.write(line + ' ') + ffull_report.write('\nexecstack: ' + execstack +' ') + ffull_report.write('\nnodrop_groups: ' + nodrop_groups +' ') + ffull_report.write('\nno mpx: ' + no_mpx +' ') + ffull_report.write('\n') else: continue diff --git a/lib/isafw/isaplugins/ISA_fsa_plugin.py b/lib/isafw/isaplugins/ISA_fsa_plugin.py index 358f158..5c09d19 100644 --- a/lib/isafw/isaplugins/ISA_fsa_plugin.py +++ b/lib/isafw/isaplugins/ISA_fsa_plugin.py @@ -41,6 +41,7 @@ def __init__(self, ISA_config): self.reportdir = ISA_config.reportdir self.logdir = ISA_config.logdir self.timestamp = ISA_config.timestamp + self.full_reports = ISA_config.full_reports self.initialized = True self.setuid_files = [] self.setgid_files = [] @@ -59,23 +60,26 @@ def process_filesystem(self, ISA_filesystem): self.files = self.find_fsobjects(ISA_filesystem.path_to_fs) with open(self.logdir + log, 'a') as flog: flog.write("\nFilelist is: " + str(self.files)) - with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as ffull_report: - ffull_report.write("Report for image: " + ISA_filesystem.img_name + '\n') - ffull_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") - for f in self.files: - st = os.lstat(f) - i = f.replace(ISA_filesystem.path_to_fs, "") - ffull_report.write("File: " + i + ' mode: ' + str(oct(st.st_mode)) + - " uid: " + str(st.st_uid) + " gid: " + str(st.st_gid) + '\n') - if ((st.st_mode&S_ISUID) == S_ISUID): - self.setuid_files.append(i) - if ((st.st_mode&S_ISGID) == S_ISGID): - self.setgid_files.append(i) - if ((st.st_mode&S_IWOTH) == S_IWOTH): - if (((st.st_mode&S_IFDIR) == S_IFDIR) and ((st.st_mode&S_ISVTX) != S_ISVTX)): - self.no_sticky_bit_ww_dirs.append(i) - if (((st.st_mode&S_IFREG) == S_IFREG) and ((st.st_mode&S_IFLNK) != S_IFLNK)): - self.ww_files.append(i) + if self.full_reports : + with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as ffull_report: + ffull_report.write("Report for image: " + ISA_filesystem.img_name + '\n') + ffull_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") + for f in self.files: + st = os.lstat(f) + i = f.replace(ISA_filesystem.path_to_fs, "") + if self.full_reports : + with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'a') as ffull_report: + ffull_report.write("File: " + i + ' mode: ' + str(oct(st.st_mode)) + + " uid: " + str(st.st_uid) + " gid: " + str(st.st_gid) + '\n') + if ((st.st_mode&S_ISUID) == S_ISUID): + self.setuid_files.append(i) + if ((st.st_mode&S_ISGID) == S_ISGID): + self.setgid_files.append(i) + if ((st.st_mode&S_IWOTH) == S_IWOTH): + if (((st.st_mode&S_IFDIR) == S_IFDIR) and ((st.st_mode&S_ISVTX) != S_ISVTX)): + self.no_sticky_bit_ww_dirs.append(i) + if (((st.st_mode&S_IFREG) == S_IFREG) and ((st.st_mode&S_IFLNK) != S_IFLNK)): + self.ww_files.append(i) self.write_problems_report(ISA_filesystem) self.write_problems_report_xml(ISA_filesystem) else: diff --git a/lib/isafw/isaplugins/ISA_kca_plugin.py b/lib/isafw/isaplugins/ISA_kca_plugin.py index 15cb94b..0a597cd 100644 --- a/lib/isafw/isaplugins/ISA_kca_plugin.py +++ b/lib/isafw/isaplugins/ISA_kca_plugin.py @@ -158,6 +158,7 @@ def __init__(self, ISA_config): self.reportdir = ISA_config.reportdir self.logdir = ISA_config.logdir self.timestamp = ISA_config.timestamp + self.full_reports = ISA_config.full_reports self.initialized = True print("Plugin ISA_KernelChecker initialized!") with open(self.logdir + log, 'w') as flog: @@ -188,22 +189,8 @@ def process_kernel(self, ISA_kernel): flog.write("\n\nhardening_kco values: " + str(self.hardening_kco)) flog.write("\n\nkeys_kco values: " + str(self.keys_kco)) flog.write("\n\nsecurity_kco values: " + str(self.security_kco)) - flog.write("\n\nintegrity_kco values: " + str(self.integrity_kco)) - with open(self.reportdir + fullreport + ISA_kernel.img_name + "_" + self.timestamp, 'w') as freport: - freport.write("Report for image: " + ISA_kernel.img_name + '\n') - freport.write("With the kernel conf at: " + ISA_kernel.path_to_config + '\n\n') - freport.write("Hardening options:\n") - for key in sorted(self.hardening_kco): - freport.write(key + ' : ' + str(self.hardening_kco[key]) + '\n') - freport.write("\nKey-related options:\n") - for key in sorted(self.keys_kco): - freport.write(key + ' : ' + str(self.keys_kco[key]) + '\n') - freport.write("\nSecurity options:\n") - for key in sorted(self.security_kco): - freport.write(key + ' : ' + str(self.security_kco[key]) + '\n') - freport.write("\nIntegrity options:\n") - for key in sorted(self.integrity_kco): - freport.write(key + ' : ' + str(self.integrity_kco[key]) + '\n') + flog.write("\n\nintegrity_kco values: " + str(self.integrity_kco)) + self.write_full_report(ISA_kernel) self.write_problems_report(ISA_kernel) else: @@ -215,6 +202,24 @@ def process_kernel(self, ISA_kernel): else: print("Plugin hasn't initialized! Not performing the call.") + def write_full_report(self, ISA_kernel): + if self.full_reports : + with open(self.reportdir + fullreport + ISA_kernel.img_name + "_" + self.timestamp, 'w') as freport: + freport.write("Report for image: " + ISA_kernel.img_name + '\n') + freport.write("With the kernel conf at: " + ISA_kernel.path_to_config + '\n\n') + freport.write("Hardening options:\n") + for key in sorted(self.hardening_kco): + freport.write(key + ' : ' + str(self.hardening_kco[key]) + '\n') + freport.write("\nKey-related options:\n") + for key in sorted(self.keys_kco): + freport.write(key + ' : ' + str(self.keys_kco[key]) + '\n') + freport.write("\nSecurity options:\n") + for key in sorted(self.security_kco): + freport.write(key + ' : ' + str(self.security_kco[key]) + '\n') + freport.write("\nIntegrity options:\n") + for key in sorted(self.integrity_kco): + freport.write(key + ' : ' + str(self.integrity_kco[key]) + '\n') + def write_problems_report(self, ISA_kernel): with open(self.reportdir + problemsreport + ISA_kernel.img_name + "_" + self.timestamp, 'w') as freport: freport.write("Report for image: " + ISA_kernel.img_name + '\n') From c7559f9b4f557c75ad79543a01b13472b481aaab Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Tue, 23 Feb 2016 10:52:21 +0200 Subject: [PATCH 15/25] adding check of range for pkg list --- classes/isafw.bbclass | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/classes/isafw.bbclass b/classes/isafw.bbclass index 551f0f4..c46f041 100644 --- a/classes/isafw.bbclass +++ b/classes/isafw.bbclass @@ -227,7 +227,8 @@ def manifest2pkglist(d): with open(manifest_file, 'r') as finput: for line in finput: items = line.split() - foutput.write(items[0] + " " + items[2] + "\n") + if items and (len(items) >= 3): + foutput.write(items[0] + " " + items[2] + "\n") return pkglist From 75243145e1618f38a643a4b962e87d7d3ee2ff97 Mon Sep 17 00:00:00 2001 From: Elena Reshetova Date: Tue, 23 Feb 2016 13:01:25 +0200 Subject: [PATCH 16/25] Adding machine name to report names, small cleanup --- classes/isafw.bbclass | 1 + lib/isafw/isafw.py | 1 + lib/isafw/isaplugins/ISA_cfa_plugin.py | 33 ++++++++++++-------------- lib/isafw/isaplugins/ISA_cve_plugin.py | 31 ++++++++++++------------ lib/isafw/isaplugins/ISA_fsa_plugin.py | 27 ++++++++++----------- lib/isafw/isaplugins/ISA_kca_plugin.py | 23 ++++++++---------- lib/isafw/isaplugins/ISA_la_plugin.py | 28 ++++++++++------------ 7 files changed, 67 insertions(+), 77 deletions(-) diff --git a/classes/isafw.bbclass b/classes/isafw.bbclass index c46f041..73352f8 100644 --- a/classes/isafw.bbclass +++ b/classes/isafw.bbclass @@ -195,6 +195,7 @@ def isafw_init(isafw, d): isafw_config.proxy = d.getVar('http_proxy', True) bb.debug(1, 'isafw: proxy is %s' % isafw_config.proxy) + isafw_config.machine = d.getVar('MACHINE', True) isafw_config.timestamp = d.getVar('DATETIME', True) isafw_config.reportdir = d.getVar('ISAFW_REPORTDIR', True) + "_" + isafw_config.timestamp if not os.path.exists(os.path.dirname(isafw_config.reportdir + "/test")): diff --git a/lib/isafw/isafw.py b/lib/isafw/isafw.py index c7e717c..22bd5ac 100644 --- a/lib/isafw/isafw.py +++ b/lib/isafw/isafw.py @@ -80,6 +80,7 @@ class ISA_config: logdir = "" # location of produced logs timestamp = "" # timestamp of the build provided by build system full_reports = False # produce full reports for plugins, False by default + machine = "" # name of machine build is produced for class ISA: diff --git a/lib/isafw/isaplugins/ISA_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index 79fa198..7d5ec11 100644 --- a/lib/isafw/isaplugins/ISA_cfa_plugin.py +++ b/lib/isafw/isaplugins/ISA_cfa_plugin.py @@ -35,9 +35,6 @@ from lxml import etree CFChecker = None -full_report = "/cfa_full_report_" -problems_report = "/cfa_problems_report_" -log = "/isafw_cfalog" class ISA_CFChecker(): initialized = False @@ -52,9 +49,9 @@ class ISA_CFChecker(): def __init__(self, ISA_config): self.proxy = ISA_config.proxy - self.reportdir = ISA_config.reportdir - self.logdir = ISA_config.logdir - self.timestamp = ISA_config.timestamp + self.logfile = ISA_config.logdir + "/isafw_cfalog" + self.full_report_name = ISA_config.reportdir + "/cfa_full_report_" + ISA_config.machine + "_" + ISA_config.timestamp + self.problems_report_name = ISA_config.reportdir + "/cfa_problems_report_" + ISA_config.machine + "_" + ISA_config.timestamp self.full_reports = ISA_config.full_reports # check that checksec is installed rc = subprocess.call(["which", "checksec.sh"]) @@ -67,13 +64,13 @@ def __init__(self, ISA_config): if rc == 0: self.initialized = True print("Plugin ISA_CFChecker initialized!") - with open(self.logdir + log, 'w') as flog: + with open(self.logfile, 'w') as flog: flog.write("\nPlugin ISA_CFChecker initialized!\n") return print("checksec, execstack or readelf tools are missing!") print("Please install checksec from http://www.trapkit.de/tools/checksec.html") print("Please install execstack from prelink package") - with open(self.logdir + log, 'w') as flog: + with open(self.logfile, 'w') as flog: flog.write("checksec, execstack or readelf tools are missing!\n") flog.write("Please install checksec from http://www.trapkit.de/tools/checksec.html\n") flog.write("Please install execstack from prelink package\n") @@ -81,14 +78,14 @@ def __init__(self, ISA_config): def process_filesystem(self, ISA_filesystem): if (self.initialized == True): if (ISA_filesystem.img_name and ISA_filesystem.path_to_fs): - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("\n\nFilesystem path is: " + ISA_filesystem.path_to_fs) if self.full_reports : - with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as ffull_report: + with open(self.full_report_name + "_" + ISA_filesystem.img_name, 'w') as ffull_report: ffull_report.write("Security-relevant flags for executables for image: " + ISA_filesystem.img_name + '\n') ffull_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") self.files = self.find_files(ISA_filesystem.path_to_fs) - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("\n\nFile list is: " + str(self.files)) self.process_files(ISA_filesystem.img_name, ISA_filesystem.path_to_fs) self.write_report(ISA_filesystem) @@ -96,16 +93,16 @@ def process_filesystem(self, ISA_filesystem): else: print("Mandatory arguments such as image name and path to the filesystem are not provided!") print("Not performing the call.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Mandatory arguments such as image name and path to the filesystem are not provided!\n") flog.write("Not performing the call.\n") else: print("Plugin hasn't initialized! Not performing the call.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Plugin hasn't initialized! Not performing the call.\n") def write_report(self, ISA_filesystem): - with open(self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as fproblems_report: + with open(self.problems_report_name + "_" + ISA_filesystem.img_name, 'w') as fproblems_report: fproblems_report.write("Report for image: " + ISA_filesystem.img_name + '\n') fproblems_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") fproblems_report.write("Files with no RELO:\n") @@ -185,7 +182,7 @@ def write_report_xml(self, ISA_filesystem): tcase8 = etree.SubElement(root, 'testcase', classname='files_with_no_mpx', name=item) etree.SubElement(tcase8, 'failure', message=item, type='violation') tree = etree.ElementTree(root) - output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' + output = self.problems_report_name + "_" + ISA_filesystem.img_name + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) def find_files(self, init_path): @@ -282,7 +279,7 @@ def process_files(self, img_name, path_to_fs): result = subprocess.check_output(cmd).decode("utf-8") except: print("Not able to decode mime type", sys.exc_info()) - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Not able to decode mime type" + sys.exc_info()) continue type = result.split()[-1] @@ -294,7 +291,7 @@ def process_files(self, img_name, path_to_fs): result = subprocess.check_output(cmd).decode("utf-8") except: print("Not able to decode mime type", sys.exc_info()) - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Not able to decode mime type" + sys.exc_info()) continue type = result.split()[-1] @@ -320,7 +317,7 @@ def process_files(self, img_name, path_to_fs): nodrop_groups = self.get_nodrop_groups(real_file) no_mpx = self.get_mpx(real_file) if self.full_reports : - with open(self.reportdir + full_report + img_name + "_" + self.timestamp, 'a') as ffull_report: + with open(self.full_report_name + "_" + img_name, 'a') as ffull_report: real_file = real_file.replace(path_to_fs, "") ffull_report.write(real_file + ": ") for s in sec_field: diff --git a/lib/isafw/isaplugins/ISA_cve_plugin.py b/lib/isafw/isaplugins/ISA_cve_plugin.py index 655f2d1..74c341b 100644 --- a/lib/isafw/isaplugins/ISA_cve_plugin.py +++ b/lib/isafw/isaplugins/ISA_cve_plugin.py @@ -32,28 +32,27 @@ import tempfile CVEChecker = None -cve_report = "/cve-report" pkglist = "/cve_check_tool_pkglist" -log = "/isafw_cvelog" class ISA_CVEChecker: initialized = False def __init__(self, ISA_config): self.proxy = ISA_config.proxy self.reportdir = ISA_config.reportdir - self.logdir = ISA_config.logdir self.timestamp = ISA_config.timestamp + self.logfile = ISA_config.logdir + "/isafw_cvelog" + self.report_name = ISA_config.reportdir + "/cve_report_" + ISA_config.machine + "_" + ISA_config.timestamp # check that cve-check-tool is installed rc = subprocess.call(["which", "cve-check-tool"]) if rc == 0: self.initialized = True print("Plugin ISA_CVEChecker initialized!") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("\nPlugin ISA_CVEChecker initialized!\n") else: print("cve-check-tool is missing!") print("Please install it from https://github.com/ikeydoherty/cve-check-tool.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("cve-check-tool is missing!\n") flog.write("Please install it from https://github.com/ikeydoherty/cve-check-tool.\n") @@ -73,29 +72,29 @@ def process_package(self, ISA_pkg): for a in alias_pkgs_faux: fauxfile.write(a) - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("\npkg info: " + pkgline_faux) else: print("Mandatory arguments such as pkg name, version and list of patches are not provided!") print("Not performing the call.") self.initialized = False - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Mandatory arguments such as pkg name, version and list of patches are not provided!\n") flog.write("Not performing the call.\n") else: print("Plugin hasn't initialized! Not performing the call.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Plugin hasn't initialized! Not performing the call.\n") def process_report(self): if (self.initialized == True): print("Creating report in HTML format.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Creating report in HTML format.\n") self.process_report_type("html") print("Creating report in CSV format.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Creating report in CSV format.\n") self.process_report_type("csv") @@ -103,7 +102,7 @@ def process_report(self): os.remove(self.reportdir + pkglist_faux) print("Creating report in XML format.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Creating report in XML format.\n") self.write_report_xml() @@ -111,7 +110,7 @@ def write_report_xml(self): from lxml import etree numTests = 0 root = etree.Element('testsuite', name='CVE_Plugin', tests='1') - with open(self.reportdir + cve_report + "_" + self.timestamp + ".csv", 'r') as f: + with open(self.report_name + ".csv", 'r') as f: for line in f: numTests += 1 line = line.strip() @@ -122,7 +121,7 @@ def write_report_xml(self): tcase = etree.SubElement(root, 'testcase', classname='ISA_CVEChecker', name=line.split(',',1)[0]) root.set('tests', str(numTests)) tree = etree.ElementTree(root) - output = self.reportdir + cve_report + "_" + self.timestamp + '.xml' + output = self.report_name + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) def process_report_type(self, rtype): @@ -143,11 +142,11 @@ def process_report_type(self, rtype): except: print("Error in executing cve-check-tool: ", sys.exc_info()) output = "Error in executing cve-check-tool" - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Error in executing cve-check-tool: " + sys.exc_info()) else: - report = cve_report + "_" + self.timestamp + "." + rtype - with open(self.reportdir + report, 'w') as freport: + report = self.report_name + "." + rtype + with open(report, 'w') as freport: freport.write(output) def process_patch_list(self, patch_files): diff --git a/lib/isafw/isaplugins/ISA_fsa_plugin.py b/lib/isafw/isaplugins/ISA_fsa_plugin.py index 5c09d19..620773e 100644 --- a/lib/isafw/isaplugins/ISA_fsa_plugin.py +++ b/lib/isafw/isaplugins/ISA_fsa_plugin.py @@ -30,17 +30,14 @@ from lxml import etree FSAnalyzer = None -full_report = "/fsa_full_report_" -problems_report = "/fsa_problems_report_" -log = "/isafw_fsalog" class ISA_FSChecker(): initialized = False def __init__(self, ISA_config): self.proxy = ISA_config.proxy - self.reportdir = ISA_config.reportdir - self.logdir = ISA_config.logdir - self.timestamp = ISA_config.timestamp + self.logfile = ISA_config.logdir + "/isafw_fsalog" + self.full_report_name = ISA_config.reportdir + "/fsa_full_report_" + ISA_config.machine + "_" + ISA_config.timestamp + self.problems_report_name = ISA_config.reportdir + "/fsa_problems_report_" + ISA_config.machine + "_" + ISA_config.timestamp self.full_reports = ISA_config.full_reports self.initialized = True self.setuid_files = [] @@ -48,27 +45,27 @@ def __init__(self, ISA_config): self.ww_files = [] self.no_sticky_bit_ww_dirs = [] print("Plugin ISA_FSChecker initialized!") - with open(self.logdir + log, 'w') as flog: + with open(self.logfile, 'w') as flog: flog.write("\nPlugin ISA_FSChecker initialized!\n") def process_filesystem(self, ISA_filesystem): if (self.initialized == True): if (ISA_filesystem.img_name and ISA_filesystem.path_to_fs): - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Analyzing filesystem at: " + ISA_filesystem.path_to_fs + " for the image: " + ISA_filesystem.img_name + "\n") self.files = self.find_fsobjects(ISA_filesystem.path_to_fs) - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("\nFilelist is: " + str(self.files)) if self.full_reports : - with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as ffull_report: + with open(self.full_report_name + "_" + ISA_filesystem.img_name, 'w') as ffull_report: ffull_report.write("Report for image: " + ISA_filesystem.img_name + '\n') ffull_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") for f in self.files: st = os.lstat(f) i = f.replace(ISA_filesystem.path_to_fs, "") if self.full_reports : - with open(self.reportdir + full_report + ISA_filesystem.img_name + "_" + self.timestamp, 'a') as ffull_report: + with open(self.full_report_name + "_" + ISA_filesystem.img_name, 'a') as ffull_report: ffull_report.write("File: " + i + ' mode: ' + str(oct(st.st_mode)) + " uid: " + str(st.st_uid) + " gid: " + str(st.st_gid) + '\n') if ((st.st_mode&S_ISUID) == S_ISUID): @@ -85,16 +82,16 @@ def process_filesystem(self, ISA_filesystem): else: print("Mandatory arguments such as image name and path to the filesystem are not provided!") print("Not performing the call.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Mandatory arguments such as image name and path to the filesystem are not provided!\n") flog.write("Not performing the call.\n") else: print("Plugin hasn't initialized! Not performing the call.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Plugin hasn't initialized! Not performing the call.\n") def write_problems_report(self, ISA_filesystem): - with open(self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp, 'w') as fproblems_report: + with open(self.problems_report_name + "_" + ISA_filesystem.img_name, 'w') as fproblems_report: fproblems_report.write("Report for image: " + ISA_filesystem.img_name + '\n') fproblems_report.write("With rootfs location at " + ISA_filesystem.path_to_fs + "\n\n") fproblems_report.write("Files with SETUID bit set:\n") @@ -130,7 +127,7 @@ def write_problems_report_xml(self, ISA_filesystem): tcase4 = etree.SubElement(root, 'testcase', classname = 'World-writable_dirs_with_no_sticky_bit', name = item) failrs4 = etree.SubElement(tcase4, 'failure', message = item, type = 'violation') tree = etree.ElementTree(root) - output = self.reportdir + problems_report + ISA_filesystem.img_name + "_" + self.timestamp + '.xml' + output = self.problems_report_name + "_" + ISA_filesystem.img_name + '.xml' tree.write(output, encoding = 'UTF-8', pretty_print = True, xml_declaration = True) def find_fsobjects(self, init_path): diff --git a/lib/isafw/isaplugins/ISA_kca_plugin.py b/lib/isafw/isaplugins/ISA_kca_plugin.py index 0a597cd..cd50884 100644 --- a/lib/isafw/isaplugins/ISA_kca_plugin.py +++ b/lib/isafw/isaplugins/ISA_kca_plugin.py @@ -29,9 +29,6 @@ from lxml import etree KCAnalyzer = None -fullreport = "/kca_full_report_" -problemsreport = "/kca_problems_report_" -log = "/isafw_kcalog" class ISA_KernelChecker(): initialized = False @@ -155,19 +152,19 @@ class ISA_KernelChecker(): def __init__(self, ISA_config): self.proxy = ISA_config.proxy - self.reportdir = ISA_config.reportdir - self.logdir = ISA_config.logdir - self.timestamp = ISA_config.timestamp + self.logfile = ISA_config.logdir + "/isafw_kcalog" + self.full_report_name = ISA_config.reportdir + "/kca_full_report_" + ISA_config.machine + "_" + ISA_config.timestamp + self.problems_report_name = ISA_config.reportdir + "/kca_problems_report_" + ISA_config.machine + "_" + ISA_config.timestamp self.full_reports = ISA_config.full_reports self.initialized = True print("Plugin ISA_KernelChecker initialized!") - with open(self.logdir + log, 'w') as flog: + with open(self.logfile, 'w') as flog: flog.write("\nPlugin ISA_KernelChecker initialized!\n") def process_kernel(self, ISA_kernel): if (self.initialized == True): if (ISA_kernel.img_name and ISA_kernel.path_to_config): - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Analyzing kernel config file at: " + ISA_kernel.path_to_config + " for the image: " + ISA_kernel.img_name + "\n") with open(ISA_kernel.path_to_config, 'r') as fkernel_conf: @@ -185,7 +182,7 @@ def process_kernel(self, ISA_kernel): for key in self.integrity_kco: if key +'=' in line: self.integrity_kco[key] = line.split('=')[1] - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("\n\nhardening_kco values: " + str(self.hardening_kco)) flog.write("\n\nkeys_kco values: " + str(self.keys_kco)) flog.write("\n\nsecurity_kco values: " + str(self.security_kco)) @@ -196,7 +193,7 @@ def process_kernel(self, ISA_kernel): else: print("Mandatory arguments such as image name and path to config are not provided!") print("Not performing the call.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Mandatory arguments such as image name and path to config are not provided!\n") flog.write("Not performing the call.\n") else: @@ -204,7 +201,7 @@ def process_kernel(self, ISA_kernel): def write_full_report(self, ISA_kernel): if self.full_reports : - with open(self.reportdir + fullreport + ISA_kernel.img_name + "_" + self.timestamp, 'w') as freport: + with open(self.full_report_name + "_" + ISA_kernel.img_name, 'w') as freport: freport.write("Report for image: " + ISA_kernel.img_name + '\n') freport.write("With the kernel conf at: " + ISA_kernel.path_to_config + '\n\n') freport.write("Hardening options:\n") @@ -221,7 +218,7 @@ def write_full_report(self, ISA_kernel): freport.write(key + ' : ' + str(self.integrity_kco[key]) + '\n') def write_problems_report(self, ISA_kernel): - with open(self.reportdir + problemsreport + ISA_kernel.img_name + "_" + self.timestamp, 'w') as freport: + with open(self.problems_report_name + "_" + ISA_kernel.img_name, 'w') as freport: freport.write("Report for image: " + ISA_kernel.img_name + '\n') freport.write("With the kernel conf at: " + ISA_kernel.path_to_config + '\n\n') freport.write("Hardening options that need improvement:\n") @@ -350,7 +347,7 @@ def write_problems_report(self, ISA_kernel): msg4 = 'current=' + key + ' is ' + str(self.integrity_kco[key]) + ', recommended=' + key + ' is ' + str(self.integrity_kco_ref[key]) failrs4 = etree.SubElement(tcase4, 'failure', message = msg4, type='violation') tree = etree.ElementTree(root) - output = self.reportdir + problemsreport + ISA_kernel.img_name + "_" + self.timestamp + '.xml' + output = self.problems_report_name + "_" + ISA_kernel.img_name + '.xml' tree.write(output, encoding = 'UTF-8', pretty_print = True, xml_declaration = True) #======== supported callbacks from ISA =============# diff --git a/lib/isafw/isaplugins/ISA_la_plugin.py b/lib/isafw/isaplugins/ISA_la_plugin.py index 1b88ad3..70d3061 100644 --- a/lib/isafw/isaplugins/ISA_la_plugin.py +++ b/lib/isafw/isaplugins/ISA_la_plugin.py @@ -36,26 +36,24 @@ flicenses = "/configs/la/licenses" fapproved_non_osi = "/configs/la/approved-non-osi" fexceptions = "/configs/la/exceptions" -log = "/isafw_lalog" class ISA_LicenseChecker(): initialized = False def __init__(self, ISA_config): self.proxy = ISA_config.proxy - self.reportdir = ISA_config.reportdir - self.logdir = ISA_config.logdir - self.timestamp = ISA_config.timestamp + self.logfile = ISA_config.logdir + "/isafw_lalog" + self.report_name = ISA_config.reportdir + "/la_problems_report_" + ISA_config.machine + "_"+ ISA_config.timestamp # check that rpm is installed (supporting only rpm packages for now) rc = subprocess.call(["which", "rpm"]) if rc == 0: self.initialized = True print("Plugin ISA_LicenseChecker initialized!") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("\nPlugin ISA_LA initialized!\n") else: print("rpm tool is missing!") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("rpm tool is missing!\n") def process_package(self, ISA_pkg): @@ -68,7 +66,7 @@ def process_package(self, ISA_pkg): print("No path to sources or source file list is provided!") print("Not able to determine licenses for package: ", ISA_pkg.name) self.initialized = False - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("No path to sources or source file list is provided!") flog.write("\nNot able to determine licenses for package: " + ISA_pkg.name) return @@ -85,7 +83,7 @@ def process_package(self, ISA_pkg): print("Error in executing rpm query: ", sys.exc_info()) print("Not able to process package: ", ISA_pkg.name) self.initialized = False - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Error in executing rpm query: " + sys.exc_info()) flog.write("\nNot able to process package: " + ISA_pkg.name) return @@ -94,24 +92,24 @@ def process_package(self, ISA_pkg): and not self.check_license(l, fapproved_non_osi) and not self.check_exceptions(ISA_pkg.name, l, fexceptions)): # log the package as not following correct license - with open(self.reportdir + "/la_problems_report_" + self.timestamp, 'a') as freport: + with open(self.report_name, 'a') as freport: freport.write(ISA_pkg.name + ": " + l + "\n") else: print("Mandatory argument package name is not provided!") print("Not performing the call.") self.initialized = False - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Mandatory argument package name is not provided!\n") flog.write("Not performing the call.\n") else: print("Plugin hasn't initialized! Not performing the call.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Plugin hasn't initialized! Not performing the call.") def process_report(self): if (self.initialized == True): print("Creating report in XML format.") - with open(self.logdir + log, 'a') as flog: + with open(self.logfile, 'a') as flog: flog.write("Creating report in XML format.\n") self.write_report_xml() @@ -119,8 +117,8 @@ def write_report_xml(self): from lxml import etree numTests = 0 root = etree.Element('testsuite', name='LA_Plugin', tests='1') - if os.path.isfile (self.reportdir + "/la_problems_report_" + self.timestamp): - with open(self.reportdir + "/la_problems_report_" + self.timestamp, 'r') as f: + if os.path.isfile (self.report_name): + with open(self.report_name, 'r') as f: for line in f: numTests += 1 line = line.strip() @@ -131,7 +129,7 @@ def write_report_xml(self): numTests = 1 root.set('tests', str(numTests)) tree = etree.ElementTree(root) - output = self.reportdir + "/la_problems_report_" + self.timestamp + '.xml' + output = self.report_name + '.xml' tree.write(output, encoding= 'UTF-8', pretty_print=True, xml_declaration=True) From d492e105a5cd16e1a6991f477ff6ac280d17a883 Mon Sep 17 00:00:00 2001 From: Marco Dallagiacoma Date: Fri, 26 Feb 2016 12:01:19 +0200 Subject: [PATCH 17/25] Generate run-time dependency graph Dependencies are now saved as an object , as run-time dependencies include details like package version. Dependencies are filtered based on DEPENDENCIES_BLACKLIST. A class has been used instead of a namedtuple because openembedded monkey-patches the namedtuple module with an alternative one which is not pickle-able. --- lib/isafw/isaplugins/ISA_dep_plugin.py | 86 ++++++++++++++++++++------ 1 file changed, 67 insertions(+), 19 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index fc17ba3..71cec26 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -1,7 +1,7 @@ import os import textwrap import subprocess - +import re try: import cPickle as pickle except ImportError: @@ -12,12 +12,21 @@ report_file = '/dep_report' DEPChecker = None +DEPENDENCIES_BLACKLIST = ['.*-dbg$', '.*-locale$', '.*-doc$'] + + +class Dependency(object): + def __init__(self, pkg_name, details): + self.pkg_name = pkg_name + self.details = details + class ISA_DEPChecker: initialized = False def __init__(self, ISA_config): - self.depgraph = {} + self.b_depgraph = {} # build-time dependency graph + self.r_depgraph = {} # run-time dependency graph self.logdir = ISA_config.logdir self.reportdir = ISA_config.reportdir self.timestamp = ISA_config.timestamp @@ -26,23 +35,61 @@ def __init__(self, ISA_config): with open(self.logdir + log, 'a') as flog: flog.write("\nPlugin ISA_DEPChecker initialized!\n") + def _filter_deps(self, deps): + is_valid = lambda dep: not any( + re.match(regex, dep.pkg_name) for regex in DEPENDENCIES_BLACKLIST + ) + return filter(is_valid, deps) + + def _parse_rdeps(self, rdeps): + if ':' not in rdeps: + return rdeps, [] + + pkg, dependencies = rdeps.split(':', 1) + + if not dependencies.strip(): + return pkg, [] + + regex = r'((?P[^\(/)\s]+)\s*(\((?P
[^)]*)\))?)' + + return pkg, [ + Dependency(dep.group('name'), dep.group('details')) + for dep in re.finditer(regex, dependencies) + if dep.group('name').strip().lower() != 'none' + ] + def process_package(self, ISA_pkg): - b_deps = ISA_pkg.b_deps + b_deps = [Dependency(pkg, '') for pkg in ISA_pkg.b_deps] + + r_deps = dict() + for deps in ISA_pkg.r_deps: + pkg, dependencies = self._parse_rdeps(deps) + r_deps.setdefault(pkg, set()).update(dependencies) + + b_filtered = self._filter_deps(b_deps) + r_filtered = {} + for pkg in r_deps: + r_filtered[pkg] = self._filter_deps(r_deps[pkg]) with open(self.reportdir + temp_file, 'a+b') as tmp: - obj = (ISA_pkg.name, b_deps) - pickle.dump(obj, tmp) + b_obj = ('b', ISA_pkg.name, b_filtered) + pickle.dump(b_obj, tmp) + + for pkg in r_filtered: + r_obj = ('r', pkg, r_filtered[pkg]) + pickle.dump(r_obj, tmp) def process_report(self): tmp = open(self.reportdir + temp_file, 'rb') while True: try: - pkg, deps = pickle.load(tmp) + dep_type, pkg, deps = pickle.load(tmp) # update dependencies if node already exists in depgraph, # add new empty node otherwise - node = self.depgraph.setdefault(pkg, set()) + depgraph = self.b_depgraph if dep_type == 'b' else self.r_depgraph + node = depgraph.setdefault(pkg, set()) node.update(deps) except EOFError: @@ -55,8 +102,13 @@ def process_report(self): except OSError: pass - dot_graph = self.generate_dot() - report_path = self.reportdir + report_file + '_' + self.timestamp + '.dot' + self.generate_graph(self.b_depgraph, 'build_time') + self.generate_graph(self.r_depgraph, 'run_time') + + def generate_graph(self, dep_graph, deps_type): + report_path = self.reportdir + report_file + '_' + deps_type + '_' + self.timestamp + '.dot' + + dot_graph = self.generate_dot(dep_graph) with open(report_path, 'w') as f: f.write(dot_graph) @@ -66,19 +118,15 @@ def process_report(self): # remove transient redundancy from graph before rendering ps = subprocess.Popen(('tred', report_path), stdout=subprocess.PIPE) subprocess.call( - ('dot', '-Tpng', '-o', report_path[:-3] + 'png'), - stdin=ps.stdout + ('dot', '-Tpng', '-o', report_path[:-3] + 'png'), + stdin=ps.stdout ) ps.wait() else: with open(self.logdir + log, 'a') as flog: - flog.write('Graphviz is missing, the graph will not be rendered.\n') - - # put dependency graph in log file. todo: remove me - with open(self.logdir + log, 'a') as flog: - flog.write(str(self.depgraph)) + flog.write('Graphviz is missing, the graphs will not be rendered.\n') - def generate_dot(self): + def generate_dot(self, digraph): """ Generate a digraph definition in the DOT language """ graph_template = textwrap.dedent('''\ digraph dependency_graph {{ @@ -89,9 +137,9 @@ def generate_dot(self): edge_template = '\t"{a}" -> "{b}";' edges = [] - for node, deps in self.depgraph.iteritems(): + for node, deps in digraph.iteritems(): for dep in deps: - edges.append(edge_template.format(a=node, b=dep)) + edges.append(edge_template.format(a=node, b=dep.pkg_name)) return graph_template.format(edges='\n'.join(edges)) From 4dc61015480b1590f8c20063860cdba99808efd1 Mon Sep 17 00:00:00 2001 From: Marco Dalla G Date: Tue, 1 Mar 2016 15:41:58 +0200 Subject: [PATCH 18/25] Save graphs as svg instead of png PNG graphs were often unreadable, especially in very large graphs. While SVG files are not as convenient and easy to open, they are more suitable for this purpose. --- lib/isafw/isaplugins/ISA_dep_plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index 71cec26..eeaa01a 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -118,7 +118,7 @@ def generate_graph(self, dep_graph, deps_type): # remove transient redundancy from graph before rendering ps = subprocess.Popen(('tred', report_path), stdout=subprocess.PIPE) subprocess.call( - ('dot', '-Tpng', '-o', report_path[:-3] + 'png'), + ('dot', '-Tsvg', '-o', report_path[:-3] + 'svg'), stdin=ps.stdout ) ps.wait() From 9361a7a5f88f62e165afd92c68c56c5a9c9d9dd7 Mon Sep 17 00:00:00 2001 From: Marco Dalla G Date: Tue, 1 Mar 2016 15:48:01 +0200 Subject: [PATCH 19/25] Add `repr` and `str` methods on `Dependency` class. Those methods are useful when debugging. --- lib/isafw/isaplugins/ISA_dep_plugin.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index eeaa01a..c866779 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -20,6 +20,12 @@ def __init__(self, pkg_name, details): self.pkg_name = pkg_name self.details = details + def __str__(self): + return '{0} ({1})'.format(self.pkg_name, self.details) + + def __repr__(self): + return self.__str__() + class ISA_DEPChecker: initialized = False From b6ed40317f7d577dfca06ba6d87b69f6679250fb Mon Sep 17 00:00:00 2001 From: Marco Dalla G Date: Wed, 23 Mar 2016 17:11:57 +0200 Subject: [PATCH 20/25] Move graphs loading from temp into dedicated method --- lib/isafw/isaplugins/ISA_dep_plugin.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index c866779..9dcd1a9 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -85,7 +85,8 @@ def process_package(self, ISA_pkg): r_obj = ('r', pkg, r_filtered[pkg]) pickle.dump(r_obj, tmp) - def process_report(self): + def load_dep_graphs(self, remove_temp=False): + """ Fill self.b_depgraph and self.r_depgraph """ tmp = open(self.reportdir + temp_file, 'rb') while True: @@ -103,10 +104,14 @@ def process_report(self): tmp.close() - try: # remove temp file - os.remove(self.reportdir + temp_file) - except OSError: - pass + if remove_temp: + try: + os.remove(self.reportdir + temp_file) + except OSError: + pass + + def process_report(self): + self.load_dep_graphs() self.generate_graph(self.b_depgraph, 'build_time') self.generate_graph(self.r_depgraph, 'run_time') From 7766ed250688232c31c75d991a96936309135080 Mon Sep 17 00:00:00 2001 From: Marco Dalla G Date: Wed, 23 Mar 2016 18:34:44 +0200 Subject: [PATCH 21/25] Generate partial dependency graphs for vulnerability reports Add the possibility to generate partial dependency graphs. Dependency graphs are now color-coded: vulnerable packages are represented by red nodes, while packages that depend on vulnerable packages are represented by yellow nodes. Edges in dependency graphs now contain info (e.g., pkg version) when those are provided. --- lib/isafw/isaplugins/ISA_dep_plugin.py | 142 +++++++++++++++++++++---- 1 file changed, 122 insertions(+), 20 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index 9dcd1a9..b91c3e7 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -113,13 +113,102 @@ def load_dep_graphs(self, remove_temp=False): def process_report(self): self.load_dep_graphs() - self.generate_graph(self.b_depgraph, 'build_time') - self.generate_graph(self.r_depgraph, 'run_time') + self._generate_graph(self.b_depgraph, 'build_time') + self._generate_graph(self.r_depgraph, 'run_time') - def generate_graph(self, dep_graph, deps_type): - report_path = self.reportdir + report_file + '_' + deps_type + '_' + self.timestamp + '.dot' + def generate_dot(self, digraph, vulnerable_packages, vuln_dependent_packages=set()): + """ Generate a digraph definition in the DOT language """ + + def _edge(a, b=None, label=None): + """ Generate the DOT definition of a edge from `a` to `b`. + If only `a` is provided, a standalone node with no links will be added to the graph. + Vulnerable packages are red, while packages that depend on vuln. packages are yellow. + """ + edge_template = '\t"{a}" -> "{b}" {opts};' + standalone_node_template = '\t"{node}" {opts};' # node with no dependencies + + if not b: + if a in vulnerable_packages: + opts = ' [style=filled,fillcolor=red]' + elif a in vuln_dependent_packages: + opts = ' [style=filled,fillcolor=yellow]' + else: + opts = '' + + return standalone_node_template.format(node=a, opts=opts) + + opts = [] + if b in vuln_dependent_packages or b in vulnerable_packages: + opts.append('color=red') + if label: + opts.append('label="' + label + '"') + + return edge_template.format( + a=a, b=b, + opts='[' + ','.join(opts) + ']' if opts else '' + ) + + graph_template = textwrap.dedent('''\ + digraph dependency_graph {{ + \tranksep=3; + {edges} + }} + ''') + + edges = [] + for node in vuln_dependent_packages: + edges.append(_edge(node)) + for node in vulnerable_packages: + edges.append(_edge(node)) + + for node, deps in digraph.iteritems(): + for dep in deps: + edges.append(_edge(a=node, b=dep.pkg_name, label=dep.details)) + if not deps: + edges.append(_edge(node)) + + return graph_template.format(edges='\n'.join(edges)) + + def invert_graph(self, depgraph): + """ Build the inverse dependency graph. + Dependencies details are not kept; only package names are considered. + """ + inv_depgraph = {} + for pkg, deps in depgraph.iteritems(): + for dep in deps: + node = inv_depgraph.setdefault(dep.pkg_name, list()) + node.append(pkg) + if pkg not in inv_depgraph: + inv_depgraph[pkg] = [] + return inv_depgraph + + def get_dependent_pkgs(self, packages_name, depgraph): + """ Given a package, return a list of the packages that depend on it. """ + if type(packages_name) not in (list, tuple): + packages_name = [packages_name] + + dependent_packages = set() + visited = set() + + inv_depgraph = self.invert_graph(depgraph) - dot_graph = self.generate_dot(dep_graph) + def dfs(node): + visited.add(node) + dependent_packages.update(inv_depgraph[node]) + for n in inv_depgraph[node]: + if n not in visited: + dfs(n) + + for pkg_name in packages_name: + dfs(pkg_name) + + return dependent_packages + + def _generate_graph(self, dep_graph, name, vulnerable_packages=tuple(), vuln_dependent_packages=set()): + """ Draw and save a dependency graph """ + report_path = self.reportdir + report_file + '_' + name + '_' + self.timestamp + '.dot' + + dot_graph = self.generate_dot(dep_graph, vulnerable_packages, vuln_dependent_packages) with open(report_path, 'w') as f: f.write(dot_graph) @@ -137,29 +226,42 @@ def generate_graph(self, dep_graph, deps_type): with open(self.logdir + log, 'a') as flog: flog.write('Graphviz is missing, the graphs will not be rendered.\n') - def generate_dot(self, digraph): - """ Generate a digraph definition in the DOT language """ - graph_template = textwrap.dedent('''\ - digraph dependency_graph {{ - \tranksep=3; - {edges} - }} - ''') - edge_template = '\t"{a}" -> "{b}";' + def generate_complete_graph(self, dep_graph, vulnerable_packages, name): + """ Draw and save the full dependency graph """ + vuln_dependent_packages = self.get_dependent_pkgs(vulnerable_packages, dep_graph) - edges = [] - for node, deps in digraph.iteritems(): - for dep in deps: - edges.append(edge_template.format(a=node, b=dep.pkg_name)) + return self._generate_graph(dep_graph, name, vulnerable_packages, vuln_dependent_packages) - return graph_template.format(edges='\n'.join(edges)) + def generate_vulnerability_graph(self, dep_graph, root_packages, vulnerable_packages, name): + """ Draw and save a partial dependency graph """ + inv_depgraph = self.invert_graph(dep_graph) + partial_graph = {} + + visited = set() + + def dfs(node): + visited.add(node) + for n in inv_depgraph[node]: + partial_graph.setdefault(n, set()).add(Dependency(node, '')) + + if n not in visited: + dfs(n) + + for pkg in root_packages: + dfs(pkg) + + vuln_dependent_packages = self.get_dependent_pkgs(vulnerable_packages, dep_graph) + + # todo: avoid tred-ing partial graphs? + return self._generate_graph(partial_graph, name, vulnerable_packages, vuln_dependent_packages) # ======== supported callbacks from ISA ============= # + def init(ISA_config): global DEPChecker - DEPChecker = DEPChecker or ISA_DEPChecker(ISA_config) + DEPChecker = ISA_DEPChecker(ISA_config) def getPluginName(): From 43650636650efa2485a7dcf992f083e9f73d7ed0 Mon Sep 17 00:00:00 2001 From: Marco Dalla G Date: Wed, 23 Mar 2016 18:39:20 +0200 Subject: [PATCH 22/25] Add hooks for complete/partial vulnerability graph generation --- lib/isafw/isaplugins/ISA_dep_plugin.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index b91c3e7..82a8774 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -277,4 +277,16 @@ def process_report(): global DEPChecker return DEPChecker.process_report() + +def vulnerability_graph(vuln_pkgs, name): + global DEPChecker + DEPChecker.load_dep_graphs() + DEPChecker.generate_complete_graph(DEPChecker.r_depgraph, vuln_pkgs, name) + + +def vulnerability_graph_partial(root_pkgs, vuln_pkgs, name): + global DEPChecker + DEPChecker.load_dep_graphs() + DEPChecker.generate_vulnerability_graph(DEPChecker.r_depgraph, root_pkgs, vuln_pkgs, name) + # ==================================================== # From 7248172f20a72c25eb6a8a4b6ed29f8a43a18785 Mon Sep 17 00:00:00 2001 From: Marco Dalla G Date: Tue, 29 Mar 2016 17:18:22 +0300 Subject: [PATCH 23/25] Add hooks for build-time vulnerability graph generation --- lib/isafw/isaplugins/ISA_dep_plugin.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index 82a8774..2d3b8f5 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -278,15 +278,26 @@ def process_report(): return DEPChecker.process_report() -def vulnerability_graph(vuln_pkgs, name): +def runtime_vulnerability_graph(vuln_pkgs, name): global DEPChecker DEPChecker.load_dep_graphs() DEPChecker.generate_complete_graph(DEPChecker.r_depgraph, vuln_pkgs, name) -def vulnerability_graph_partial(root_pkgs, vuln_pkgs, name): +def runtime_vulnerability_graph_partial(root_pkgs, vuln_pkgs, name): global DEPChecker DEPChecker.load_dep_graphs() DEPChecker.generate_vulnerability_graph(DEPChecker.r_depgraph, root_pkgs, vuln_pkgs, name) + +def buildtime_vulnerability_graph(vuln_pkgs, name): + global DEPChecker + DEPChecker.load_dep_graphs() + DEPChecker.generate_complete_graph(DEPChecker.b_depgraph, vuln_pkgs, name) + + +def buildtime_vulnerability_graph_partial(root_pkgs, vuln_pkgs, name): + global DEPChecker + DEPChecker.load_dep_graphs() + DEPChecker.generate_vulnerability_graph(DEPChecker.b_depgraph, root_pkgs, vuln_pkgs, name) # ==================================================== # From 1606cfed3668490441d5aa159945af7e1230752b Mon Sep 17 00:00:00 2001 From: Marco Dalla G Date: Tue, 29 Mar 2016 17:19:54 +0300 Subject: [PATCH 24/25] Add cleanup method to isafw --- lib/isafw/isafw.py | 19 +++++++++++++++++++ lib/isafw/isaplugins/ISA_dep_plugin.py | 13 ++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/lib/isafw/isafw.py b/lib/isafw/isafw.py index b9ee19a..f988e9c 100644 --- a/lib/isafw/isafw.py +++ b/lib/isafw/isafw.py @@ -203,5 +203,24 @@ def process_report(self): except: print("Exception in plugin: ", sys.exc_info()) + def cleanup(self): + for name in isaplugins.__all__: + plugin = getattr(isaplugins, name) + try: + # see if the plugin has a 'cleanup' attribute + cleanup = plugin.cleanup + except AttributeError: + # if it doesn't, it is ok, won't call this plugin + pass + else: + if self.ISA_config.plugin_whitelist and plugin.getPluginName() not in self.ISA_config.plugin_whitelist: + continue + if self.ISA_config.plugin_blacklist and plugin.getPluginName() in self.ISA_config.plugin_blacklist: + continue + try: + cleanup() + except: + print("Exception in plugin cleanup: ", sys.exc_info()) + diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index 2d3b8f5..1f653e0 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -255,9 +255,14 @@ def dfs(node): # todo: avoid tred-ing partial graphs? return self._generate_graph(partial_graph, name, vulnerable_packages, vuln_dependent_packages) + def cleanup(self): + try: # remove temp file + os.remove(self.reportdir + temp_file) + except OSError: + pass -# ======== supported callbacks from ISA ============= # +# ======== supported callbacks from ISA ============= # def init(ISA_config): global DEPChecker @@ -300,4 +305,10 @@ def buildtime_vulnerability_graph_partial(root_pkgs, vuln_pkgs, name): global DEPChecker DEPChecker.load_dep_graphs() DEPChecker.generate_vulnerability_graph(DEPChecker.b_depgraph, root_pkgs, vuln_pkgs, name) + + +def cleanup(): + global DEPChecker + DEPChecker.cleanup() + # ==================================================== # From 7b5ec56cc8d5419fc8a7b091f398d61df9604afb Mon Sep 17 00:00:00 2001 From: Marco Dallagiacoma Date: Sun, 15 May 2016 18:02:26 +0300 Subject: [PATCH 25/25] Add method to get a list of all the binaries generated by some source packages Mapping of source packages to the binary packages they generate is built during the package-processing phase, and it is stored in a temporary file. --- lib/isafw/isaplugins/ISA_dep_plugin.py | 45 ++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/lib/isafw/isaplugins/ISA_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py index 1f653e0..51715c1 100644 --- a/lib/isafw/isaplugins/ISA_dep_plugin.py +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -9,6 +9,7 @@ log = '/isafw_deplog' temp_file = '/isafw_dep_tmp' +source_to_bins_file = '/isafw_dep_tmp_stob' report_file = '/dep_report' DEPChecker = None @@ -33,6 +34,7 @@ class ISA_DEPChecker: def __init__(self, ISA_config): self.b_depgraph = {} # build-time dependency graph self.r_depgraph = {} # run-time dependency graph + self.stob = None # map run-time source packages to the binary packages they generate self.logdir = ISA_config.logdir self.reportdir = ISA_config.reportdir self.timestamp = ISA_config.timestamp @@ -68,8 +70,10 @@ def process_package(self, ISA_pkg): b_deps = [Dependency(pkg, '') for pkg in ISA_pkg.b_deps] r_deps = dict() + bins = set() # binaries generated from this source package (if it is a source package) for deps in ISA_pkg.r_deps: pkg, dependencies = self._parse_rdeps(deps) + bins.add(pkg) r_deps.setdefault(pkg, set()).update(dependencies) b_filtered = self._filter_deps(b_deps) @@ -85,6 +89,46 @@ def process_package(self, ISA_pkg): r_obj = ('r', pkg, r_filtered[pkg]) pickle.dump(r_obj, tmp) + if bins: + with open(self.reportdir + source_to_bins_file, 'a+b') as f: + obj = (ISA_pkg.name, bins) + pickle.dump(obj, f) + + def load_source_to_bins_mapping(self): + """ Fill the self.stob dictionary + The self.stob dictionary maps a source package name + to the names of the binary packages that it builds + """ + tmp = open(self.reportdir + source_to_bins_file, 'rb') + self.stob = {} + + while True: + try: + s, b = pickle.load(tmp) + self.stob[s] = b + except EOFError: + break + + tmp.close() + + def sources_to_bins(self, source_pkgs): + """ + Given a list of source package names, + return the list of names of the binary packages + generated by those source packages + + No errors will be raised in case of non-existent + or misspelled source package name(s) + """ + if self.stob is None: + self.load_source_to_bins_mapping() + + bins = set() + for source_pkg in source_pkgs: + bins.update(self.stob.get(source_pkg, set())) + + return list(bins) + def load_dep_graphs(self, remove_temp=False): """ Fill self.b_depgraph and self.r_depgraph """ tmp = open(self.reportdir + temp_file, 'rb') @@ -257,6 +301,7 @@ def dfs(node): def cleanup(self): try: # remove temp file + # todo: remove source_to_bin mapping file? os.remove(self.reportdir + temp_file) except OSError: pass