Skip to content

Commit 2d7489b

Browse files
committed
Merge #18796: scripts: security-check.py refactors
eacedfb scripts: add additional type annotations to security-check.py (fanquake) 83d063e scripts: add run_command to security-check.py (fanquake) 13f606b scripts: remove NONFATAL from security-check.py (fanquake) 061acf6 scripts: no-longer check for 32 bit windows in security-check.py (fanquake) Pull request description: * Remove 32-bit Windows checks. * Remove NONFATAL checking. Added in #8249, however unused since #13764. * Add `run_command` to de-duplicate all of the subprocess calls. Mentioned in #18713. * Add additional type annotations. * Print stderr when there is an issue running a command. ACKs for top commit: laanwj: ACK eacedfb Tree-SHA512: 69a7ccfdf346ee202b3e8f940634c5daed1d2b5a5d15ac9800252866ba3284ec66e391a66a0b341f5a4e5e8482fe1b614d4671e8e766112ff059405081184a85
2 parents 4dd2e52 + eacedfb commit 2d7489b

File tree

1 file changed

+45
-79
lines changed

1 file changed

+45
-79
lines changed

contrib/devtools/security-check.py

Lines changed: 45 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,33 @@
1212
import sys
1313
import os
1414

15+
from typing import List, Optional
16+
1517
READELF_CMD = os.getenv('READELF', '/usr/bin/readelf')
1618
OBJDUMP_CMD = os.getenv('OBJDUMP', '/usr/bin/objdump')
1719
OTOOL_CMD = os.getenv('OTOOL', '/usr/bin/otool')
18-
NONFATAL = {} # checks which are non-fatal for now but only generate a warning
1920

