dash/contrib/devtools/symbol-check.py
merge-script 479cb8bcb8
Merge bitcoin/bitcoin#30312: contrib: add R(UN)PATH check to ELF symbol-check
4289dd02cce688a69c596f7cd5e47f831b00aa1b contrib: add R(UN)PATH check to ELF symbol-check (fanquake)

Pull request description:

  Our binaries shouldn't contain any rpaths, or runpaths, so check that at release time.

  Guix build (aarch64):
  ```bash
  14f1b54936f71aaf8fedb987e1c2f5642c34ac35f4856cdbd7bf7b4a9f42507c  guix-build-4289dd02cce6/output/aarch64-linux-gnu/SHA256SUMS.part
  b120503ac4a37c160aa1bdc662348a2a662cb9b3d0477daa177e92afacab6a27  guix-build-4289dd02cce6/output/aarch64-linux-gnu/bitcoin-4289dd02cce6-aarch64-linux-gnu-debug.tar.gz
  6416e3678aa13301ba2327e65dcd83afefd15d21c96c1b574cf616a65466a260  guix-build-4289dd02cce6/output/aarch64-linux-gnu/bitcoin-4289dd02cce6-aarch64-linux-gnu.tar.gz
  b7f11fa4abc034b32c208a9d642a0cafc0de693d49c8f3b28d1cf2d318af6826  guix-build-4289dd02cce6/output/arm-linux-gnueabihf/SHA256SUMS.part
  7cf6269025b10bc5ad65cd98baba68b4b65d3caabcd4d62e7af892bbb16c253f  guix-build-4289dd02cce6/output/arm-linux-gnueabihf/bitcoin-4289dd02cce6-arm-linux-gnueabihf-debug.tar.gz
  c136665417cc1f9254f4541c1161ea6d24d2325e4baa2dd4a306f67a1edf9c20  guix-build-4289dd02cce6/output/arm-linux-gnueabihf/bitcoin-4289dd02cce6-arm-linux-gnueabihf.tar.gz
  37dd0359e073cbb38dcd6e17953d31e414ce27516c31b18ef6aa1bc9530dbed2  guix-build-4289dd02cce6/output/arm64-apple-darwin/SHA256SUMS.part
  3d01f5db06e1b1cbebc5a5a4277ad3da8911afe35e111d4650e2beee6038cfdb  guix-build-4289dd02cce6/output/arm64-apple-darwin/bitcoin-4289dd02cce6-arm64-apple-darwin-unsigned.tar.gz
  9d8ad7cbdc1e154b33c138de21e7a0ffc8a8857b522db9272c70d5c10732460c  guix-build-4289dd02cce6/output/arm64-apple-darwin/bitcoin-4289dd02cce6-arm64-apple-darwin-unsigned.zip
  23f14eeaeac2e881e41543a796dfa65f132e89153281f9fbcb8019d08fff7eb2  guix-build-4289dd02cce6/output/arm64-apple-darwin/bitcoin-4289dd02cce6-arm64-apple-darwin.tar.gz
  b62d9e202e5414ebefe129e733beaee76b4f9b05a7dd20807fbbdbe24f27f692  guix-build-4289dd02cce6/output/dist-archive/bitcoin-4289dd02cce6.tar.gz
  1301b33745b9016bbb7f038ee2004f630244deaa6575b6c75391cda2d8d3455d  guix-build-4289dd02cce6/output/powerpc64-linux-gnu/SHA256SUMS.part
  54e988c3d3fc204d4b232dcf24df8a80733c46e3adf9ea0c81a1637609fc1131  guix-build-4289dd02cce6/output/powerpc64-linux-gnu/bitcoin-4289dd02cce6-powerpc64-linux-gnu-debug.tar.gz
  6acb3348d67d3e96a6f3a6e782ae088532b75d4b38cbfe249e114e07eafefdb6  guix-build-4289dd02cce6/output/powerpc64-linux-gnu/bitcoin-4289dd02cce6-powerpc64-linux-gnu.tar.gz
  cc30cfe72eefa498f347d825c57a19501d0c24330d2b4bcdfa432cf7a17880e8  guix-build-4289dd02cce6/output/riscv64-linux-gnu/SHA256SUMS.part
  a56144f8a816c6b62970c29d6c805fe7d26c4d1171e96379e1e6a53c7cb504f6  guix-build-4289dd02cce6/output/riscv64-linux-gnu/bitcoin-4289dd02cce6-riscv64-linux-gnu-debug.tar.gz
  7d67eac59846d478b5ecefa1396d3214c2f9b394ec4de4c099b2b2304750e13e  guix-build-4289dd02cce6/output/riscv64-linux-gnu/bitcoin-4289dd02cce6-riscv64-linux-gnu.tar.gz
  2d1b1ec32f566ad37b7fb6e2d8b150389c0d02013fc99b12e9e93cb938cfdbc7  guix-build-4289dd02cce6/output/x86_64-apple-darwin/SHA256SUMS.part
  1bb7a360974cc839499f82cf00dc6e59c3584a3094382086217eacb97b6da931  guix-build-4289dd02cce6/output/x86_64-apple-darwin/bitcoin-4289dd02cce6-x86_64-apple-darwin-unsigned.tar.gz
  821a1587e285a8514a9f2ed9bec7d422e91d5f1db3cbb9e4c5ce4467298e692f  guix-build-4289dd02cce6/output/x86_64-apple-darwin/bitcoin-4289dd02cce6-x86_64-apple-darwin-unsigned.zip
  15956787628f5014adc53fe98fc995c56424c8e6c1e698283f65099751a61dbd  guix-build-4289dd02cce6/output/x86_64-apple-darwin/bitcoin-4289dd02cce6-x86_64-apple-darwin.tar.gz
  76e8a8275f27a5d2b4eb29c0440c5dfbecb0d9348362ff7defe989820d5ec80b  guix-build-4289dd02cce6/output/x86_64-linux-gnu/SHA256SUMS.part
  3c8b19f1a130ebb12a1ca4103fa283e71d909f09da5cef8047db1e7e7dfea162  guix-build-4289dd02cce6/output/x86_64-linux-gnu/bitcoin-4289dd02cce6-x86_64-linux-gnu-debug.tar.gz
  87b3735cda595556e8adfd4de5c9cc966e9996cdb76b8f9dfa8c978d37397d40  guix-build-4289dd02cce6/output/x86_64-linux-gnu/bitcoin-4289dd02cce6-x86_64-linux-gnu.tar.gz
  fc535f76e6323fb0feeeee790865d0d2c8ea9d2c85d72a620acfb83546fb7628  guix-build-4289dd02cce6/output/x86_64-w64-mingw32/SHA256SUMS.part
  9fd5d3b4e91fa8c4d137c9f8d2089c81f4c4165bb6ab9a404b163f7b363587fd  guix-build-4289dd02cce6/output/x86_64-w64-mingw32/bitcoin-4289dd02cce6-win64-debug.zip
  b4658cdc94e70cd7b810a44770684b742d96d7d40d720cc54f209784e0ad4ad6  guix-build-4289dd02cce6/output/x86_64-w64-mingw32/bitcoin-4289dd02cce6-win64-setup-unsigned.exe
  e4557d1f8c1b0eb5014ad56d2a17a0a618c3ba7e6d073628e633382e1d4191aa  guix-build-4289dd02cce6/output/x86_64-w64-mingw32/bitcoin-4289dd02cce6-win64-unsigned.tar.gz
  3f9a104633f6962b0b8efb64a7d6be453f56617561e7f5b49a84ae191942ea5d  guix-build-4289dd02cce6/output/x86_64-w64-mingw32/bitcoin-4289dd02cce6-win64.zip
  ```

