1# Copyright (c) 2011 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import fnmatch 6import glob 7import logging 8import os 9 10from autotest_lib.client.bin import test, utils 11from autotest_lib.client.common_lib import error 12from optparse import OptionParser 13 14FILE_CMD="file -m /usr/local/share/misc/magic.mgc" 15 16class ToolchainOptionSet: 17 """ 18 Handles a set of hits, along with potential whitelists to ignore. 19 """ 20 def __init__(self, description, bad_files, whitelist_file): 21 self.description = description 22 self.bad_set = set(bad_files.splitlines()) 23 self.whitelist_set = set([]) 24 self.process_whitelist_with_private(whitelist_file) 25 26 27 def process_whitelist_with_private(self, whitelist_file): 28 """ 29 Filter out hits found on non-comment lines in the whitelist and 30 and private whitelist. 31 32 @param whitelist_file: path to whitelist file 33 """ 34 whitelist_files = [whitelist_file] 35 private_file = os.path.join(os.path.dirname(whitelist_file), 36 "private_" + 37 os.path.basename(whitelist_file)) 38 whitelist_files.append(private_file) 39 self.process_whitelists(whitelist_files) 40 41 42 def process_whitelist(self, whitelist_file): 43 """ 44 Filter out hits found on non-comment lines in the whitelist. 45 46 @param whitelist_file: path to whitelist file 47 """ 48 if os.path.isfile(whitelist_file): 49 f = open(whitelist_file) 50 whitelist = [x for x in f.read().splitlines() 51 if not x.startswith('#')] 52 f.close() 53 self.whitelist_set = self.whitelist_set.union(set(whitelist)) 54 55 filtered_list = [] 56 for bad_file in self.bad_set: 57 # Does |bad_file| match any entry in the whitelist? 58 in_whitelist = any([fnmatch.fnmatch(bad_file, whitelist_entry) 59 for whitelist_entry in self.whitelist_set]) 60 if not in_whitelist: 61 filtered_list.append(bad_file) 62 63 self.filtered_set = set(filtered_list) 64 # TODO(jorgelo): remove glob patterns from |new_passes|. 65 self.new_passes = self.whitelist_set.difference(self.bad_set) 66 67 68 def process_whitelists(self, whitelist_files): 69 """ 70 Filter out hits found in a list of whitelist files. 71 72 @param whitelist_files: list of paths to whitelist files 73 """ 74 for whitelist_file in whitelist_files: 75 self.process_whitelist(whitelist_file) 76 77 78 def get_fail_summary_message(self): 79 m = "Test %s " % self.description 80 m += "%d failures" % len(self.filtered_set) 81 return m 82 83 84 def get_fail_message(self): 85 m = self.get_fail_summary_message() 86 sorted_list = list(self.filtered_set) 87 sorted_list.sort() 88 m += "\nFAILED:\n%s\n\n" % "\n".join(sorted_list) 89 return m 90 91 92 def __str__(self): 93 m = "Test %s " % self.description 94 m += ("%d failures, %d in whitelist, %d in filtered, %d new passes " % 95 (len(self.bad_set), 96 len(self.whitelist_set), 97 len(self.filtered_set), 98 len(self.new_passes))) 99 100 if len(self.filtered_set): 101 sorted_list = list(self.filtered_set) 102 sorted_list.sort() 103 m += "FAILED:\n%s" % "\n".join(sorted_list) 104 else: 105 m += "PASSED!" 106 107 if len(self.new_passes): 108 sorted_list = list(self.new_passes) 109 sorted_list.sort() 110 m += ("\nNew passes (remove these from the whitelist):\n%s" % 111 "\n".join(sorted_list)) 112 logging.debug(m) 113 return m 114 115 116class platform_ToolchainOptions(test.test): 117 """ 118 Tests for various expected conditions on ELF binaries in the image. 119 """ 120 version = 2 121 122 def get_cmd(self, test_cmd, find_options=""): 123 base_cmd = ("find '%s' -wholename %s -prune -o " 124 " -wholename /proc -prune -o " 125 " -wholename /dev -prune -o " 126 " -wholename /sys -prune -o " 127 " -wholename /mnt/stateful_partition -prune -o " 128 " -wholename /usr/local -prune -o " 129 # There are files in /home (e.g. encrypted files that look 130 # like they have ELF headers) that cause false positives, 131 # and since that's noexec anyways, it should be skipped. 132 " -wholename '/home' -prune -o " 133 " -wholename " 134 "/opt/google/containers/android/rootfs/root/vendor" 135 " -prune -o " 136 " -wholename " 137 "/run/containers/android_*/root/vendor" 138 " -prune -o " 139 " %s " 140 " -not -name 'libstdc++.so.*' " 141 " -not -name 'libgcc_s.so.*' " 142 " -type f -executable -exec " 143 "sh -c '%s " 144 "{} | grep -q ELF && " 145 "(%s || echo {})' ';'") 146 rootdir = "/" 147 cmd = base_cmd % (rootdir, self.autodir, find_options, FILE_CMD, 148 test_cmd) 149 return cmd 150 151 152 def create_and_filter(self, description, cmd, whitelist_file, 153 find_options=""): 154 """ 155 Runs a command, with "{}" replaced (via "find -exec") with the 156 target ELF binary. If the command fails, the file is marked as 157 failing the test. Results are filtered against the provided 158 whitelist file. 159 160 @param description: text name of the check being done 161 @param cmd: command to run via find's -exec option 162 @param whitelist_file: list of failures to ignore 163 @param find_options: additional options for find to limit the scope 164 """ 165 full_cmd = self.get_cmd(cmd, find_options) 166 bad_files = utils.system_output(full_cmd) 167 cso = ToolchainOptionSet(description, bad_files, whitelist_file) 168 cso.process_whitelist_with_private(whitelist_file) 169 return cso 170 171 172 def run_once(self, rootdir="/", args=[]): 173 """ 174 Do a find for all the ELF files on the system. 175 For each one, test for compiler options that should have been used 176 when compiling the file. 177 178 For missing compiler options, print the files. 179 """ 180 181 parser = OptionParser() 182 parser.add_option('--hardfp', 183 dest='enable_hardfp', 184 default=False, 185 action='store_true', 186 help='Whether to check for hardfp binaries.') 187 (options, args) = parser.parse_args(args) 188 189 option_sets = [] 190 191 libc_glob = "/lib/libc-[0-9]*" 192 193 readelf_cmd = glob.glob("/usr/local/*/binutils-bin/*/readelf")[0] 194 195 # We do not test binaries if they are built with Address Sanitizer 196 # because it is a separate testing tool. 197 no_asan_used = utils.system_output("%s -s " 198 "/opt/google/chrome/chrome | " 199 "egrep -q \"__asan_init\" || " 200 "echo no ASAN" % readelf_cmd) 201 if not no_asan_used: 202 logging.debug("ASAN detected on /opt/google/chrome/chrome. " 203 "Will skip all checks.") 204 return 205 206 # Check that gold was used to build binaries. 207 # TODO(jorgelo): re-enable this check once crbug.com/417912 is fixed. 208 # gold_cmd = ("%s -S {} 2>&1 | " 209 # "egrep -q \".note.gnu.gold-ve\"" % readelf_cmd) 210 # gold_find_options = "" 211 # if utils.get_cpu_arch() == "arm": 212 # # gold is only enabled for Chrome on ARM. 213 # gold_find_options = "-path \"/opt/google/chrome/chrome\"" 214 # gold_whitelist = os.path.join(self.bindir, "gold_whitelist") 215 # option_sets.append(self.create_and_filter("gold", 216 # gold_cmd, 217 # gold_whitelist, 218 # gold_find_options)) 219 220 # Verify non-static binaries have BIND_NOW in dynamic section. 221 now_cmd = ("(%s {} | grep -q statically) ||" 222 "%s -d {} 2>&1 | " 223 "egrep -q \"BIND_NOW\"" % (FILE_CMD, readelf_cmd)) 224 now_whitelist = os.path.join(self.bindir, "now_whitelist") 225 option_sets.append(self.create_and_filter("-Wl,-z,now", 226 now_cmd, 227 now_whitelist)) 228 229 # Verify non-static binaries have RELRO program header. 230 relro_cmd = ("(%s {} | grep -q statically) ||" 231 "%s -l {} 2>&1 | " 232 "egrep -q \"GNU_RELRO\"" % (FILE_CMD, readelf_cmd)) 233 relro_whitelist = os.path.join(self.bindir, "relro_whitelist") 234 option_sets.append(self.create_and_filter("-Wl,-z,relro", 235 relro_cmd, 236 relro_whitelist)) 237 238 # Verify non-static binaries are dynamic (built PIE). 239 pie_cmd = ("(%s {} | grep -q statically) ||" 240 "%s -l {} 2>&1 | " 241 "egrep -q \"Elf file type is DYN\"" % (FILE_CMD, 242 readelf_cmd)) 243 pie_whitelist = os.path.join(self.bindir, "pie_whitelist") 244 option_sets.append(self.create_and_filter("-fPIE", 245 pie_cmd, 246 pie_whitelist)) 247 248 # Verify ELFs don't include TEXTRELs. 249 # FIXME: Remove the i?86 filter after the bug is fixed. 250 # crbug.com/686926 251 if (utils.get_current_kernel_arch() not in 252 ('i%d86' % i for i in xrange(3,7))): 253 textrel_cmd = ("(%s {} | grep -q statically) ||" 254 "%s -d {} 2>&1 | " 255 "(egrep -q \"0x0+16..TEXTREL\"; [ $? -ne 0 ])" 256 % (FILE_CMD, readelf_cmd)) 257 textrel_whitelist = os.path.join(self.bindir, "textrel_whitelist") 258 option_sets.append(self.create_and_filter("TEXTREL", 259 textrel_cmd, 260 textrel_whitelist)) 261 262 # Verify all binaries have non-exec STACK program header. 263 stack_cmd = ("%s -lW {} 2>&1 | " 264 "egrep -q \"GNU_STACK.*RW \"" % readelf_cmd) 265 stack_whitelist = os.path.join(self.bindir, "stack_whitelist") 266 option_sets.append(self.create_and_filter("Executable Stack", 267 stack_cmd, 268 stack_whitelist)) 269 270 # Verify no binaries have W+X LOAD program headers. 271 loadwx_cmd = ("%s -lW {} 2>&1 | " 272 "grep \"LOAD\" | egrep -v \"(RW |R E|R )\" | " 273 "wc -l | grep -q \"^0$\"" % readelf_cmd) 274 loadwx_whitelist = os.path.join(self.bindir, "loadwx_whitelist") 275 option_sets.append(self.create_and_filter("LOAD Writable and Exec", 276 loadwx_cmd, 277 loadwx_whitelist)) 278 279 # Verify ARM binaries are all using VFP registers. 280 if (options.enable_hardfp and utils.get_cpu_arch() == 'arm'): 281 hardfp_cmd = ("%s -A {} 2>&1 | " 282 "egrep -q \"Tag_ABI_VFP_args: VFP registers\"" % 283 readelf_cmd) 284 hardfp_whitelist = os.path.join(self.bindir, "hardfp_whitelist") 285 option_sets.append(self.create_and_filter("hardfp", hardfp_cmd, 286 hardfp_whitelist)) 287 288 # Verify all binaries are not linked with libgcc_s.so. 289 libgcc_cmd = ("%s -dW {} 2>&1 | grep \"NEEDED\" | " 290 "(! grep -q \"libgcc_s.so\")" % readelf_cmd) 291 libgcc_whitelist = os.path.join(self.bindir, "libgcc_whitelist") 292 option_sets.append(self.create_and_filter("Libgcc_s Users", 293 libgcc_cmd, 294 libgcc_whitelist)) 295 296 # Verify all binaries are not linked with libstdc++.so. 297 libstdcxx_cmd = ("%s -dW {} 2>&1 | grep \"NEEDED\" | " 298 "(! grep -q \"libstdc++.so\")" % readelf_cmd) 299 libstdcxx_whitelist = os.path.join(self.bindir, "libstdcxx_whitelist") 300 option_sets.append(self.create_and_filter("Libstdc++ Users", 301 libstdcxx_cmd, 302 libstdcxx_whitelist)) 303 304 fail_msg = "" 305 306 # There is currently no way to clear binary prebuilts for all devs. 307 # Thus, when a new check is added to this test, the test might fail 308 # for users who have old prebuilts which have not been compiled 309 # in the correct manner. 310 fail_summaries = [] 311 full_msg = "Test results:" 312 num_fails = 0 313 for cos in option_sets: 314 if len(cos.filtered_set): 315 num_fails += 1 316 fail_msg += cos.get_fail_message() + "\n" 317 fail_summaries.append(cos.get_fail_summary_message()) 318 full_msg += str(cos) + "\n\n" 319 fail_summary_msg = ", ".join(fail_summaries) 320 321 logging.error(fail_msg) 322 logging.debug(full_msg) 323 if num_fails: 324 raise error.TestFail(fail_summary_msg) 325