Skip to content

Commit 02d0bce

Browse files
committed
Add contrib/symbol-check.py
1 parent 694ce8f commit 02d0bce

File tree

1 file changed

+256
-0
lines changed

1 file changed

+256
-0
lines changed

contrib/symbol-check.py

Lines changed: 256 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
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 the secp256k1 shared library only contain certain symbols
7+
and are only linked against allowed libraries.
8+
9+
Example usage:
10+
11+
contrib/symbol-check.py .libs/libsecp256k1.so.0.0.0
12+
or
13+
contrib/symbol-check.py .libs/libsecp256k1-0.dll
14+
'''
15+
import sys
16+
from typing import List, Dict
17+
18+
import lief #type:ignore
19+
20+
MAX_VERSIONS = {
21+
'GLIBC': {
22+
lief.ELF.ARCH.x86_64: (2,4),
23+
lief.ELF.ARCH.ARM: (2,4),
24+
lief.ELF.ARCH.AARCH64:(2,17),
25+
lief.ELF.ARCH.PPC64: (2,17),
26+
lief.ELF.ARCH.RISCV: (2,27),
27+
},
28+
}
29+
30+
# Symbols being expected to be exported by the secp256k1 shared library.
31+
EXPECTED_EXPORTS = {
32+
'secp256k1_context_clone',
33+
'secp256k1_context_create',
34+
'secp256k1_context_destroy',
35+
'secp256k1_context_no_precomp',
36+
'secp256k1_context_preallocated_clone',
37+
'secp256k1_context_preallocated_clone_size',
38+
'secp256k1_context_preallocated_create',
39+
'secp256k1_context_preallocated_destroy',
40+
'secp256k1_context_preallocated_size',
41+
'secp256k1_context_randomize',
42+
'secp256k1_context_set_error_callback',
43+
'secp256k1_context_set_illegal_callback',
44+
'secp256k1_ec_privkey_negate',
45+
'secp256k1_ec_privkey_tweak_add',
46+
'secp256k1_ec_privkey_tweak_mul',
47+
'secp256k1_ec_pubkey_cmp',
48+
'secp256k1_ec_pubkey_combine',
49+
'secp256k1_ec_pubkey_create',
50+
'secp256k1_ec_pubkey_negate',
51+
'secp256k1_ec_pubkey_parse',
52+
'secp256k1_ec_pubkey_serialize',
53+
'secp256k1_ec_pubkey_tweak_add',
54+
'secp256k1_ec_pubkey_tweak_mul',
55+
'secp256k1_ec_seckey_negate',
56+
'secp256k1_ec_seckey_tweak_add',
57+
'secp256k1_ec_seckey_tweak_mul',
58+
'secp256k1_ec_seckey_verify',
59+
'secp256k1_ecdsa_sign',
60+
'secp256k1_ecdsa_signature_normalize',
61+
'secp256k1_ecdsa_signature_parse_compact',
62+
'secp256k1_ecdsa_signature_parse_der',
63+
'secp256k1_ecdsa_signature_serialize_compact',
64+
'secp256k1_ecdsa_signature_serialize_der',
65+
'secp256k1_ecdsa_verify',
66+
'secp256k1_nonce_function_default',
67+
'secp256k1_nonce_function_rfc6979',
68+
'secp256k1_scratch_space_create',
69+
'secp256k1_scratch_space_destroy',
70+
'secp256k1_tagged_sha256',
71+
# ECDH module:
72+
'secp256k1_ecdh',
73+
'secp256k1_ecdh_hash_function_default',
74+
'secp256k1_ecdh_hash_function_sha256',
75+
# ECDSA pubkey recovery module:
76+
'secp256k1_ecdsa_recover',
77+
'secp256k1_ecdsa_recoverable_signature_convert',
78+
'secp256k1_ecdsa_recoverable_signature_parse_compact',
79+
'secp256k1_ecdsa_recoverable_signature_serialize_compact',
80+
'secp256k1_ecdsa_sign_recoverable',
81+
# extrakeys module:
82+
'secp256k1_keypair_create',
83+
'secp256k1_keypair_pub',
84+
'secp256k1_keypair_sec',
85+
'secp256k1_keypair_xonly_pub',
86+
'secp256k1_keypair_xonly_tweak_add',
87+
'secp256k1_xonly_pubkey_cmp',
88+
'secp256k1_xonly_pubkey_from_pubkey',
89+
'secp256k1_xonly_pubkey_parse',
90+
'secp256k1_xonly_pubkey_serialize',
91+
'secp256k1_xonly_pubkey_tweak_add',
92+
'secp256k1_xonly_pubkey_tweak_add_check',
93+
# schnorrsig module:
94+
'secp256k1_nonce_function_bip340',
95+
'secp256k1_schnorrsig_sign',
96+
'secp256k1_schnorrsig_sign32',
97+
'secp256k1_schnorrsig_sign_custom',
98+
'secp256k1_schnorrsig_verify',
99+
}
100+
101+
# Allowed NEEDED libraries
102+
ELF_ALLOWED_LIBRARIES = {
103+
'libc.so.6', # C library
104+
'ld-linux-aarch64.so.1', # 64-bit ARM dynamic linker
105+
'ld-linux-armhf.so.3', # 32-bit ARM dynamic linker
106+
'ld-linux-riscv64-lp64d.so.1', # 64-bit RISC-V dynamic linker
107+
}
108+
109+
MACHO_ALLOWED_LIBRARIES = {
110+
'libsecp256k1.0.dylib',
111+
'libSystem.B.dylib', # libc, libm, libpthread, libinfo
112+
}
113+
114+
PE_ALLOWED_LIBRARIES = {
115+
'KERNEL32.dll', # win32 base APIs
116+
'msvcrt.dll', # C standard library for MSVC
117+
}
118+
119+
def check_version(max_versions, version, arch) -> bool:
120+
(lib, _, ver) = version.rpartition('_')
121+
ver = tuple([int(x) for x in ver.split('.')])
122+
if not lib in max_versions:
123+
return False
124+
if isinstance(max_versions[lib], tuple):
125+
return ver <= max_versions[lib]
126+
else:
127+
return ver <= max_versions[lib][arch]
128+
129+
def check_ELF_imported_symbols(binary) -> bool:
130+
ok: bool = True
131+
132+
for symbol in binary.concrete.imported_symbols:
133+
if not symbol.imported:
134+
continue
135+
136+
version = symbol.symbol_version if symbol.has_version else None
137+
138+
if version:
139+
aux_version = version.symbol_version_auxiliary.name if version.has_auxiliary_version else None
140+
if aux_version and not check_version(MAX_VERSIONS, aux_version, binary.concrete.header.machine_type):
141+
print(f'{filename}: symbol {symbol.name} from unsupported version {version}')
142+
ok = False
143+
return ok
144+
145+
def check_ELF_exported_symbols(binary) -> bool:
146+
ok: bool = True
147+
for symbol in binary.concrete.dynamic_symbols:
148+
if not symbol.exported:
149+
continue
150+
name = symbol.name
151+
if binary.concrete.header.machine_type == lief.ELF.ARCH.RISCV or name in EXPECTED_EXPORTS:
152+
continue
153+
print(f'{filename}: export of symbol {name} not allowed!')
154+
ok = False
155+
return ok
156+
157+
def check_ELF_libraries(binary) -> bool:
158+
ok: bool = True
159+
for library in binary.concrete.libraries:
160+
if library not in ELF_ALLOWED_LIBRARIES:
161+
print(f'{filename}: {library} is not in ALLOWED_LIBRARIES!')
162+
ok = False
163+
return ok
164+
165+
def check_MACHO_libraries(binary) -> bool:
166+
ok: bool = True
167+
for dylib in binary.concrete.libraries:
168+
library = dylib.name.split('/')[-1]
169+
if binary.abstract.header.object_type == lief.OBJECT_TYPES.LIBRARY and library == 'libbitcoinconsensus.0.dylib':
170+
continue
171+
if library not in MACHO_ALLOWED_LIBRARIES:
172+
print(f'{filename}: {library} is not in ALLOWED_LIBRARIES!')
173+
ok = False
174+
return ok
175+
176+
def check_MACHO_min_os(binary) -> bool:
177+
if binary.concrete.build_version.minos == [10,15,0]:
178+
return True
179+
return False
180+
181+
def check_MACHO_sdk(binary) -> bool:
182+
if binary.concrete.build_version.sdk == [11, 0, 0]:
183+
return True
184+
return False
185+
186+
def check_PE_exported_functions(binary) -> bool:
187+
ok: bool = True
188+
for function in binary.concrete.exported_functions:
189+
name = function.name
190+
if name in EXPECTED_EXPORTS:
191+
continue
192+
print(f'{filename}: export of function {name} not allowed!')
193+
ok = False
194+
return ok
195+
196+
def check_PE_libraries(binary) -> bool:
197+
ok: bool = True
198+
for dylib in binary.concrete.libraries:
199+
if dylib not in PE_ALLOWED_LIBRARIES:
200+
print(f'{filename}: {dylib} is not in ALLOWED_LIBRARIES!')
201+
ok = False
202+
return ok
203+
204+
def check_PE_subsystem_version(binary) -> bool:
205+
major: int = binary.concrete.optional_header.major_subsystem_version
206+
minor: int = binary.concrete.optional_header.minor_subsystem_version
207+
if major == 5 and minor == 2:
208+
return True
209+
return False
210+
211+
CHECKS = {
212+
lief.EXE_FORMATS.ELF: [
213+
('EXPORTED_SYMBOLS', check_ELF_exported_symbols),
214+
('IMPORTED_SYMBOLS', check_ELF_imported_symbols),
215+
('LIBRARY_DEPENDENCIES', check_ELF_libraries),
216+
],
217+
lief.EXE_FORMATS.MACHO: [
218+
('DYNAMIC_LIBRARIES', check_MACHO_libraries),
219+
('MIN_OS', check_MACHO_min_os),
220+
('SDK', check_MACHO_sdk),
221+
],
222+
lief.EXE_FORMATS.PE: [
223+
('EXPORTED_FUNCTIONS', check_PE_exported_functions),
224+
('DYNAMIC_LIBRARIES', check_PE_libraries),
225+
('SUBSYSTEM_VERSION', check_PE_subsystem_version),
226+
]
227+
}
228+
229+
if __name__ == '__main__':
230+
retval: int = 0
231+
for filename in sys.argv[1:]:
232+
try:
233+
binary = lief.parse(filename)
234+
etype = binary.concrete.format
235+
if etype == lief.EXE_FORMATS.UNKNOWN:
236+
print(f'{filename}: unknown executable format')
237+
retval = 1
238+
continue
239+
240+
obj_type = binary.abstract.header.object_type
241+
if obj_type != lief.OBJECT_TYPES.LIBRARY:
242+
print(f'{filename}: unsupported file type')
243+
retval = 1
244+
continue
245+
246+
failed: List[str] = []
247+
for (name, func) in CHECKS[etype]:
248+
if not func(binary):
249+
failed.append(name)
250+
if failed:
251+
print(f'{filename}: failed {" ".join(failed)}')
252+
retval = 1
253+
except IOError:
254+
print(f'{filename}: cannot open')
255+
retval = 1
256+
sys.exit(retval)

0 commit comments

Comments
 (0)