ACKs for top commit:
  theuni:
    utACK 4289dd02cce688a69c596f7cd5e47f831b00aa1b
  hebasto:
    ACK 4289dd02cce688a69c596f7cd5e47f831b00aa1b.

Tree-SHA512: f2e1f6525f699e0d069c70f5e7f1a7e8f8758dcdaff778d49e671bc106a9592f9a7b25c4dbeeaf92b654646fd3b902ad45c8547fd1b28e3287933e8c1497333d
2024-10-26 19:14:05 -05:00

323 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright (c) 2014 Wladimir J. van der Laan
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
'''
A script to check that the (Linux) release executables only contain
certain symbols and are only linked against allowed libraries.
Example usage:
find ../path/to/binaries -type f -executable | xargs python3 contrib/devtools/symbol-check.py
'''
import sys
from typing import Dict, List
import lief
# Debian 11 (Bullseye) EOL: est. 2026 https://wiki.debian.org/LTS
#
# - libgcc version 10.2.1 (https://packages.debian.org/search?suite=bullseye&arch=any&searchon=names&keywords=libgcc-s1)
# - libc version 2.31 (https://packages.debian.org/search?suite=bullseye&arch=any&searchon=names&keywords=libc6)
#
# Ubuntu 20.04 (Focal) EOL: 2030. https://wiki.ubuntu.com/ReleaseTeam
#
# - libgcc version 10.3.0 (https://packages.ubuntu.com/focal/libgcc1)
# - libc version 2.31 (https://packages.ubuntu.com/focal/libc6)
#
# CentOS Stream 9 EOL: est. 2027 https://www.centos.org/cl-vs-cs
#
# - libgcc version 12.2.1 (https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/Packages)
# - libc version 2.34 (https://mirror.stream.centos.org/9-stream/AppStream/x86_64/os/Packages)
#
# See https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html for more info.
MAX_VERSIONS = {
'GCC': (4,3,0),
'GLIBC': {
lief.ELF.ARCH.x86_64: (2,31),
lief.ELF.ARCH.ARM: (2,31),
lief.ELF.ARCH.AARCH64:(2,31),
lief.ELF.ARCH.PPC64: (2,31),
lief.ELF.ARCH.RISCV: (2,31),
},
'LIBATOMIC': (1,0),
'V': (0,5,0), # xkb (bitcoin-qt only)
}
# Ignore symbols that are exported as part of every executable
IGNORE_EXPORTS = {
'_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__',
'__bss_end__', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr',
'environ', '_environ', '__environ',
# Used in stacktraces.cpp
'__cxa_demangle'
}
# Expected linker-loader names can be found here:
# https://sourceware.org/glibc/wiki/ABIList?action=recall&rev=16
ELF_INTERPRETER_NAMES: Dict[lief.ELF.ARCH, Dict[lief.ENDIANNESS, str]] = {
lief.ELF.ARCH.x86_64: {
lief.ENDIANNESS.LITTLE: "/lib64/ld-linux-x86-64.so.2",
},
lief.ELF.ARCH.ARM: {
lief.ENDIANNESS.LITTLE: "/lib/ld-linux-armhf.so.3",
},
lief.ELF.ARCH.AARCH64: {
lief.ENDIANNESS.LITTLE: "/lib/ld-linux-aarch64.so.1",
},
lief.ELF.ARCH.PPC64: {
lief.ENDIANNESS.BIG: "/lib64/ld64.so.1",
lief.ENDIANNESS.LITTLE: "/lib64/ld64.so.2",
},
lief.ELF.ARCH.RISCV: {
lief.ENDIANNESS.LITTLE: "/lib/ld-linux-riscv64-lp64d.so.1",
},
}
ELF_ABIS: Dict[lief.ELF.ARCH, Dict[lief.ENDIANNESS, List[int]]] = {
lief.ELF.ARCH.x86_64: {
lief.ENDIANNESS.LITTLE: [3,2,0],
},
lief.ELF.ARCH.ARM: {
lief.ENDIANNESS.LITTLE: [3,2,0],
},
lief.ELF.ARCH.AARCH64: {
lief.ENDIANNESS.LITTLE: [3,7,0],
},
lief.ELF.ARCH.PPC64: {
lief.ENDIANNESS.LITTLE: [3,10,0],
lief.ENDIANNESS.BIG: [3,2,0],
},
lief.ELF.ARCH.RISCV: {
lief.ENDIANNESS.LITTLE: [4,15,0],
},
}
# Allowed NEEDED libraries
ELF_ALLOWED_LIBRARIES = {
# dashd and dash-qt
'libgcc_s.so.1', # GCC base support
'libc.so.6', # C library
'libpthread.so.0', # threading
'libm.so.6', # math library
'librt.so.1', # real-time (clock)
'libatomic.so.1',
'ld-linux-x86-64.so.2', # 64-bit dynamic linker
'ld-linux.so.2', # 32-bit dynamic linker
'ld-linux-aarch64.so.1', # 64-bit ARM dynamic linker
'ld-linux-armhf.so.3', # 32-bit ARM dynamic linker
'ld64.so.1', # POWER64 ABIv1 dynamic linker
'ld64.so.2', # POWER64 ABIv2 dynamic linker
'ld-linux-riscv64-lp64d.so.1', # 64-bit RISC-V dynamic linker
'libz.so.1', # zlib
# dash-qt only
'libxcb.so.1', # part of X11
'libxcb-shm.so.0', # X11 shared memory extension
'libxkbcommon.so.0', # keyboard keymapping
'libxkbcommon-x11.so.0', # keyboard keymapping
'libfontconfig.so.1', # font support
'libfreetype.so.6', # font parsing
'libdl.so.2', # programming interface to dynamic linker
'libxcb-icccm.so.4',
'libxcb-image.so.0',
'libxcb-shm.so.0',
'libxcb-keysyms.so.1',
'libxcb-randr.so.0',
'libxcb-render-util.so.0',
'libxcb-render.so.0',
'libxcb-shape.so.0',
'libxcb-sync.so.1',
'libxcb-xfixes.so.0',
'libxcb-xinerama.so.0',
'libxcb-xkb.so.1',
}
MACHO_ALLOWED_LIBRARIES = {
# bitcoind and bitcoin-qt
'libc++.1.dylib', # C++ Standard Library
'libSystem.B.dylib', # libc, libm, libpthread, libinfo
# bitcoin-qt only
'AppKit', # user interface
'ApplicationServices', # common application tasks.
'Carbon', # deprecated c back-compat API
'ColorSync',
'CoreFoundation', # low level func, data types
'CoreGraphics', # 2D rendering
'CoreServices', # operating system services
'CoreText', # interface for laying out text and handling fonts.
'CoreVideo', # video processing
'Foundation', # base layer functionality for apps/frameworks
'ImageIO', # read and write image file formats.
'IOKit', # user-space access to hardware devices and drivers.
'IOSurface', # cross process image/drawing buffers
'libobjc.A.dylib', # Objective-C runtime library
'Metal', # 3D graphics
'Security', # access control and authentication
'QuartzCore', # animation
}
PE_ALLOWED_LIBRARIES = {
'ADVAPI32.dll', # security & registry
'IPHLPAPI.DLL', # IP helper API
'KERNEL32.dll', # win32 base APIs
'msvcrt.dll', # C standard library for MSVC
'SHELL32.dll', # shell API
'WS2_32.dll', # sockets
'bcrypt.dll',
# bitcoin-qt only
'dwmapi.dll', # desktop window manager
'GDI32.dll', # graphics device interface
'IMM32.dll', # input method editor
'NETAPI32.dll', # network management
'ole32.dll', # component object model
'OLEAUT32.dll', # OLE Automation API
'SHLWAPI.dll', # light weight shell API
'USER32.dll', # user interface
'USERENV.dll', # user management
'UxTheme.dll', # visual style
'VERSION.dll', # version checking
'WINMM.dll', # WinMM audio API
'WTSAPI32.dll', # Remote Desktop
}
def check_version(max_versions, version, arch) -> bool:
(lib, _, ver) = version.rpartition('_')
ver = tuple([int(x) for x in ver.split('.')])
if not lib in max_versions:
return False
if isinstance(max_versions[lib], tuple):
return ver <= max_versions[lib]
else:
return ver <= max_versions[lib][arch]
def check_imported_symbols(binary) -> bool:
ok: bool = True
for symbol in binary.imported_symbols:
if not symbol.imported:
continue
version = symbol.symbol_version if symbol.has_version else None
if version:
aux_version = version.symbol_version_auxiliary.name if version.has_auxiliary_version else None
if aux_version and not check_version(MAX_VERSIONS, aux_version, binary.header.machine_type):
print(f'{filename}: symbol {symbol.name} from unsupported version {version}')
ok = False
return ok
def check_exported_symbols(binary) -> bool:
ok: bool = True
for symbol in binary.dynamic_symbols:
if not symbol.exported:
continue
name = symbol.name
if binary.header.machine_type == lief.ELF.ARCH.RISCV or name in IGNORE_EXPORTS:
continue
print(f'{binary.name}: export of symbol {name} not allowed!')
ok = False
return ok
def check_RUNPATH(binary) -> bool:
assert binary.get(lief.ELF.DYNAMIC_TAGS.RUNPATH) is None
assert binary.get(lief.ELF.DYNAMIC_TAGS.RPATH) is None
return True
def check_ELF_libraries(binary) -> bool:
ok: bool = True
for library in binary.libraries:
if library not in ELF_ALLOWED_LIBRARIES:
print(f'{filename}: {library} is not in ALLOWED_LIBRARIES!')
ok = False
return ok
def check_MACHO_libraries(binary) -> bool:
ok: bool = True
for dylib in binary.libraries:
split = dylib.name.split('/')
if split[-1] not in MACHO_ALLOWED_LIBRARIES:
print(f'{split[-1]} is not in ALLOWED_LIBRARIES!')
ok = False
return ok
def check_MACHO_min_os(binary) -> bool:
if binary.build_version.minos == [10,15,0]:
return True
return False
def check_MACHO_sdk(binary) -> bool:
if binary.build_version.sdk == [11, 0, 0]:
return True
return False
def check_PE_libraries(binary) -> bool:
ok: bool = True
for dylib in binary.libraries:
if dylib not in PE_ALLOWED_LIBRARIES:
print(f'{dylib} is not in ALLOWED_LIBRARIES!')
ok = False
return ok
def check_PE_subsystem_version(binary) -> bool:
major: int = binary.optional_header.major_subsystem_version
minor: int = binary.optional_header.minor_subsystem_version
if major == 6 and minor == 1:
return True
return False
def check_ELF_interpreter(binary) -> bool:
expected_interpreter = ELF_INTERPRETER_NAMES[binary.header.machine_type][binary.abstract.header.endianness]
return binary.concrete.interpreter == expected_interpreter
def check_ELF_ABI(binary) -> bool:
expected_abi = ELF_ABIS[binary.header.machine_type][binary.abstract.header.endianness]
note = binary.concrete.get(lief.ELF.NOTE_TYPES.ABI_TAG)
assert note.details.abi == lief.ELF.NOTE_ABIS.LINUX
return note.details.version == expected_abi
CHECKS = {
lief.EXE_FORMATS.ELF: [
('IMPORTED_SYMBOLS', check_imported_symbols),
('EXPORTED_SYMBOLS', check_exported_symbols),
('LIBRARY_DEPENDENCIES', check_ELF_libraries),
('INTERPRETER_NAME', check_ELF_interpreter),
('ABI', check_ELF_ABI),
('RUNPATH', check_RUNPATH),
],
lief.EXE_FORMATS.MACHO: [
('DYNAMIC_LIBRARIES', check_MACHO_libraries),
('MIN_OS', check_MACHO_min_os),
('SDK', check_MACHO_sdk),
],
lief.EXE_FORMATS.PE: [
('DYNAMIC_LIBRARIES', check_PE_libraries),
('SUBSYSTEM_VERSION', check_PE_subsystem_version),
]
}
if __name__ == '__main__':
retval: int = 0
for filename in sys.argv[1:]:
try:
binary = lief.parse(filename)
etype = binary.format
if etype == lief.EXE_FORMATS.UNKNOWN:
print(f'{filename}: unknown executable format')
retval = 1
continue
failed: List[str] = []
for (name, func) in CHECKS[etype]:
if not func(binary):
failed.append(name)
if failed:
print(f'{filename}: failed {" ".join(failed)}')
retval = 1
except IOError:
print(f'{filename}: cannot open')
retval = 1
sys.exit(retval)