Improve COutPoint less operator
[bitcoinplatinum.git] / contrib / devtools / security-check.py
blob0319f739c49365c1ac690813c9d5cec424861bc2
1 #!/usr/bin/python2
2 '''
3 Perform basic ELF security checks on a series of executables.
4 Exit status will be 0 if successful, and the program will be silent.
5 Otherwise the exit status will be 1 and it will log which executables failed which checks.
6 Needs `readelf` (for ELF) and `objdump` (for PE).
7 '''
8 from __future__ import division,print_function
9 import subprocess
10 import sys
11 import os
13 READELF_CMD = os.getenv('READELF', '/usr/bin/readelf')
14 OBJDUMP_CMD = os.getenv('OBJDUMP', '/usr/bin/objdump')
16 def check_ELF_PIE(executable):
17 '''
18 Check for position independent executable (PIE), allowing for address space randomization.
19 '''
20 p = subprocess.Popen([READELF_CMD, '-h', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
21 (stdout, stderr) = p.communicate()
22 if p.returncode:
23 raise IOError('Error opening file')
25 ok = False
26 for line in stdout.split('\n'):
27 line = line.split()
28 if len(line)>=2 and line[0] == 'Type:' and line[1] == 'DYN':
29 ok = True
30 return ok
32 def get_ELF_program_headers(executable):
33 '''Return type and flags for ELF program headers'''
34 p = subprocess.Popen([READELF_CMD, '-l', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
35 (stdout, stderr) = p.communicate()
36 if p.returncode:
37 raise IOError('Error opening file')
38 in_headers = False
39 count = 0
40 headers = []
41 for line in stdout.split('\n'):
42 if line.startswith('Program Headers:'):
43 in_headers = True
44 if line == '':
45 in_headers = False
46 if in_headers:
47 if count == 1: # header line
48 ofs_typ = line.find('Type')
49 ofs_offset = line.find('Offset')
50 ofs_flags = line.find('Flg')
51 ofs_align = line.find('Align')
52 if ofs_typ == -1 or ofs_offset == -1 or ofs_flags == -1 or ofs_align == -1:
53 raise ValueError('Cannot parse elfread -lW output')
54 elif count > 1:
55 typ = line[ofs_typ:ofs_offset].rstrip()
56 flags = line[ofs_flags:ofs_align].rstrip()
57 headers.append((typ, flags))
58 count += 1
59 return headers
61 def check_ELF_NX(executable):
62 '''
63 Check that no sections are writable and executable (including the stack)
64 '''
65 have_wx = False
66 have_gnu_stack = False
67 for (typ, flags) in get_ELF_program_headers(executable):
68 if typ == 'GNU_STACK':
69 have_gnu_stack = True
70 if 'W' in flags and 'E' in flags: # section is both writable and executable
71 have_wx = True
72 return have_gnu_stack and not have_wx
74 def check_ELF_RELRO(executable):
75 '''
76 Check for read-only relocations.
77 GNU_RELRO program header must exist
78 Dynamic section must have BIND_NOW flag
79 '''
80 have_gnu_relro = False
81 for (typ, flags) in get_ELF_program_headers(executable):
82 # Note: not checking flags == 'R': here as linkers set the permission differently
83 # This does not affect security: the permission flags of the GNU_RELRO program header are ignored, the PT_LOAD header determines the effective permissions.
84 # However, the dynamic linker need to write to this area so these are RW.
85 # Glibc itself takes care of mprotecting this area R after relocations are finished.
86 # See also http://permalink.gmane.org/gmane.comp.gnu.binutils/71347
87 if typ == 'GNU_RELRO':
88 have_gnu_relro = True
90 have_bindnow = False
91 p = subprocess.Popen([READELF_CMD, '-d', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
92 (stdout, stderr) = p.communicate()
93 if p.returncode:
94 raise IOError('Error opening file')
95 for line in stdout.split('\n'):
96 tokens = line.split()
97 if len(tokens)>1 and tokens[1] == '(BIND_NOW)' or (len(tokens)>2 and tokens[1] == '(FLAGS)' and 'BIND_NOW' in tokens[2]):
98 have_bindnow = True
99 return have_gnu_relro and have_bindnow
101 def check_ELF_Canary(executable):
103 Check for use of stack canary
105 p = subprocess.Popen([READELF_CMD, '--dyn-syms', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
106 (stdout, stderr) = p.communicate()
107 if p.returncode:
108 raise IOError('Error opening file')
109 ok = False
110 for line in stdout.split('\n'):
111 if '__stack_chk_fail' in line:
112 ok = True
113 return ok
115 def get_PE_dll_characteristics(executable):
117 Get PE DllCharacteristics bits
119 p = subprocess.Popen([OBJDUMP_CMD, '-x', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
120 (stdout, stderr) = p.communicate()
121 if p.returncode:
122 raise IOError('Error opening file')
123 for line in stdout.split('\n'):
124 tokens = line.split()
125 if len(tokens)>=2 and tokens[0] == 'DllCharacteristics':
126 return int(tokens[1],16)
127 return 0
130 def check_PE_PIE(executable):
131 '''PIE: DllCharacteristics bit 0x40 signifies dynamicbase (ASLR)'''
132 return bool(get_PE_dll_characteristics(executable) & 0x40)
134 def check_PE_NX(executable):
135 '''NX: DllCharacteristics bit 0x100 signifies nxcompat (DEP)'''
136 return bool(get_PE_dll_characteristics(executable) & 0x100)
138 CHECKS = {
139 'ELF': [
140 ('PIE', check_ELF_PIE),
141 ('NX', check_ELF_NX),
142 ('RELRO', check_ELF_RELRO),
143 ('Canary', check_ELF_Canary)
145 'PE': [
146 ('PIE', check_PE_PIE),
147 ('NX', check_PE_NX)
151 def identify_executable(executable):
152 with open(filename, 'rb') as f:
153 magic = f.read(4)
154 if magic.startswith(b'MZ'):
155 return 'PE'
156 elif magic.startswith(b'\x7fELF'):
157 return 'ELF'
158 return None
160 if __name__ == '__main__':
161 retval = 0
162 for filename in sys.argv[1:]:
163 try:
164 etype = identify_executable(filename)
165 if etype is None:
166 print('%s: unknown format' % filename)
167 retval = 1
168 continue
170 failed = []
171 for (name, func) in CHECKS[etype]:
172 if not func(filename):
173 failed.append(name)
174 if failed:
175 print('%s: failed %s' % (filename, ' '.join(failed)))
176 retval = 1
177 except IOError:
178 print('%s: cannot open' % filename)
179 retval = 1
180 exit(retval)