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.
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).
11 from __future__
import division
,print_function
,unicode_literals
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
):
22 Check for position independent executable (PIE), allowing for address space randomization.
24 p
= subprocess
.Popen([READELF_CMD
, '-h', '-W', executable
], stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, stdin
=subprocess
.PIPE
)
25 (stdout
, stderr
) = p
.communicate()
27 raise IOError('Error opening file')
30 for line
in stdout
.split(b
'\n'):
32 if len(line
)>=2 and line
[0] == b
'Type:' and line
[1] == b
'DYN':
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()
41 raise IOError('Error opening file')
45 for line
in stdout
.split(b
'\n'):
46 if line
.startswith(b
'Program 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')
59 typ
= line
[ofs_typ
:ofs_offset
].rstrip()
60 flags
= line
[ofs_flags
:ofs_align
].rstrip()
61 headers
.append((typ
, flags
))
65 def check_ELF_NX(executable
):
67 Check that no sections are writable and executable (including the stack)
70 have_gnu_stack
= False
71 for (typ
, flags
) in get_ELF_program_headers(executable
):
72 if typ
== b
'GNU_STACK':
74 if b
'W' in flags
and b
'E' in flags
: # section is both writable and executable
76 return have_gnu_stack
and not have_wx
78 def check_ELF_RELRO(executable
):
80 Check for read-only relocations.
81 GNU_RELRO program header must exist
82 Dynamic section must have BIND_NOW flag
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':
95 p
= subprocess
.Popen([READELF_CMD
, '-d', '-W', executable
], stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
, stdin
=subprocess
.PIPE
)
96 (stdout
, stderr
) = p
.communicate()
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]):
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()
112 raise IOError('Error opening file')
114 for line
in stdout
.split(b
'\n'):
115 if b
'__stack_chk_fail' in line
:
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()
128 raise IOError('Error opening file')
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)
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')
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
168 ('PIE', check_ELF_PIE
),
169 ('NX', check_ELF_NX
),
170 ('RELRO', check_ELF_RELRO
),
171 ('Canary', check_ELF_Canary
)
174 ('DYNAMIC_BASE', check_PE_DYNAMIC_BASE
),
175 ('HIGH_ENTROPY_VA', check_PE_HIGH_ENTROPY_VA
),
180 def identify_executable(executable
):
181 with
open(filename
, 'rb') as f
:
183 if magic
.startswith(b
'MZ'):
185 elif magic
.startswith(b
'\x7fELF'):
189 if __name__
== '__main__':
191 for filename
in sys
.argv
[1:]:
193 etype
= identify_executable(filename
)
195 print('%s: unknown format' % filename
)
201 for (name
, func
) in CHECKS
[etype
]:
202 if not func(filename
):
208 print('%s: failed %s' % (filename
, ' '.join(failed
)))
211 print('%s: warning %s' % (filename
, ' '.join(warning
)))
213 print('%s: cannot open' % filename
)