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 diff --git a/classes/isafw.bbclass b/classes/isafw.bbclass index f302de8..0205aa5 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] @@ -125,6 +125,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() { @@ -132,6 +133,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" @@ -190,18 +192,19 @@ 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): 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) 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")): @@ -234,7 +237,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 diff --git a/lib/isafw/isafw.py b/lib/isafw/isafw.py index 01b8b44..f988e9c 100644 --- a/lib/isafw/isafw.py +++ b/lib/isafw/isafw.py @@ -81,6 +81,8 @@ 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 + machine = "" # name of machine build is produced for class ISA: @@ -201,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_cfa_plugin.py b/lib/isafw/isaplugins/ISA_cfa_plugin.py index 0e58c65..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 @@ -45,36 +42,50 @@ class ISA_CFChecker(): no_canary = [] no_pie = [] no_nx = [] + execstack = [] + execstack_not_defined = [] + nodrop_groups = [] + no_mpx = [] 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"]) 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: + # check that execstack is installed + rc = subprocess.call(["which", "readelf"]) + if rc == 0: + self.initialized = True + print("Plugin ISA_CFChecker initialized!") + 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.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") 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) - 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.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) @@ -82,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") @@ -110,36 +121,68 @@ 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') + 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') + 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='4') - 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: - 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 - tcase2 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_canary') + tcase1 = etree.SubElement(root, 'testcase', classname='files_with_no_RELO', name=item) + etree.SubElement(tcase1, 'failure', message=item, type='violation') 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 - tcase3 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_PIE') + tcase2 = etree.SubElement(root, 'testcase', classname='files_with_no_canary', name=item) + etree.SubElement(tcase2, 'failure', message=item, type='violation') 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 - tcase4 = etree.SubElement(root, 'testcase', classname='ISA_CFChecker', name='files_with_no_NX') + tcase3 = etree.SubElement(root, 'testcase', classname='files_with_no_PIE', name=item) + etree.SubElement(tcase3, 'failure', message=item, type='violation') 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)) + tcase4 = etree.SubElement(root, 'testcase', classname='files_with_no_NX', name=item) + etree.SubElement(tcase4, 'failure', message=item, type='violation') + 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') + 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') + 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') + 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' + 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): @@ -149,6 +192,43 @@ 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_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_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, @@ -199,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] @@ -211,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] @@ -233,13 +313,20 @@ def process_files(self, img_name, path_to_fs): sec_field = "File is pdf" else: sec_field = self.get_security_flags(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('\n') + execstack = self.get_execstack(real_file) + nodrop_groups = self.get_nodrop_groups(real_file) + no_mpx = self.get_mpx(real_file) + if self.full_reports : + 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: + 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_cve_plugin.py b/lib/isafw/isaplugins/ISA_cve_plugin.py index 1691482..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,32 +72,57 @@ 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.") - with open(self.logdir + log, 'a') as flog: + self.initialized = False + 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): - 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.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: - flog.write("Creating report in CSV format.\n") - self.process_report_type("csv") + print("Creating report in CSV format.") + with open(self.logfile, '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.logfile, 'a') as flog: + flog.write("Creating report in XML format.\n") + self.write_report_xml() + + def write_report_xml(self): + from lxml import etree + numTests = 0 + root = etree.Element('testsuite', name='CVE_Plugin', tests='1') + with open(self.report_name + ".csv", 'r') as f: + for line in f: + numTests += 1 + line = line.strip() + 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.report_name + '.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 @@ -118,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_dep_plugin.py b/lib/isafw/isaplugins/ISA_dep_plugin.py new file mode 100644 index 0000000..51715c1 --- /dev/null +++ b/lib/isafw/isaplugins/ISA_dep_plugin.py @@ -0,0 +1,359 @@ +import os +import textwrap +import subprocess +import re +try: + import cPickle as pickle +except ImportError: + import pickle + +log = '/isafw_deplog' +temp_file = '/isafw_dep_tmp' +source_to_bins_file = '/isafw_dep_tmp_stob' +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 + + def __str__(self): + return '{0} ({1})'.format(self.pkg_name, self.details) + + def __repr__(self): + return self.__str__() + + +class ISA_DEPChecker: + initialized = False + + 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 + + self.initialized = True + 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 = [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) + 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: + 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) + + 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') + + while True: + try: + dep_type, pkg, deps = pickle.load(tmp) + + # update dependencies if node already exists in depgraph, + # add new empty node otherwise + depgraph = self.b_depgraph if dep_type == 'b' else self.r_depgraph + node = depgraph.setdefault(pkg, set()) + node.update(deps) + + except EOFError: + break + + tmp.close() + + 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') + + 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) + + 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) + + # 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', '-Tsvg', '-o', report_path[:-3] + 'svg'), + stdin=ps.stdout + ) + ps.wait() + else: + with open(self.logdir + log, 'a') as flog: + flog.write('Graphviz is missing, the graphs will not be rendered.\n') + + 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) + + return self._generate_graph(dep_graph, name, vulnerable_packages, vuln_dependent_packages) + + 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) + + def cleanup(self): + try: # remove temp file + # todo: remove source_to_bin mapping file? + os.remove(self.reportdir + temp_file) + except OSError: + pass + + +# ======== supported callbacks from ISA ============= # + +def init(ISA_config): + global DEPChecker + DEPChecker = 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() + + +def runtime_vulnerability_graph(vuln_pkgs, name): + global DEPChecker + DEPChecker.load_dep_graphs() + DEPChecker.generate_complete_graph(DEPChecker.r_depgraph, 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) + + +def cleanup(): + global DEPChecker + DEPChecker.cleanup() + +# ==================================================== # diff --git a/lib/isafw/isaplugins/ISA_fsa_plugin.py b/lib/isafw/isaplugins/ISA_fsa_plugin.py index 40860c9..620773e 100644 --- a/lib/isafw/isaplugins/ISA_fsa_plugin.py +++ b/lib/isafw/isaplugins/ISA_fsa_plugin.py @@ -30,67 +30,68 @@ 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 = [] self.setgid_files = [] 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)) - 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.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.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): + 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: 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") @@ -107,30 +108,26 @@ 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: - failrs1 = etree.SubElement(tcase1, 'failure', msg = 'Non-compliant files found', type = 'violation') for item in self.setuid_files: - etree.SubElement(failrs1, 'value').text = item - tcase2 = etree.SubElement(root, 'testacase', classname = 'ISA_FSChecker', name = 'Files_with_SETGID_bit_set') + tcase1 = etree.SubElement(root, 'testcase', classname = 'Files_with_SETUID_bit_set', name = item) + failrs1 = etree.SubElement(tcase1, 'failure', message = item, type = 'violation') 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 - tcase3 = etree.SubElement(root, 'testase', classname = 'ISA_FSChecker', name = 'World-writable_files') + tcase2 = etree.SubElement(root, 'testacase', classname = 'Files_with_SETGID_bit_set', name = item) + failrs2 = etree.SubElement(tcase2, 'failure', message = item, type = 'violation') 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 - tcase4 = etree.SubElement(root, 'testcase', classname = 'ISA_FSChecker', name = 'World-writable_dirs_with_no_sticky_bit') + tcase3 = etree.SubElement(root, 'testase', classname = 'World-writable_files', name = item) + failrs3 = etree.SubElement(tcase3, 'failure', message = item, type = 'violation') 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)) + 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 837dfe7..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 @@ -54,8 +51,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 +73,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', @@ -153,18 +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: @@ -182,39 +182,43 @@ 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)) - 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: 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: print("Plugin hasn't initialized! Not performing the call.") + def write_full_report(self, ISA_kernel): + if self.full_reports : + 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") + 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: + 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") @@ -283,12 +287,12 @@ 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') - failrs1 = etree.SubElement(tcase1, 'failure', msg = 'Non-compliant kernel settings found', type = 'violation') + 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 + 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 @@ -299,17 +303,15 @@ 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 - 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') + 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') 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 + ':' + str(self.keys_kco[key] + '"' + ' recommended="' + key + ':' + str(self.keys_kco_ref[key] + '"')) - value2 = etree.SubElement(failrs2, 'value').text= msg2 - 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') + 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') 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"): @@ -328,11 +330,10 @@ 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 - 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') + 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') 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 @@ -343,11 +344,10 @@ 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' + 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 d56acea..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): @@ -67,7 +65,8 @@ 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) - with open(self.logdir + log, 'a') as flog: + self.initialized = False + 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 @@ -83,7 +82,8 @@ 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) - with open(self.logdir + log, 'a') as flog: + self.initialized = False + 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 @@ -92,20 +92,47 @@ 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.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.") - with open(self.logdir + log, 'a') as flog: + self.initialized = False + 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.logfile, 'a') as flog: + flog.write("Creating report in XML format.\n") + self.write_report_xml() + + 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.report_name): + with open(self.report_name, '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.report_name + '.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 +167,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() #====================================================# 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 4ebb825..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}" @@ -15,6 +18,6 @@ do_install() { install -m 0755 ${WORKDIR}/checksec.sh ${D}${bindir} } -RDEPENDS_${PN} = "bash" +RDEPENDS_${PN} = "bash binutils" BBCLASSEXTEND = "native"