Skip to content

Commit 8ae0d51

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

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

contrib/symbol-check.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
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 a secp256k1 shared library
7+
exports only expected symbols.
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
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+
def check_version(max_versions, version, arch) -> bool:
102+
(lib, _, ver) = version.rpartition('_')
103+
ver = tuple([int(x) for x in ver.split('.')])
104+
if not lib in max_versions:
105+
return False
106+
if isinstance(max_versions[lib], tuple):
107+
return ver <= max_versions[lib]
108+
else:
109+
return ver <= max_versions[lib][arch]
110+
111+
def check_ELF_imported_symbols(library) -> bool:
112+
ok: bool = True
113+
elf_lib: lief.ELF.Binary = library.concrete
114+
115+
for symbol in elf_lib.imported_symbols:
116+
if not symbol.imported:
117+
continue
118+
119+
version = symbol.symbol_version if symbol.has_version else None
120+
if version:
121+
aux_version = version.symbol_version_auxiliary.name if version.has_auxiliary_version else None
122+
if aux_version and not check_version(MAX_VERSIONS, aux_version, elf_lib.header.machine_type):
123+
print(f'{filename}: symbol {symbol.name} from unsupported version {version}')
124+
ok = False
125+
return ok
126+
127+
def check_ELF_exported_symbols(library) -> bool:
128+
ok: bool = True
129+
elf_lib: lief.ELF.Binary = library.concrete
130+
131+
for symbol in elf_lib.exported_symbols:
132+
name: str = symbol.name
133+
if name in EXPECTED_EXPORTS:
134+
continue
135+
print(f'{filename}: export of symbol {name} not expected')
136+
ok = False
137+
return ok
138+
139+
def check_PE_exported_functions(library) -> bool:
140+
ok: bool = True
141+
pe_lib: lief.PE.Binary = library.concrete
142+
143+
for function in pe_lib.exported_functions:
144+
name: str = function.name
145+
if name in EXPECTED_EXPORTS:
146+
continue
147+
print(f'{filename}: export of function {name} not expected')
148+
ok = False
149+
return ok
150+
151+
CHECKS = {
152+
lief.EXE_FORMATS.ELF: [
153+
('EXPORTED_SYMBOLS', check_ELF_exported_symbols),
154+
('IMPORTED_SYMBOLS', check_ELF_imported_symbols),
155+
],
156+
lief.EXE_FORMATS.PE: [
157+
('EXPORTED_FUNCTIONS', check_PE_exported_functions),
158+
]
159+
}
160+
161+
USAGE = """
162+
symbol-check.py is a script to check that a secp256k1 shared library
163+
exports only expected symbols.
164+
165+
Usage:
166+
./contrib/symbol-check.py <library>
167+
168+
"""
169+
170+
if __name__ == '__main__':
171+
if len(sys.argv) != 2:
172+
sys.exit(USAGE)
173+
174+
filename: str = sys.argv[1]
175+
try:
176+
library: lief.Binary = lief.parse(filename)
177+
exe_format: lief.EXE_FORMATS = library.format
178+
if exe_format != lief.EXE_FORMATS.ELF and exe_format != lief.EXE_FORMATS.PE:
179+
print(f'{filename}: unsupported executable format, only ELF and PE formats are supported')
180+
sys.exit(1)
181+
182+
obj_type = library.abstract.header.object_type
183+
if obj_type != lief.OBJECT_TYPES.LIBRARY:
184+
print(f'{filename}: unsupported object type, only LIBRARY type is supported')
185+
sys.exit(1)
186+
187+
failed: List[str] = []
188+
for (name, func) in CHECKS[exe_format]:
189+
if not func(library):
190+
failed.append(name)
191+
if failed:
192+
print(f'{filename}: failed {" ".join(failed)}')
193+
sys.exit(1)
194+
except IOError:
195+
print(f'{filename}: cannot open')
196+
sys.exit(1)

0 commit comments

Comments
 (0)