qa: Add multiwallet prefix test
[bitcoinplatinum.git] / contrib / devtools / security-check.py
blob6eb5667453d10e0ecd884e876c2581479f0fb95f
1 #!/usr/bin/env python
2 # Copyright (c) 2015-2016 The Bitcoin Core developers
3 # Distributed under the MIT software license, see the accompanying
4 # file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 '''
6 Perform basic ELF security checks on a series of executables.
7 Exit status will be 0 if successful, and the program will be silent.
8 Otherwise the exit status will be 1 and it will log which executables failed which checks.
9 Needs `readelf` (for ELF) and `objdump` (for PE).
10 '''
11 from __future__ import division,print_function,unicode_literals
12 import subprocess
13 import sys
14 import os
16 READELF_CMD = os.getenv('READELF', '/usr/bin/readelf')
17 OBJDUMP_CMD = os.getenv('OBJDUMP', '/usr/bin/objdump')
18 NONFATAL = {'HIGH_ENTROPY_VA'} # checks which are non-fatal for now but only generate a warning
20 def check_ELF_PIE(executable):
21 '''
22 Check for position independent executable (PIE), allowing for address space randomization.
23 '''
24 p = subprocess.Popen([READELF_CMD, '-h', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
25 (stdout, stderr) = p.communicate()
26 if p.returncode:
27 raise IOError('Error opening file')
29 ok = False
30 for line in stdout.split(b'\n'):
31 line = line.split()
32 if len(line)>=2 and line[0] == b'Type:' and line[1] == b'DYN':
33 ok = True
34 return ok
36 def get_ELF_program_headers(executable):
37 '''Return type and flags for ELF program headers'''
38 p = subprocess.Popen([READELF_CMD, '-l', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
39 (stdout, stderr) = p.communicate()
40 if p.returncode:
41 raise IOError('Error opening file')
42 in_headers = False
43 count = 0
44 headers = []
45 for line in stdout.split(b'\n'):
46 if line.startswith(b'Program Headers:'):
47 in_headers = True
48 if line == b'':
49 in_headers = False
50 if in_headers:
51 if count == 1: # header line
52 ofs_typ = line.find(b'Type')
53 ofs_offset = line.find(b'Offset')
54 ofs_flags = line.find(b'Flg')
55 ofs_align = line.find(b'Align')
56 if ofs_typ == -1 or ofs_offset == -1 or ofs_flags == -1 or ofs_align == -1:
57 raise ValueError('Cannot parse elfread -lW output')
58 elif count > 1:
59 typ = line[ofs_typ:ofs_offset].rstrip()
60 flags = line[ofs_flags:ofs_align].rstrip()
61 headers.append((typ, flags))
62 count += 1
63 return headers
65 def check_ELF_NX(executable):
66 '''
67 Check that no sections are writable and executable (including the stack)
68 '''
69 have_wx = False
70 have_gnu_stack = False
71 for (typ, flags) in get_ELF_program_headers(executable):
72 if typ == b'GNU_STACK':
73 have_gnu_stack = True
74 if b'W' in flags and b'E' in flags: # section is both writable and executable
75 have_wx = True
76 return have_gnu_stack and not have_wx
78 def check_ELF_RELRO(executable):
79 '''
80 Check for read-only relocations.
81 GNU_RELRO program header must exist
82 Dynamic section must have BIND_NOW flag
83 '''
84 have_gnu_relro = False
85 for (typ, flags) in get_ELF_program_headers(executable):
86 # Note: not checking flags == 'R': here as linkers set the permission differently
87 # This does not affect security: the permission flags of the GNU_RELRO program header are ignored, the PT_LOAD header determines the effective permissions.
88 # However, the dynamic linker need to write to this area so these are RW.
89 # Glibc itself takes care of mprotecting this area R after relocations are finished.
90 # See also http://permalink.gmane.org/gmane.comp.gnu.binutils/71347
91 if typ == b'GNU_RELRO':
92 have_gnu_relro = True
94 have_bindnow = False
95 p = subprocess.Popen([READELF_CMD, '-d', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
96 (stdout, stderr) = p.communicate()
97 if p.returncode:
98 raise IOError('Error opening file')
99 for line in stdout.split(b'\n'):
100 tokens = line.split()
101 if len(tokens)>1 and tokens[1] == b'(BIND_NOW)' or (len(tokens)>2 and tokens[1] == b'(FLAGS)' and b'BIND_NOW' in tokens[2]):
102 have_bindnow = True
103 return have_gnu_relro and have_bindnow
105 def check_ELF_Canary(executable):
107 Check for use of stack canary
109 p = subprocess.Popen([READELF_CMD, '--dyn-syms', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
110 (stdout, stderr) = p.communicate()
111 if p.returncode:
112 raise IOError('Error opening file')
113 ok = False
114 for line in stdout.split(b'\n'):
115 if b'__stack_chk_fail' in line:
116 ok = True
117 return ok
119 def get_PE_dll_characteristics(executable):
121 Get PE DllCharacteristics bits.
122 Returns a tuple (arch,bits) where arch is 'i386:x86-64' or 'i386'
123 and bits is the DllCharacteristics value.
125 p = subprocess.Popen([OBJDUMP_CMD, '-x', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
126 (stdout, stderr) = p.communicate()
127 if p.returncode:
128 raise IOError('Error opening file')
129 arch = ''
130 bits = 0
131 for line in stdout.split('\n'):
132 tokens = line.split()
133 if len(tokens)>=2 and tokens[0] == 'architecture:':
134 arch = tokens[1].rstrip(',')
135 if len(tokens)>=2 and tokens[0] == 'DllCharacteristics':
136 bits = int(tokens[1],16)
137 return (arch,bits)
139 IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
140 IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040
141 IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100
143 def check_PE_DYNAMIC_BASE(executable):
144 '''PIE: DllCharacteristics bit 0x40 signifies dynamicbase (ASLR)'''
145 (arch,bits) = get_PE_dll_characteristics(executable)
146 reqbits = IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE
147 return (bits & reqbits) == reqbits
149 # On 64 bit, must support high-entropy 64-bit address space layout randomization in addition to DYNAMIC_BASE
150 # to have secure ASLR.
151 def check_PE_HIGH_ENTROPY_VA(executable):
152 '''PIE: DllCharacteristics bit 0x20 signifies high-entropy ASLR'''
153 (arch,bits) = get_PE_dll_characteristics(executable)
154 if arch == 'i386:x86-64':
155 reqbits = IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA
156 else: # Unnecessary on 32-bit
157 assert(arch == 'i386')
158 reqbits = 0
159 return (bits & reqbits) == reqbits
161 def check_PE_NX(executable):
162 '''NX: DllCharacteristics bit 0x100 signifies nxcompat (DEP)'''
163 (arch,bits) = get_PE_dll_characteristics(executable)
164 return (bits & IMAGE_DLL_CHARACTERISTICS_NX_COMPAT) == IMAGE_DLL_CHARACTERISTICS_NX_COMPAT
166 CHECKS = {
167 'ELF': [
168 ('PIE', check_ELF_PIE),
169 ('NX', check_ELF_NX),
170 ('RELRO', check_ELF_RELRO),
171 ('Canary', check_ELF_Canary)
173 'PE': [
174 ('DYNAMIC_BASE', check_PE_DYNAMIC_BASE),
175 ('HIGH_ENTROPY_VA', check_PE_HIGH_ENTROPY_VA),
176 ('NX', check_PE_NX)
180 def identify_executable(executable):
181 with open(filename, 'rb') as f:
182 magic = f.read(4)
183 if magic.startswith(b'MZ'):
184 return 'PE'
185 elif magic.startswith(b'\x7fELF'):
186 return 'ELF'
187 return None
189 if __name__ == '__main__':
190 retval = 0
191 for filename in sys.argv[1:]:
192 try:
193 etype = identify_executable(filename)
194 if etype is None:
195 print('%s: unknown format' % filename)
196 retval = 1
197 continue
199 failed = []
200 warning = []
201 for (name, func) in CHECKS[etype]:
202 if not func(filename):
203 if name in NONFATAL:
204 warning.append(name)
205 else:
206 failed.append(name)
207 if failed:
208 print('%s: failed %s' % (filename, ' '.join(failed)))
209 retval = 1
210 if warning:
211 print('%s: warning %s' % (filename, ' '.join(warning)))
212 except IOError:
213 print('%s: cannot open' % filename)
214 retval = 1
215 sys.exit(retval)