symbol-check.py raw

   1  #!/usr/bin/env python3
   2  # Copyright (c) 2014 Wladimir J. van der Laan
   3  # Distributed under the MIT software license, see the accompanying
   4  # file COPYING or http://www.opensource.org/licenses/mit-license.php.
   5  '''
   6  A script to check that release executables only contain certain symbols
   7  and are only linked against allowed libraries.
   8  
   9  Example usage:
  10  
  11      find ../path/to/guix/binaries -type f -executable | xargs python3 contrib/guix/symbol-check.py
  12  '''
  13  import sys
  14  
  15  import lief
  16  
  17  # Debian 11 (Bullseye) EOL: 2026. https://wiki.debian.org/LTS
  18  #
  19  # - libc version 2.31 (https://packages.debian.org/source/bullseye/glibc)
  20  #
  21  # Ubuntu 20.04 (Focal) EOL: 2030. https://wiki.ubuntu.com/ReleaseTeam
  22  #
  23  # - libc version 2.31 (https://packages.ubuntu.com/focal/libc6)
  24  #
  25  # CentOS Stream 9 EOL: 2027. https://www.centos.org/cl-vs-cs/#end-of-life
  26  #
  27  # - libc version 2.34 (https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/Packages/)
  28  #
  29  # bitcoin-qt
  30  #
  31  # Ubuntu 22.04 is currently the baseline for ELF_ALLOWED_LIBRARIES:
  32  #
  33  # libfontconfig version 2.13.1 (https://packages.ubuntu.com/jammy/libfontconfig1)
  34  #
  35  # libfreetype version 2.11.1 (https://packages.ubuntu.com/jammy/libfreetype6)
  36  
  37  MAX_VERSIONS = {
  38  'GLIBC': {
  39      lief.ELF.ARCH.X86_64: (2,31),
  40      lief.ELF.ARCH.ARM:    (2,31),
  41      lief.ELF.ARCH.AARCH64:(2,31),
  42      lief.ELF.ARCH.PPC64:  (2,31),
  43      lief.ELF.ARCH.RISCV:  (2,31),
  44      }
  45  }
  46  
  47  # Ignore symbols that are exported as part of every executable
  48  IGNORE_EXPORTS = {
  49  'stdin', 'stdout', 'stderr',
  50  }
  51  
  52  # Expected linker-loader names can be found here:
  53  # https://sourceware.org/glibc/wiki/ABIList?action=recall&rev=16
  54  ELF_INTERPRETER_NAMES: dict[lief.ELF.ARCH, dict[lief.Header.ENDIANNESS, str]] = {
  55      lief.ELF.ARCH.X86_64:  {
  56          lief.Header.ENDIANNESS.LITTLE: "/lib64/ld-linux-x86-64.so.2",
  57      },
  58      lief.ELF.ARCH.ARM:     {
  59          lief.Header.ENDIANNESS.LITTLE: "/lib/ld-linux-armhf.so.3",
  60      },
  61      lief.ELF.ARCH.AARCH64: {
  62          lief.Header.ENDIANNESS.LITTLE: "/lib/ld-linux-aarch64.so.1",
  63      },
  64      lief.ELF.ARCH.PPC64:   {
  65          lief.Header.ENDIANNESS.BIG: "/lib64/ld64.so.1",
  66          lief.Header.ENDIANNESS.LITTLE: "/lib64/ld64.so.2",
  67      },
  68      lief.ELF.ARCH.RISCV:    {
  69          lief.Header.ENDIANNESS.LITTLE: "/lib/ld-linux-riscv64-lp64d.so.1",
  70      },
  71  }
  72  
  73  ELF_ABIS: dict[lief.ELF.ARCH, dict[lief.Header.ENDIANNESS, list[int]]] = {
  74      lief.ELF.ARCH.X86_64: {
  75          lief.Header.ENDIANNESS.LITTLE: [3,2,0],
  76      },
  77      lief.ELF.ARCH.ARM: {
  78          lief.Header.ENDIANNESS.LITTLE: [3,2,0],
  79      },
  80      lief.ELF.ARCH.AARCH64: {
  81          lief.Header.ENDIANNESS.LITTLE: [3,7,0],
  82      },
  83      lief.ELF.ARCH.PPC64: {
  84          lief.Header.ENDIANNESS.LITTLE: [3,10,0],
  85          lief.Header.ENDIANNESS.BIG: [3,2,0],
  86      },
  87      lief.ELF.ARCH.RISCV: {
  88          lief.Header.ENDIANNESS.LITTLE: [4,15,0],
  89      },
  90  }
  91  
  92  # Allowed NEEDED libraries
  93  ELF_ALLOWED_LIBRARIES = {
  94  # bitcoind and bitcoin-qt
  95  'libc.so.6', # C library
  96  'libpthread.so.0', # threading
  97  'libm.so.6', # math library
  98  'ld-linux-x86-64.so.2', # 64-bit dynamic linker
  99  'ld-linux.so.2', # 32-bit dynamic linker
 100  'ld-linux-aarch64.so.1', # 64-bit ARM dynamic linker
 101  'ld-linux-armhf.so.3', # 32-bit ARM dynamic linker
 102  'ld64.so.1', # POWER64 ABIv1 dynamic linker
 103  'ld64.so.2', # POWER64 ABIv2 dynamic linker
 104  'ld-linux-riscv64-lp64d.so.1', # 64-bit RISC-V dynamic linker
 105  # bitcoin-qt only
 106  'libfontconfig.so.1', # font support
 107  'libfreetype.so.6', # font parsing
 108  'libdl.so.2', # programming interface to dynamic linker
 109  }
 110  
 111  MACHO_ALLOWED_LIBRARIES = {
 112  # bitcoind and bitcoin-qt
 113  'libc++.1.dylib', # C++ Standard Library
 114  'libSystem.B.dylib', # libc, libm, libpthread, libinfo
 115  # bitcoin-qt only
 116  'AppKit', # user interface
 117  'ApplicationServices', # common application tasks.
 118  'Carbon', # deprecated c back-compat API
 119  'ColorSync',
 120  'CoreFoundation', # low level func, data types
 121  'CoreGraphics', # 2D rendering
 122  'CoreServices', # operating system services
 123  'CoreText', # interface for laying out text and handling fonts.
 124  'CoreVideo', # video processing
 125  'Foundation', # base layer functionality for apps/frameworks
 126  'ImageIO', # read and write image file formats.
 127  'IOKit', # user-space access to hardware devices and drivers.
 128  'IOSurface', # cross process image/drawing buffers
 129  'libobjc.A.dylib', # Objective-C runtime library
 130  'Metal', # 3D graphics
 131  'QuartzCore', # animation
 132  'Security', # access control and authentication
 133  'UniformTypeIdentifiers', # collection of types that map to MIME and file types
 134  }
 135  
 136  PE_ALLOWED_LIBRARIES = {
 137  'ADVAPI32.dll', # legacy security & registry
 138  'bcrypt.dll', # newer security and identity API
 139  'IPHLPAPI.DLL', # IP helper API
 140  'KERNEL32.dll', # win32 base APIs
 141  'msvcrt.dll', # C standard library for MSVC
 142  'SHELL32.dll', # shell API
 143  'WS2_32.dll', # sockets
 144  # bitcoin-qt only
 145  'api-ms-win-core-synch-l1-2-0.dll', # Synchronization Primitives API
 146  'api-ms-win-core-winrt-l1-1-0.dll', # Windows Runtime API
 147  'api-ms-win-core-winrt-string-l1-1-0.dll', # WinRT String API
 148  'AUTHZ.dll', # Windows Authorization Framework
 149  'comdlg32.dll', # Common Dialog Box Library
 150  'd3d11.dll', # Direct3D 11 API
 151  'd3d12.dll', # Direct3D 12 API
 152  'd3d9.dll', # Direct3D 9 API
 153  'dwmapi.dll', # desktop window manager
 154  'DWrite.dll', # DirectX Typography Services
 155  'dxgi.dll', # DirectX Graphics Infrastructure
 156  'GDI32.dll', # graphics device interface
 157  'IMM32.dll', # input method editor
 158  'NETAPI32.dll', # network management
 159  'ole32.dll', # component object model
 160  'OLEAUT32.dll', # OLE Automation API
 161  'SHLWAPI.dll', # light weight shell API
 162  'USER32.dll', # user interface
 163  'USERENV.dll', # user management
 164  'UxTheme.dll', # visual style
 165  'VERSION.dll', # version checking
 166  'WINMM.dll', # WinMM audio API
 167  'WTSAPI32.dll', # Remote Desktop
 168  'SETUPAPI.dll', # Windows Setup API
 169  'SHCORE.dll', # Stream Handler Core
 170  }
 171  
 172  def check_version(max_versions, version, arch) -> bool:
 173      (lib, _, ver) = version.rpartition('_')
 174      ver = tuple([int(x) for x in ver.split('.')])
 175      if lib not in max_versions:
 176          return False
 177      if isinstance(max_versions[lib], tuple):
 178          return ver <= max_versions[lib]
 179      else:
 180          return ver <= max_versions[lib][arch]
 181  
 182  def check_imported_symbols(binary) -> bool:
 183      ok: bool = True
 184  
 185      for symbol in binary.imported_symbols:
 186          if not symbol.imported:
 187              continue
 188  
 189          version = symbol.symbol_version if symbol.has_version else None
 190  
 191          if version:
 192              aux_version = version.symbol_version_auxiliary.name if version.has_auxiliary_version else None
 193              if aux_version and not check_version(MAX_VERSIONS, aux_version, binary.header.machine_type):
 194                  print(f'{filename}: symbol {symbol.name} from unsupported version {version}')
 195                  ok = False
 196      return ok
 197  
 198  def check_exported_symbols(binary) -> bool:
 199      ok: bool = True
 200  
 201      for symbol in binary.dynamic_symbols:
 202          if not symbol.exported:
 203              continue
 204          name = symbol.name
 205          if name in IGNORE_EXPORTS:
 206              continue
 207          print(f'{filename}: export of symbol {name} not allowed!')
 208          ok = False
 209      return ok
 210  
 211  def check_RUNPATH(binary) -> bool:
 212      assert binary.get(lief.ELF.DynamicEntry.TAG.RUNPATH) is None
 213      assert binary.get(lief.ELF.DynamicEntry.TAG.RPATH) is None
 214      return True
 215  
 216  def check_ELF_libraries(binary) -> bool:
 217      ok: bool = True
 218      for library in binary.libraries:
 219          if library not in ELF_ALLOWED_LIBRARIES:
 220              print(f'{filename}: {library} is not in ALLOWED_LIBRARIES!')
 221              ok = False
 222      return ok
 223  
 224  def check_MACHO_libraries(binary) -> bool:
 225      ok: bool = True
 226      for dylib in binary.libraries:
 227          split = dylib.name.split('/')
 228          if split[-1] not in MACHO_ALLOWED_LIBRARIES:
 229              print(f'{split[-1]} is not in ALLOWED_LIBRARIES!')
 230              ok = False
 231      return ok
 232  
 233  def check_MACHO_min_os(binary) -> bool:
 234      if binary.build_version.minos == [14,0,0]:
 235          return True
 236      return False
 237  
 238  def check_MACHO_sdk(binary) -> bool:
 239      if binary.build_version.sdk == [14, 0, 0]:
 240          return True
 241      return False
 242  
 243  def check_MACHO_lld(binary) -> bool:
 244      if binary.build_version.tools[0].version == [19, 1, 7]:
 245          return True
 246      return False
 247  
 248  def check_PE_libraries(binary) -> bool:
 249      ok: bool = True
 250      for dylib in binary.libraries:
 251          if dylib not in PE_ALLOWED_LIBRARIES:
 252              print(f'{dylib} is not in ALLOWED_LIBRARIES!')
 253              ok = False
 254      return ok
 255  
 256  def check_PE_subsystem_version(binary) -> bool:
 257      major: int = binary.optional_header.major_subsystem_version
 258      minor: int = binary.optional_header.minor_subsystem_version
 259      if major == 6 and minor == 2:
 260          return True
 261      return False
 262  
 263  def check_PE_application_manifest(binary) -> bool:
 264      if not binary.has_resources:
 265          # No resources at all.
 266          return False
 267  
 268      rm = binary.resources_manager
 269      return rm.has_manifest
 270  
 271  def check_ELF_interpreter(binary) -> bool:
 272      expected_interpreter = ELF_INTERPRETER_NAMES[binary.header.machine_type][binary.abstract.header.endianness]
 273  
 274      return binary.concrete.interpreter == expected_interpreter
 275  
 276  def check_ELF_ABI(binary) -> bool:
 277      expected_abi = ELF_ABIS[binary.header.machine_type][binary.abstract.header.endianness]
 278      note = binary.concrete.get(lief.ELF.Note.TYPE.GNU_ABI_TAG)
 279      assert note.abi == lief.ELF.NoteAbi.ABI.LINUX
 280      return note.version == expected_abi
 281  
 282  CHECKS = {
 283  lief.Binary.FORMATS.ELF: [
 284      ('IMPORTED_SYMBOLS', check_imported_symbols),
 285      ('EXPORTED_SYMBOLS', check_exported_symbols),
 286      ('LIBRARY_DEPENDENCIES', check_ELF_libraries),
 287      ('INTERPRETER_NAME', check_ELF_interpreter),
 288      ('ABI', check_ELF_ABI),
 289      ('RUNPATH', check_RUNPATH),
 290  ],
 291  lief.Binary.FORMATS.MACHO: [
 292      ('DYNAMIC_LIBRARIES', check_MACHO_libraries),
 293      ('MIN_OS', check_MACHO_min_os),
 294      ('SDK', check_MACHO_sdk),
 295      ('LLD', check_MACHO_lld),
 296  ],
 297  lief.Binary.FORMATS.PE: [
 298      ('DYNAMIC_LIBRARIES', check_PE_libraries),
 299      ('SUBSYSTEM_VERSION', check_PE_subsystem_version),
 300      ('APPLICATION_MANIFEST', check_PE_application_manifest),
 301  ]
 302  }
 303  
 304  if __name__ == '__main__':
 305      retval: int = 0
 306      for filename in sys.argv[1:]:
 307          binary = lief.parse(filename)
 308  
 309          etype = binary.format
 310  
 311          failed: list[str] = []
 312          for (name, func) in CHECKS[etype]:
 313              if not func(binary):
 314                  failed.append(name)
 315          if failed:
 316              print(f'{filename}: failed {" ".join(failed)}')
 317              retval = 1
 318      sys.exit(retval)
 319