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