checkbins.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2011 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """Makes sure that all EXE and DLL files in the provided directory were built
  6. correctly.
  7. In essense it runs a subset of BinScope tests ensuring that binaries have
  8. /NXCOMPAT, /DYNAMICBASE and /SAFESEH.
  9. """
  10. import json
  11. import os
  12. import optparse
  13. import sys
  14. REPO_ROOT = os.path.join(os.path.dirname(__file__), '..', '..')
  15. FILES_CFG = os.path.join(REPO_ROOT, 'chrome', 'tools', 'build', 'win',
  16. 'FILES.cfg')
  17. PEFILE_DIR = os.path.join(REPO_ROOT, 'third_party', 'pefile_py3')
  18. sys.path.append(PEFILE_DIR)
  19. import pefile
  20. PE_FILE_EXTENSIONS = ['.exe', '.dll']
  21. # https://docs.microsoft.com/en-us/windows/win32/debug/pe-format
  22. DYNAMICBASE_FLAG = 0x0040
  23. NXCOMPAT_FLAG = 0x0100
  24. NO_SEH_FLAG = 0x0400
  25. GUARD_CF_FLAG = 0x4000
  26. MACHINE_TYPE_AMD64 = 0x8664
  27. CETCOMPAT_BIT = 0 # offset in extended dll characteristics
  28. # Please do not add your file here without confirming that it indeed doesn't
  29. # require /NXCOMPAT and /DYNAMICBASE. Contact //sandbox/win/OWNERS or your
  30. # local Windows guru for advice.
  31. EXCLUDED_FILES = [
  32. 'crashpad_util_test_process_info_test_child.exe',
  33. 'mini_installer.exe',
  34. 'previous_version_mini_installer.exe',
  35. ]
  36. # PE files that are otherwise included but for which /cetcompat is not required.
  37. CETCOMPAT_NOT_REQUIRED = [
  38. 'chrome_proxy.exe',
  39. 'chrome_pwa_launcher.exe',
  40. 'elevation_service.exe',
  41. 'nacl64.exe',
  42. 'notification_helper.exe',
  43. ]
  44. def IsPEFile(path):
  45. return (os.path.isfile(path) and
  46. os.path.splitext(path)[1].lower() in PE_FILE_EXTENSIONS and
  47. os.path.basename(path) not in EXCLUDED_FILES)
  48. def IsBitSet(data, bit_idx):
  49. return 0 != data[int(bit_idx / 8)] & (1 << (bit_idx % 8))
  50. def IsCetExpected(path):
  51. return os.path.basename(path) not in CETCOMPAT_NOT_REQUIRED
  52. def main(options, args):
  53. directory = args[0]
  54. pe_total = 0
  55. pe_passed = 0
  56. failures = []
  57. # Load FILES.cfg - it is a python file setting a FILES variable.
  58. exec_globals = {'__builtins__': None}
  59. with open(FILES_CFG, encoding="utf-8") as f:
  60. code = compile(f.read(), FILES_CFG, 'exec')
  61. exec(code, exec_globals)
  62. files_cfg = exec_globals['FILES']
  63. # Determines whether a specified file is in the 'default'
  64. # filegroup - which means it's shipped with Chrome.
  65. def IsInDefaultFileGroup(path):
  66. for fileobj in files_cfg:
  67. if fileobj['filename'] == os.path.basename(path):
  68. if 'default' in fileobj.get('filegroup', {}):
  69. return True
  70. return False
  71. for file in os.listdir(directory):
  72. path = os.path.abspath(os.path.join(directory, file))
  73. if not IsPEFile(path):
  74. continue
  75. pe = pefile.PE(path, fast_load=True)
  76. pe.parse_data_directories(directories=[
  77. pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG'],
  78. pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_DEBUG']
  79. ])
  80. pe_total = pe_total + 1
  81. success = True
  82. # Check for /DYNAMICBASE.
  83. if pe.OPTIONAL_HEADER.DllCharacteristics & DYNAMICBASE_FLAG:
  84. if options.verbose:
  85. print("Checking %s for /DYNAMICBASE... PASS" % path)
  86. else:
  87. success = False
  88. print("Checking %s for /DYNAMICBASE... FAIL" % path)
  89. # Check for /NXCOMPAT.
  90. if pe.OPTIONAL_HEADER.DllCharacteristics & NXCOMPAT_FLAG:
  91. if options.verbose:
  92. print("Checking %s for /NXCOMPAT... PASS" % path)
  93. else:
  94. success = False
  95. print("Checking %s for /NXCOMPAT... FAIL" % path)
  96. # Check for /SAFESEH. Binaries should meet one of the following
  97. # criteria:
  98. # 1) Have no SEH table as indicated by the DLL characteristics
  99. # 2) Have a LOAD_CONFIG section containing a valid SEH table
  100. # 3) Be a 64-bit binary, in which case /SAFESEH isn't required
  101. #
  102. # Refer to the following MSDN article for more information:
  103. # http://msdn.microsoft.com/en-us/library/9a89h429.aspx
  104. if (pe.OPTIONAL_HEADER.DllCharacteristics & NO_SEH_FLAG or
  105. (hasattr(pe, "DIRECTORY_ENTRY_LOAD_CONFIG") and
  106. pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerCount > 0 and
  107. pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.SEHandlerTable != 0) or
  108. pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64):
  109. if options.verbose:
  110. print("Checking %s for /SAFESEH... PASS" % path)
  111. else:
  112. success = False
  113. print("Checking %s for /SAFESEH... FAIL" % path)
  114. # ASLR is weakened on Windows 64-bit when the ImageBase is below 4GB
  115. # (because the loader will never be rebase the image above 4GB).
  116. if pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64:
  117. if pe.OPTIONAL_HEADER.ImageBase <= 0xFFFFFFFF:
  118. print("Checking %s ImageBase (0x%X < 4GB)... FAIL" %
  119. (path, pe.OPTIONAL_HEADER.ImageBase))
  120. success = False
  121. elif options.verbose:
  122. print("Checking %s ImageBase (0x%X > 4GB)... PASS" %
  123. (path, pe.OPTIONAL_HEADER.ImageBase))
  124. # Can only guarantee that files that are built by Chromium
  125. # are protected by /GUARD:CF. Some system libraries are not.
  126. if IsInDefaultFileGroup(path):
  127. # Check for /GUARD:CF.
  128. if pe.OPTIONAL_HEADER.DllCharacteristics & GUARD_CF_FLAG:
  129. if options.verbose:
  130. print("Checking %s for /GUARD:CF... PASS" % path)
  131. else:
  132. success = False
  133. print("Checking %s for /GUARD:CF... FAIL" % path)
  134. else:
  135. if options.verbose:
  136. print("Skipping check for /GUARD:CF for %s." % path)
  137. # Check cetcompat for x64 - debug directory type
  138. # IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS.
  139. if pe.FILE_HEADER.Machine == MACHINE_TYPE_AMD64:
  140. if IsInDefaultFileGroup(path) and IsCetExpected(path):
  141. found_cetcompat = False
  142. for dbg_ent in pe.DIRECTORY_ENTRY_DEBUG:
  143. if dbg_ent.struct.Type == pefile.DEBUG_TYPE[
  144. 'IMAGE_DEBUG_TYPE_EX_DLLCHARACTERISTICS']:
  145. # pefile does not read this, so access the raw data.
  146. ex_dll_offset = dbg_ent.struct.PointerToRawData
  147. ex_dll_length = dbg_ent.struct.SizeOfData
  148. ex_dll_char_data = pe.__data__[ex_dll_offset:ex_dll_offset +
  149. ex_dll_length]
  150. if IsBitSet(ex_dll_char_data, CETCOMPAT_BIT):
  151. found_cetcompat = True
  152. break # only one ex_dllcharacteristics so can stop once seen.
  153. if found_cetcompat:
  154. if options.verbose:
  155. print("Checking %s for /CETCOMPAT... PASS" % path)
  156. else:
  157. success = False
  158. print("Checking %s for /CETCOMPAT... FAIL" % path)
  159. else:
  160. if options.verbose:
  161. print("Skipping check for /CETCOMPAT for %s." % path)
  162. # Update tally.
  163. if success:
  164. pe_passed = pe_passed + 1
  165. else:
  166. failures.append(path)
  167. print("Result: %d files found, %d files passed" % (pe_total, pe_passed))
  168. if options.json:
  169. with open(options.json, 'w') as f:
  170. json.dump(failures, f)
  171. if pe_passed != pe_total:
  172. sys.exit(1)
  173. if __name__ == '__main__':
  174. usage = "Usage: %prog [options] DIRECTORY"
  175. option_parser = optparse.OptionParser(usage=usage)
  176. option_parser.add_option("-v", "--verbose", action="store_true",
  177. default=False, help="Print debug logging")
  178. option_parser.add_option("--json", help="Path to JSON output file")
  179. options, args = option_parser.parse_args()
  180. if not args:
  181. option_parser.print_help()
  182. sys.exit(0)
  183. main(options, args)