Skip to content

Commit 766964f

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

File tree

1 file changed

+138
-0
lines changed

1 file changed

+138
-0
lines changed

contrib/symbol-check.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
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 re
16+
import sys
17+
import subprocess
18+
from typing import List
19+
20+
import lief #type:ignore
21+
22+
MAX_VERSIONS = {
23+
'GLIBC': {
24+
lief.ELF.ARCH.x86_64: (2,4),
25+
lief.ELF.ARCH.ARM: (2,4),
26+
lief.ELF.ARCH.AARCH64:(2,17),
27+
lief.ELF.ARCH.PPC64: (2,17),
28+
lief.ELF.ARCH.RISCV: (2,27),
29+
},
30+
}
31+
32+
def grep_exported_symbols() -> List[str]:
33+
grep_output = subprocess.check_output(["git", "grep", "^SECP256K1_API", "--", "include"], universal_newlines=True, encoding="utf8")
34+
lines = grep_output.split("\n")
35+
exports: List[str] = []
36+
for line in lines:
37+
if line.strip():
38+
function_name = re.sub(r'\W', '', line.split(" ")[-1])
39+
exports.append(function_name)
40+
return exports
41+
42+
def check_version(max_versions, version, arch) -> bool:
43+
(lib, _, ver) = version.rpartition('_')
44+
ver = tuple([int(x) for x in ver.split('.')])
45+
if not lib in max_versions:
46+
return False
47+
if isinstance(max_versions[lib], tuple):
48+
return ver <= max_versions[lib]
49+
else:
50+
return ver <= max_versions[lib][arch]
51+
52+
def check_ELF_imported_symbols(library, *_) -> bool:
53+
ok: bool = True
54+
elf_lib: lief.ELF.Binary = library.concrete
55+
56+
for symbol in elf_lib.imported_symbols:
57+
if not symbol.imported:
58+
continue
59+
60+
version = symbol.symbol_version if symbol.has_version else None
61+
if version:
62+
aux_version = version.symbol_version_auxiliary.name if version.has_auxiliary_version else None
63+
if aux_version and not check_version(MAX_VERSIONS, aux_version, elf_lib.header.machine_type):
64+
print(f'{filename}: symbol {symbol.name} from unsupported version {version}')
65+
ok = False
66+
return ok
67+
68+
def check_ELF_exported_symbols(library, expected_exports) -> bool:
69+
ok: bool = True
70+
elf_lib: lief.ELF.Binary = library.concrete
71+
72+
for symbol in elf_lib.exported_symbols:
73+
name: str = symbol.name
74+
if name in expected_exports:
75+
continue
76+
print(f'{filename}: export of symbol {name} not expected')
77+
ok = False
78+
return ok
79+
80+
def check_PE_exported_functions(library, expected_exports) -> bool:
81+
ok: bool = True
82+
pe_lib: lief.PE.Binary = library.concrete
83+
84+
for function in pe_lib.exported_functions:
85+
name: str = function.name
86+
if name in expected_exports:
87+
continue
88+
print(f'{filename}: export of function {name} not expected')
89+
ok = False
90+
return ok
91+
92+
CHECKS = {
93+
lief.EXE_FORMATS.ELF: [
94+
('EXPORTED_SYMBOLS', check_ELF_exported_symbols),
95+
('IMPORTED_SYMBOLS', check_ELF_imported_symbols),
96+
],
97+
lief.EXE_FORMATS.PE: [
98+
('EXPORTED_FUNCTIONS', check_PE_exported_functions),
99+
]
100+
}
101+
102+
USAGE = """
103+
symbol-check.py is a script to check that a secp256k1 shared library
104+
exports only expected symbols.
105+
106+
Usage:
107+
./contrib/symbol-check.py <library>
108+
109+
"""
110+
111+
if __name__ == '__main__':
112+
if len(sys.argv) != 2:
113+
sys.exit(USAGE)
114+
115+
filename: str = sys.argv[1]
116+
try:
117+
library: lief.Binary = lief.parse(filename)
118+
exe_format: lief.EXE_FORMATS = library.format
119+
if exe_format != lief.EXE_FORMATS.ELF and exe_format != lief.EXE_FORMATS.PE:
120+
print(f'{filename}: unsupported executable format, only ELF and PE formats are supported')
121+
sys.exit(1)
122+
123+
obj_type = library.abstract.header.object_type
124+
if obj_type != lief.OBJECT_TYPES.LIBRARY:
125+
print(f'{filename}: unsupported object type, only LIBRARY type is supported')
126+
sys.exit(1)
127+
128+
expected_exports = grep_exported_symbols()
129+
failed: List[str] = []
130+
for (name, func) in CHECKS[exe_format]:
131+
if not func(library, expected_exports):
132+
failed.append(name)
133+
if failed:
134+
print(f'{filename}: failed {" ".join(failed)}')
135+
sys.exit(1)
136+
except IOError:
137+
print(f'{filename}: cannot open')
138+
sys.exit(1)

0 commit comments

Comments
 (0)