15
15
import re
16
16
import sys
17
17
import os
18
+ from typing import List , Optional , Tuple
18
19
19
20
# Debian 8 (Jessie) EOL: 2020. https://wiki.debian.org/DebianReleases#Production_Releases
20
21
#
52
53
}
53
54
READELF_CMD = os .getenv ('READELF' , '/usr/bin/readelf' )
54
55
CPPFILT_CMD = os .getenv ('CPPFILT' , '/usr/bin/c++filt' )
56
+ OTOOL_CMD = os .getenv ('OTOOL' , '/usr/bin/otool' )
57
+
55
58
# Allowed NEEDED libraries
56
- ALLOWED_LIBRARIES = {
59
+ ELF_ALLOWED_LIBRARIES = {
57
60
# bitcoind and bitcoin-qt
58
61
'libgcc_s.so.1' , # GCC base support
59
62
'libc.so.6' , # C library
79
82
'AArch64' :(2 ,17 ),
80
83
'RISC-V' : (2 ,27 )
81
84
}
85
+
86
+ MACHO_ALLOWED_LIBRARIES = {
87
+ # bitcoind and bitcoin-qt
88
+ 'libc++.1.dylib' , # C++ Standard Library
89
+ 'libSystem.B.dylib' , # libc, libm, libpthread, libinfo
90
+ # bitcoin-qt only
91
+ 'AppKit' , # user interface
92
+ 'ApplicationServices' , # common application tasks.
93
+ 'Carbon' , # deprecated c back-compat API
94
+ 'CoreFoundation' , # low level func, data types
95
+ 'CoreGraphics' , # 2D rendering
96
+ 'CoreServices' , # operating system services
97
+ 'CoreText' , # interface for laying out text and handling fonts.
98
+ 'Foundation' , # base layer functionality for apps/frameworks
99
+ 'ImageIO' , # read and write image file formats.
100
+ 'IOKit' , # user-space access to hardware devices and drivers.
101
+ 'libobjc.A.dylib' , # Objective-C runtime library
102
+ }
103
+
82
104
class CPPFilt (object ):
83
105
'''
84
106
Demangle C++ symbol names.
@@ -98,15 +120,15 @@ def close(self):
98
120
self .proc .stdout .close ()
99
121
self .proc .wait ()
100
122
101
- def read_symbols (executable , imports = True ):
123
+ def read_symbols (executable , imports = True ) -> List [ Tuple [ str , str , str ]] :
102
124
'''
103
- Parse an ELF executable and return a list of (symbol,version) tuples
125
+ Parse an ELF executable and return a list of (symbol,version, arch ) tuples
104
126
for dynamic, imported symbols.
105
127
'''
106
128
p = subprocess .Popen ([READELF_CMD , '--dyn-syms' , '-W' , '-h' , executable ], stdout = subprocess .PIPE , stderr = subprocess .PIPE , stdin = subprocess .PIPE , universal_newlines = True )
107
129
(stdout , stderr ) = p .communicate ()
108
130
if p .returncode :
109
- raise IOError ('Could not read symbols for %s: %s' % (executable , stderr .strip ()))
131
+ raise IOError ('Could not read symbols for {}: {}' . format (executable , stderr .strip ()))
110
132
syms = []
111
133
for line in stdout .splitlines ():
112
134
line = line .split ()
@@ -121,7 +143,7 @@ def read_symbols(executable, imports=True):
121
143
syms .append ((sym , version , arch ))
122
144
return syms
123
145
124
- def check_version (max_versions , version , arch ):
146
+ def check_version (max_versions , version , arch ) -> bool :
125
147
if '_' in version :
126
148
(lib , _ , ver ) = version .rpartition ('_' )
127
149
else :
@@ -132,7 +154,7 @@ def check_version(max_versions, version, arch):
132
154
return False
133
155
return ver <= max_versions [lib ] or lib == 'GLIBC' and ver <= ARCH_MIN_GLIBC_VER [arch ]
134
156
135
- def read_libraries (filename ):
157
+ def elf_read_libraries (filename ) -> List [ str ] :
136
158
p = subprocess .Popen ([READELF_CMD , '-d' , '-W' , filename ], stdout = subprocess .PIPE , stderr = subprocess .PIPE , stdin = subprocess .PIPE , universal_newlines = True )
137
159
(stdout , stderr ) = p .communicate ()
138
160
if p .returncode :
@@ -148,26 +170,94 @@ def read_libraries(filename):
148
170
raise ValueError ('Unparseable (NEEDED) specification' )
149
171
return libraries
150
172
151
- if __name__ == '__main__' :
173
+ def check_imported_symbols ( filename ) -> bool :
152
174
cppfilt = CPPFilt ()
175
+ ok = True
176
+ for sym , version , arch in read_symbols (filename , True ):
177
+ if version and not check_version (MAX_VERSIONS , version , arch ):
178
+ print ('{}: symbol {} from unsupported version {}' .format (filename , cppfilt (sym ), version ))
179
+ ok = False
180
+ return ok
181
+
182
+ def check_exported_symbols (filename ) -> bool :
183
+ cppfilt = CPPFilt ()
184
+ ok = True
185
+ for sym ,version ,arch in read_symbols (filename , False ):
186
+ if arch == 'RISC-V' or sym in IGNORE_EXPORTS :
187
+ continue
188
+ print ('{}: export of symbol {} not allowed' .format (filename , cppfilt (sym )))
189
+ ok = False
190
+ return ok
191
+
192
+ def check_ELF_libraries (filename ) -> bool :
193
+ ok = True
194
+ for library_name in elf_read_libraries (filename ):
195
+ if library_name not in ELF_ALLOWED_LIBRARIES :
196
+ print ('{}: NEEDED library {} is not allowed' .format (filename , library_name ))
197
+ ok = False
198
+ return ok
199
+
200
+ def macho_read_libraries (filename ) -> List [str ]:
201
+ p = subprocess .Popen ([OTOOL_CMD , '-L' , filename ], stdout = subprocess .PIPE , stderr = subprocess .PIPE , stdin = subprocess .PIPE , universal_newlines = True )
202
+ (stdout , stderr ) = p .communicate ()
203
+ if p .returncode :
204
+ raise IOError ('Error opening file' )
205
+ libraries = []
206
+ for line in stdout .splitlines ():
207
+ tokens = line .split ()
208
+ if len (tokens ) == 1 : # skip executable name
209
+ continue
210
+ libraries .append (tokens [0 ].split ('/' )[- 1 ])
211
+ return libraries
212
+
213
+ def check_MACHO_libraries (filename ) -> bool :
214
+ ok = True
215
+ for dylib in macho_read_libraries (filename ):
216
+ if dylib not in MACHO_ALLOWED_LIBRARIES :
217
+ print ('{} is not in ALLOWED_LIBRARIES!' .format (dylib ))
218
+ ok = False
219
+ return ok
220
+
221
+ CHECKS = {
222
+ 'ELF' : [
223
+ ('IMPORTED_SYMBOLS' , check_imported_symbols ),
224
+ ('EXPORTED_SYMBOLS' , check_exported_symbols ),
225
+ ('LIBRARY_DEPENDENCIES' , check_ELF_libraries )
226
+ ],
227
+ 'MACHO' : [
228
+ ('DYNAMIC_LIBRARIES' , check_MACHO_libraries )
229
+ ]
230
+ }
231
+
232
+ def identify_executable (executable ) -> Optional [str ]:
233
+ with open (filename , 'rb' ) as f :
234
+ magic = f .read (4 )
235
+ if magic .startswith (b'MZ' ):
236
+ return 'PE'
237
+ elif magic .startswith (b'\x7f ELF' ):
238
+ return 'ELF'
239
+ elif magic .startswith (b'\xcf \xfa ' ):
240
+ return 'MACHO'
241
+ return None
242
+
243
+ if __name__ == '__main__' :
153
244
retval = 0
154
245
for filename in sys .argv [1 :]:
155
- # Check imported symbols
156
- for sym ,version ,arch in read_symbols (filename , True ):
157
- if version and not check_version (MAX_VERSIONS , version , arch ):
158
- print ('%s: symbol %s from unsupported version %s' % (filename , cppfilt (sym ), version ))
159
- retval = 1
160
- # Check exported symbols
161
- if arch != 'RISC-V' :
162
- for sym ,version ,arch in read_symbols (filename , False ):
163
- if sym in IGNORE_EXPORTS :
164
- continue
165
- print ('%s: export of symbol %s not allowed' % (filename , cppfilt (sym )))
166
- retval = 1
167
- # Check dependency libraries
168
- for library_name in read_libraries (filename ):
169
- if library_name not in ALLOWED_LIBRARIES :
170
- print ('%s: NEEDED library %s is not allowed' % (filename , library_name ))
246
+ try :
247
+ etype = identify_executable (filename )
248
+ if etype is None :
249
+ print ('{}: unknown format' .format (filename ))
171
250
retval = 1
251
+ continue
172
252
253
+ failed = []
254
+ for (name , func ) in CHECKS [etype ]:
255
+ if not func (filename ):
256
+ failed .append (name )
257
+ if failed :
258
+ print ('{}: failed {}' .format (filename , ' ' .join (failed )))
259
+ retval = 1
260
+ except IOError :
261
+ print ('{}: cannot open' .format (filename ))
262
+ retval = 1
173
263
sys .exit (retval )
0 commit comments