| # Copyright (c) 2011 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import fnmatch |
| import glob |
| import logging |
| import os |
| |
| from autotest_lib.client.bin import test, utils |
| from autotest_lib.client.common_lib import error |
| from optparse import OptionParser |
| |
| FILE_CMD="file -m /usr/local/share/misc/magic.mgc" |
| |
| class ToolchainOptionSet: |
| """ |
| Handles a set of hits, along with potential whitelists to ignore. |
| """ |
| def __init__(self, description, bad_files, whitelist_file): |
| self.description = description |
| self.bad_set = set(bad_files.splitlines()) |
| self.whitelist_set = set([]) |
| self.process_whitelist_with_private(whitelist_file) |
| |
| |
| def process_whitelist_with_private(self, whitelist_file): |
| """ |
| Filter out hits found on non-comment lines in the whitelist and |
| and private whitelist. |
| |
| @param whitelist_file: path to whitelist file |
| """ |
| whitelist_files = [whitelist_file] |
| private_file = os.path.join(os.path.dirname(whitelist_file), |
| "private_" + |
| os.path.basename(whitelist_file)) |
| whitelist_files.append(private_file) |
| self.process_whitelists(whitelist_files) |
| |
| |
| def process_whitelist(self, whitelist_file): |
| """ |
| Filter out hits found on non-comment lines in the whitelist. |
| |
| @param whitelist_file: path to whitelist file |
| """ |
| if os.path.isfile(whitelist_file): |
| f = open(whitelist_file) |
| whitelist = [x for x in f.read().splitlines() |
| if not x.startswith('#')] |
| f.close() |
| self.whitelist_set = self.whitelist_set.union(set(whitelist)) |
| |
| filtered_list = [] |
| for bad_file in self.bad_set: |
| # Does |bad_file| match any entry in the whitelist? |
| in_whitelist = any([fnmatch.fnmatch(bad_file, whitelist_entry) |
| for whitelist_entry in self.whitelist_set]) |
| if not in_whitelist: |
| filtered_list.append(bad_file) |
| |
| self.filtered_set = set(filtered_list) |
| # TODO(jorgelo): remove glob patterns from |new_passes|. |
| self.new_passes = self.whitelist_set.difference(self.bad_set) |
| |
| |
| def process_whitelists(self, whitelist_files): |
| """ |
| Filter out hits found in a list of whitelist files. |
| |
| @param whitelist_files: list of paths to whitelist files |
| """ |
| for whitelist_file in whitelist_files: |
| self.process_whitelist(whitelist_file) |
| |
| |
| def get_fail_summary_message(self): |
| m = "Test %s " % self.description |
| m += "%d failures" % len(self.filtered_set) |
| return m |
| |
| |
| def get_fail_message(self): |
| m = self.get_fail_summary_message() |
| sorted_list = list(self.filtered_set) |
| sorted_list.sort() |
| m += "\nFAILED:\n%s\n\n" % "\n".join(sorted_list) |
| return m |
| |
| |
| def __str__(self): |
| m = "Test %s " % self.description |
| m += ("%d failures, %d in whitelist, %d in filtered, %d new passes " % |
| (len(self.bad_set), |
| len(self.whitelist_set), |
| len(self.filtered_set), |
| len(self.new_passes))) |
| |
| if len(self.filtered_set): |
| sorted_list = list(self.filtered_set) |
| sorted_list.sort() |
| m += "FAILED:\n%s" % "\n".join(sorted_list) |
| else: |
| m += "PASSED!" |
| |
| if len(self.new_passes): |
| sorted_list = list(self.new_passes) |
| sorted_list.sort() |
| m += ("\nNew passes (remove these from the whitelist):\n%s" % |
| "\n".join(sorted_list)) |
| logging.debug(m) |
| return m |
| |
| |
| class platform_ToolchainOptions(test.test): |
| """ |
| Tests for various expected conditions on ELF binaries in the image. |
| """ |
| version = 2 |
| |
| def get_cmd(self, test_cmd, find_options=""): |
| base_cmd = ("find '%s' -wholename %s -prune -o " |
| " -wholename /proc -prune -o " |
| " -wholename /dev -prune -o " |
| " -wholename /sys -prune -o " |
| " -wholename /mnt/stateful_partition -prune -o " |
| " -wholename /usr/local -prune -o " |
| # There are files in /home (e.g. encrypted files that look |
| # like they have ELF headers) that cause false positives, |
| # and since that's noexec anyways, it should be skipped. |
| " -wholename '/home' -prune -o " |
| " -wholename " |
| "/opt/google/containers/android/rootfs/root/vendor" |
| " -prune -o " |
| " -wholename " |
| "/run/containers/android_*/root/vendor" |
| " -prune -o " |
| " %s " |
| " -not -name 'libstdc++.so.*' " |
| " -not -name 'libgcc_s.so.*' " |
| " -type f -executable -exec " |
| "sh -c '%s " |
| "{} | grep -q ELF && " |
| "(%s || echo {})' ';'") |
| rootdir = "/" |
| cmd = base_cmd % (rootdir, self.autodir, find_options, FILE_CMD, |
| test_cmd) |
| return cmd |
| |
| |
| def create_and_filter(self, description, cmd, whitelist_file, |
| find_options=""): |
| """ |
| Runs a command, with "{}" replaced (via "find -exec") with the |
| target ELF binary. If the command fails, the file is marked as |
| failing the test. Results are filtered against the provided |
| whitelist file. |
| |
| @param description: text name of the check being done |
| @param cmd: command to run via find's -exec option |
| @param whitelist_file: list of failures to ignore |
| @param find_options: additional options for find to limit the scope |
| """ |
| full_cmd = self.get_cmd(cmd, find_options) |
| bad_files = utils.system_output(full_cmd) |
| cso = ToolchainOptionSet(description, bad_files, whitelist_file) |
| cso.process_whitelist_with_private(whitelist_file) |
| return cso |
| |
| |
| def run_once(self, rootdir="/", args=[]): |
| """ |
| Do a find for all the ELF files on the system. |
| For each one, test for compiler options that should have been used |
| when compiling the file. |
| |
| For missing compiler options, print the files. |
| """ |
| |
| parser = OptionParser() |
| parser.add_option('--hardfp', |
| dest='enable_hardfp', |
| default=False, |
| action='store_true', |
| help='Whether to check for hardfp binaries.') |
| (options, args) = parser.parse_args(args) |
| |
| option_sets = [] |
| |
| libc_glob = "/lib/libc-[0-9]*" |
| |
| readelf_cmd = glob.glob("/usr/local/*/binutils-bin/*/readelf")[0] |
| |
| # We do not test binaries if they are built with Address Sanitizer |
| # because it is a separate testing tool. |
| no_asan_used = utils.system_output("%s -s " |
| "/opt/google/chrome/chrome | " |
| "egrep -q \"__asan_init\" || " |
| "echo no ASAN" % readelf_cmd) |
| if not no_asan_used: |
| logging.debug("ASAN detected on /opt/google/chrome/chrome. " |
| "Will skip all checks.") |
| return |
| |
| # Check that gold was used to build binaries. |
| # TODO(jorgelo): re-enable this check once crbug.com/417912 is fixed. |
| # gold_cmd = ("%s -S {} 2>&1 | " |
| # "egrep -q \".note.gnu.gold-ve\"" % readelf_cmd) |
| # gold_find_options = "" |
| # if utils.get_cpu_arch() == "arm": |
| # # gold is only enabled for Chrome on ARM. |
| # gold_find_options = "-path \"/opt/google/chrome/chrome\"" |
| # gold_whitelist = os.path.join(self.bindir, "gold_whitelist") |
| # option_sets.append(self.create_and_filter("gold", |
| # gold_cmd, |
| # gold_whitelist, |
| # gold_find_options)) |
| |
| # Verify non-static binaries have BIND_NOW in dynamic section. |
| now_cmd = ("(%s {} | grep -q statically) ||" |
| "%s -d {} 2>&1 | " |
| "egrep -q \"BIND_NOW\"" % (FILE_CMD, readelf_cmd)) |
| now_whitelist = os.path.join(self.bindir, "now_whitelist") |
| option_sets.append(self.create_and_filter("-Wl,-z,now", |
| now_cmd, |
| now_whitelist)) |
| |
| # Verify non-static binaries have RELRO program header. |
| relro_cmd = ("(%s {} | grep -q statically) ||" |
| "%s -l {} 2>&1 | " |
| "egrep -q \"GNU_RELRO\"" % (FILE_CMD, readelf_cmd)) |
| relro_whitelist = os.path.join(self.bindir, "relro_whitelist") |
| option_sets.append(self.create_and_filter("-Wl,-z,relro", |
| relro_cmd, |
| relro_whitelist)) |
| |
| # Verify non-static binaries are dynamic (built PIE). |
| pie_cmd = ("(%s {} | grep -q statically) ||" |
| "%s -l {} 2>&1 | " |
| "egrep -q \"Elf file type is DYN\"" % (FILE_CMD, |
| readelf_cmd)) |
| pie_whitelist = os.path.join(self.bindir, "pie_whitelist") |
| option_sets.append(self.create_and_filter("-fPIE", |
| pie_cmd, |
| pie_whitelist)) |
| |
| # Verify ELFs don't include TEXTRELs. |
| # FIXME: Remove the i?86 filter after the bug is fixed. |
| # crbug.com/686926 |
| if (utils.get_current_kernel_arch() not in |
| ('i%d86' % i for i in xrange(3,7))): |
| textrel_cmd = ("(%s {} | grep -q statically) ||" |
| "%s -d {} 2>&1 | " |
| "(egrep -q \"0x0+16..TEXTREL\"; [ $? -ne 0 ])" |
| % (FILE_CMD, readelf_cmd)) |
| textrel_whitelist = os.path.join(self.bindir, "textrel_whitelist") |
| option_sets.append(self.create_and_filter("TEXTREL", |
| textrel_cmd, |
| textrel_whitelist)) |
| |
| # Verify all binaries have non-exec STACK program header. |
| stack_cmd = ("%s -lW {} 2>&1 | " |
| "egrep -q \"GNU_STACK.*RW \"" % readelf_cmd) |
| stack_whitelist = os.path.join(self.bindir, "stack_whitelist") |
| option_sets.append(self.create_and_filter("Executable Stack", |
| stack_cmd, |
| stack_whitelist)) |
| |
| # Verify no binaries have W+X LOAD program headers. |
| loadwx_cmd = ("%s -lW {} 2>&1 | " |
| "grep \"LOAD\" | egrep -v \"(RW |R E|R )\" | " |
| "wc -l | grep -q \"^0$\"" % readelf_cmd) |
| loadwx_whitelist = os.path.join(self.bindir, "loadwx_whitelist") |
| option_sets.append(self.create_and_filter("LOAD Writable and Exec", |
| loadwx_cmd, |
| loadwx_whitelist)) |
| |
| # Verify ARM binaries are all using VFP registers. |
| if (options.enable_hardfp and utils.get_cpu_arch() == 'arm'): |
| hardfp_cmd = ("%s -A {} 2>&1 | " |
| "egrep -q \"Tag_ABI_VFP_args: VFP registers\"" % |
| readelf_cmd) |
| hardfp_whitelist = os.path.join(self.bindir, "hardfp_whitelist") |
| option_sets.append(self.create_and_filter("hardfp", hardfp_cmd, |
| hardfp_whitelist)) |
| |
| # Verify all binaries are not linked with libgcc_s.so. |
| libgcc_cmd = ("%s -dW {} 2>&1 | grep \"NEEDED\" | " |
| "(! grep -q \"libgcc_s.so\")" % readelf_cmd) |
| libgcc_whitelist = os.path.join(self.bindir, "libgcc_whitelist") |
| option_sets.append(self.create_and_filter("Libgcc_s Users", |
| libgcc_cmd, |
| libgcc_whitelist)) |
| |
| # Verify all binaries are not linked with libstdc++.so. |
| libstdcxx_cmd = ("%s -dW {} 2>&1 | grep \"NEEDED\" | " |
| "(! grep -q \"libstdc++.so\")" % readelf_cmd) |
| libstdcxx_whitelist = os.path.join(self.bindir, "libstdcxx_whitelist") |
| option_sets.append(self.create_and_filter("Libstdc++ Users", |
| libstdcxx_cmd, |
| libstdcxx_whitelist)) |
| |
| fail_msg = "" |
| |
| # There is currently no way to clear binary prebuilts for all devs. |
| # Thus, when a new check is added to this test, the test might fail |
| # for users who have old prebuilts which have not been compiled |
| # in the correct manner. |
| fail_summaries = [] |
| full_msg = "Test results:" |
| num_fails = 0 |
| for cos in option_sets: |
| if len(cos.filtered_set): |
| num_fails += 1 |
| fail_msg += cos.get_fail_message() + "\n" |
| fail_summaries.append(cos.get_fail_summary_message()) |
| full_msg += str(cos) + "\n\n" |
| fail_summary_msg = ", ".join(fail_summaries) |
| |
| logging.error(fail_msg) |
| logging.debug(full_msg) |
| if num_fails: |
| raise error.TestFail(fail_summary_msg) |