1515import re
1616import sys
1717import os
18+ from typing import List , Optional , Tuple
1819
1920# Debian 8 (Jessie) EOL: 2020. https://wiki.debian.org/DebianReleases#Production_Releases
2021#
5253}
5354READELF_CMD = os .getenv ('READELF' , '/usr/bin/readelf' )
5455CPPFILT_CMD = os .getenv ('CPPFILT' , '/usr/bin/c++filt' )
56+ OTOOL_CMD = os .getenv ('OTOOL' , '/usr/bin/otool' )
57+
5558# Allowed NEEDED libraries
56- ALLOWED_LIBRARIES = {
59+ ELF_ALLOWED_LIBRARIES = {
5760# bitcoind and bitcoin-qt
5861'libgcc_s.so.1' , # GCC base support
5962'libc.so.6' , # C library
7982'AArch64' :(2 ,17 ),
8083'RISC-V' : (2 ,27 )
8184}
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+
82104class CPPFilt (object ):
83105 '''
84106 Demangle C++ symbol names.
@@ -98,15 +120,15 @@ def close(self):
98120 self .proc .stdout .close ()
99121 self .proc .wait ()
100122
101- def read_symbols (executable , imports = True ):
123+ def read_symbols (executable , imports = True ) -> List [ Tuple [ str , str , str ]] :
102124 '''
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
104126 for dynamic, imported symbols.
105127 '''
106128 p = subprocess .Popen ([READELF_CMD , '--dyn-syms' , '-W' , '-h' , executable ], stdout = subprocess .PIPE , stderr = subprocess .PIPE , stdin = subprocess .PIPE , universal_newlines = True )
107129 (stdout , stderr ) = p .communicate ()
108130 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 ()))
110132 syms = []
111133 for line in stdout .splitlines ():
112134 line = line .split ()
@@ -121,7 +143,7 @@ def read_symbols(executable, imports=True):
121143 syms .append ((sym , version , arch ))
122144 return syms
123145
124- def check_version (max_versions , version , arch ):
146+ def check_version (max_versions , version , arch ) -> bool :
125147 if '_' in version :
126148 (lib , _ , ver ) = version .rpartition ('_' )
127149 else :
@@ -132,7 +154,7 @@ def check_version(max_versions, version, arch):
132154 return False
133155 return ver <= max_versions [lib ] or lib == 'GLIBC' and ver <= ARCH_MIN_GLIBC_VER [arch ]
134156
135- def read_libraries (filename ):
157+ def elf_read_libraries (filename ) -> List [ str ] :
136158 p = subprocess .Popen ([READELF_CMD , '-d' , '-W' , filename ], stdout = subprocess .PIPE , stderr = subprocess .PIPE , stdin = subprocess .PIPE , universal_newlines = True )
137159 (stdout , stderr ) = p .communicate ()
138160 if p .returncode :
@@ -148,26 +170,94 @@ def read_libraries(filename):
148170 raise ValueError ('Unparseable (NEEDED) specification' )
149171 return libraries
150172
151- if __name__ == '__main__' :
173+ def check_imported_symbols ( filename ) -> bool :
152174 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__' :
153244 retval = 0
154245 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 ))
171250 retval = 1
251+ continue
172252
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
173263 sys .exit (retval )
0 commit comments