20-
def check_ELF_PIE(executable):
21+
def run_command(command) -> str:
22+
p = subprocess.run(command, stdout=subprocess.PIPE, check=True, universal_newlines=True)
23+
return p.stdout
24+
25+
def check_ELF_PIE(executable) -> bool:
2126
'''
2227
Check for position independent executable (PIE), allowing for address space randomization.
2328
'''
24-
p = subprocess.Popen([READELF_CMD, '-h', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
25-
(stdout, stderr) = p.communicate()
26-
if p.returncode:
27-
raise IOError('Error opening file')
29+
stdout = run_command([READELF_CMD, '-h', '-W', executable])
2830

2931
ok = False
3032
for line in stdout.splitlines():
31-
line = line.split()
32-
if len(line)>=2 and line[0] == 'Type:' and line[1] == 'DYN':
33+
tokens = line.split()
34+
if len(line)>=2 and tokens[0] == 'Type:' and tokens[1] == 'DYN':
3335
ok = True
3436
return ok
3537

3638
def get_ELF_program_headers(executable):
3739
'''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, universal_newlines=True)
39-
(stdout, stderr) = p.communicate()
40-
if p.returncode:
41-
raise IOError('Error opening file')
40+
stdout = run_command([READELF_CMD, '-l', '-W', executable])
41+
4242
in_headers = False
4343
count = 0
4444
headers = []
@@ -62,7 +62,7 @@ def get_ELF_program_headers(executable):
6262
count += 1
6363
return headers
6464

65-
def check_ELF_NX(executable):
65+
def check_ELF_NX(executable) -> bool:
6666
'''
6767
Check that no sections are writable and executable (including the stack)
6868
'''
@@ -75,7 +75,7 @@ def check_ELF_NX(executable):
7575
have_wx = True
7676
return have_gnu_stack and not have_wx
7777

78-
def check_ELF_RELRO(executable):
78+
def check_ELF_RELRO(executable) -> bool:
7979
'''
8080
Check for read-only relocations.
8181
GNU_RELRO program header must exist
@@ -84,101 +84,78 @@ def check_ELF_RELRO(executable):
8484
have_gnu_relro = False
8585
for (typ, flags) in get_ELF_program_headers(executable):
8686
# 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.
87+
# This does not affect security: the permission flags of the GNU_RELRO program
88+
# header are ignored, the PT_LOAD header determines the effective permissions.
8889
# However, the dynamic linker need to write to this area so these are RW.
8990
# Glibc itself takes care of mprotecting this area R after relocations are finished.
9091
# See also https://marc.info/?l=binutils&m=1498883354122353
9192
if typ == 'GNU_RELRO':
9293
have_gnu_relro = True
9394

9495
have_bindnow = False
95-
p = subprocess.Popen([READELF_CMD, '-d', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
96-
(stdout, stderr) = p.communicate()
97-
if p.returncode:
98-
raise IOError('Error opening file')
96+
stdout = run_command([READELF_CMD, '-d', '-W', executable])
97+
9998
for line in stdout.splitlines():
10099
tokens = line.split()
101100
if len(tokens)>1 and tokens[1] == '(BIND_NOW)' or (len(tokens)>2 and tokens[1] == '(FLAGS)' and 'BIND_NOW' in tokens[2:]):
102101
have_bindnow = True
103102
return have_gnu_relro and have_bindnow
104103

105-
def check_ELF_Canary(executable):
104+
def check_ELF_Canary(executable) -> bool:
106105
'''
107106
Check for use of stack canary
108107
'''
109-
p = subprocess.Popen([READELF_CMD, '--dyn-syms', '-W', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
110-
(stdout, stderr) = p.communicate()
111-
if p.returncode:
112-
raise IOError('Error opening file')
108+
stdout = run_command([READELF_CMD, '--dyn-syms', '-W', executable])
109+
113110
ok = False
114111
for line in stdout.splitlines():
115112
if '__stack_chk_fail' in line:
116113
ok = True
117114
return ok
118115

119-
def get_PE_dll_characteristics(executable):
120-
'''
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.
124-
'''
125-
p = subprocess.Popen([OBJDUMP_CMD, '-x', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
126-
(stdout, stderr) = p.communicate()
127-
if p.returncode:
128-
raise IOError('Error opening file')
129-
arch = ''
116+
def get_PE_dll_characteristics(executable) -> int:
117+
'''Get PE DllCharacteristics bits'''
118+
stdout = run_command([OBJDUMP_CMD, '-x', executable])
119+
130120
bits = 0
131121
for line in stdout.splitlines():
132122
tokens = line.split()
133-
if len(tokens)>=2 and tokens[0] == 'architecture:':
134-
arch = tokens[1].rstrip(',')
135123
if len(tokens)>=2 and tokens[0] == 'DllCharacteristics':
136124
bits = int(tokens[1],16)
137-
return (arch,bits)
125+
return bits
138126

139127
IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020
140128
IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040
141129
IMAGE_DLL_CHARACTERISTICS_NX_COMPAT = 0x0100
142130

143-
def check_PE_DYNAMIC_BASE(executable):
131+
def check_PE_DYNAMIC_BASE(executable) -> bool:
144132
'''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
133+
bits = get_PE_dll_characteristics(executable)
134+
return (bits & IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE) == IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE
148135

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):
136+
# Must support high-entropy 64-bit address space layout randomization
137+
# in addition to DYNAMIC_BASE to have secure ASLR.
138+
def check_PE_HIGH_ENTROPY_VA(executable) -> bool:
152139
'''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
140+
bits = get_PE_dll_characteristics(executable)
141+
return (bits & IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA) == IMAGE_DLL_CHARACTERISTICS_HIGH_ENTROPY_VA
160142

161143
def check_PE_RELOC_SECTION(executable) -> bool:
162144
'''Check for a reloc section. This is required for functional ASLR.'''
163-
p = subprocess.Popen([OBJDUMP_CMD, '-h', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
164-
(stdout, stderr) = p.communicate()
165-
if p.returncode:
166-
raise IOError('Error opening file')
145+
stdout = run_command([OBJDUMP_CMD, '-h', executable])
146+
167147
for line in stdout.splitlines():
168148
if '.reloc' in line:
169149
return True
170150
return False
171151

172-
def check_PE_NX(executable):
152+
def check_PE_NX(executable) -> bool:
173153
'''NX: DllCharacteristics bit 0x100 signifies nxcompat (DEP)'''
174-
(arch,bits) = get_PE_dll_characteristics(executable)
154+
bits = get_PE_dll_characteristics(executable)
175155
return (bits & IMAGE_DLL_CHARACTERISTICS_NX_COMPAT) == IMAGE_DLL_CHARACTERISTICS_NX_COMPAT
176156

177-
def get_MACHO_executable_flags(executable):
178-
p = subprocess.Popen([OTOOL_CMD, '-vh', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
179-
(stdout, stderr) = p.communicate()
180-
if p.returncode:
181-
raise IOError('Error opening file')
157+
def get_MACHO_executable_flags(executable) -> List[str]:
158+
stdout = run_command([OTOOL_CMD, '-vh', executable])
182159

183160
flags = []
184161
for line in stdout.splitlines():
@@ -222,10 +199,7 @@ def check_MACHO_LAZY_BINDINGS(executable) -> bool:
222199
Check for no lazy bindings.
223200
We don't use or check for MH_BINDATLOAD. See #18295.
224201
'''
225-
p = subprocess.Popen([OTOOL_CMD, '-l', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
226-
(stdout, stderr) = p.communicate()
227-
if p.returncode:
228-
raise IOError('Error opening file')
202+
stdout = run_command([OTOOL_CMD, '-l', executable])
229203

230204
for line in stdout.splitlines():
231205
tokens = line.split()
@@ -238,10 +212,8 @@ def check_MACHO_Canary(executable) -> bool:
238212
'''
239213
Check for use of stack canary
240214
'''
241-
p = subprocess.Popen([OTOOL_CMD, '-Iv', executable], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True)
242-
(stdout, stderr) = p.communicate()
243-
if p.returncode:
244-
raise IOError('Error opening file')
215+
stdout = run_command([OTOOL_CMD, '-Iv', executable])
216+
245217
ok = False
246218
for line in stdout.splitlines():
247219
if '___stack_chk_fail' in line:
@@ -270,7 +242,7 @@ def check_MACHO_Canary(executable) -> bool:
270242
]
271243
}
272244

273-
def identify_executable(executable):
245+
def identify_executable(executable) -> Optional[str]:
274246
with open(filename, 'rb') as f:
275247
magic = f.read(4)
276248
if magic.startswith(b'MZ'):
@@ -292,18 +264,12 @@ def identify_executable(executable):
292264
continue
293265

294266
failed = []
295-
warning = []
296267
for (name, func) in CHECKS[etype]:
297268
if not func(filename):
298-
if name in NONFATAL:
299-
warning.append(name)
300-
else:
301-
failed.append(name)
269+
failed.append(name)
302270
if failed:
303271
print('%s: failed %s' % (filename, ' '.join(failed)))
304272
retval = 1
305-
if warning:
306-
print('%s: warning %s' % (filename, ' '.join(warning)))
307273
except IOError:
308274
print('%s: cannot open' % filename)
309275
retval = 1

0 commit comments

Comments
 (0)