Skip to content

Commit fa0b5d6

Browse files
committed
Merge bitcoin#30423: contrib: simplify test-security-check
1bc9f64 contrib: assume binary existence in sec/sym checks (fanquake) 51d8f43 contrib: simplify ELF test-security-check (fanquake) 1810e20 contrib: simplify PE test-security-check (fanquake) 6c9746f contrib: simplify MACHO test-security-check (fanquake) Pull request description: The current `test-security-check` script is hard to understand, and change (i.e https://github.com/bitcoin/bitcoin/pull/29987/files#diff-52aa0cda44721f089e53b128cb1232a876006ef257b211655456b17dfb2ec712); tests are also not done in isolation (when-possible). Fix that, and add missing checks. Simplifies future toolchain/security/hardening changes. ACKs for top commit: hebasto: ACK 1bc9f64 (assuming my Guix hashes match; I'll provide them shortly). TheCharlatan: ACK 1bc9f64 Tree-SHA512: 1885d0ce63a94ffa61345327f919da20b63de6dd4148d6db3ee8bad4485253a36e8ab0dbee48cecc02ea35d139edfed75453af45fc364bcbef6fe16b6823bc7a
2 parents 9607277 + 1bc9f64 commit fa0b5d6

File tree

3 files changed

+73
-119
lines changed

3 files changed

+73
-119
lines changed

contrib/devtools/security-check.py

+27-42
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ def check_ELF_RELRO(binary) -> bool:
3838

3939
return have_gnu_relro and have_bindnow
4040

41-
def check_ELF_Canary(binary) -> bool:
41+
def check_ELF_CANARY(binary) -> bool:
4242
'''
4343
Check for use of stack canary
4444
'''
4545
return binary.has_symbol('__stack_chk_fail')
4646

47-
def check_ELF_separate_code(binary):
47+
def check_ELF_SEPARATE_CODE(binary):
4848
'''
4949
Check that sections are appropriately separated in virtual memory,
5050
based on their permissions. This checks for missing -Wl,-z,separate-code
@@ -105,7 +105,7 @@ def check_ELF_separate_code(binary):
105105
return False
106106
return True
107107

108-
def check_ELF_control_flow(binary) -> bool:
108+
def check_ELF_CONTROL_FLOW(binary) -> bool:
109109
'''
110110
Check for control flow instrumentation
111111
'''
@@ -130,7 +130,7 @@ def check_PE_RELOC_SECTION(binary) -> bool:
130130
'''Check for a reloc section. This is required for functional ASLR.'''
131131
return binary.has_relocations
132132

133-
def check_PE_control_flow(binary) -> bool:
133+
def check_PE_CONTROL_FLOW(binary) -> bool:
134134
'''
135135
Check for control flow instrumentation
136136
'''
@@ -145,7 +145,7 @@ def check_PE_control_flow(binary) -> bool:
145145
return True
146146
return False
147147

148-
def check_PE_Canary(binary) -> bool:
148+
def check_PE_CANARY(binary) -> bool:
149149
'''
150150
Check for use of stack canary
151151
'''
@@ -163,7 +163,7 @@ def check_MACHO_FIXUP_CHAINS(binary) -> bool:
163163
'''
164164
return binary.has_dyld_chained_fixups
165165

166-
def check_MACHO_Canary(binary) -> bool:
166+
def check_MACHO_CANARY(binary) -> bool:
167167
'''
168168
Check for use of stack canary
169169
'''
@@ -182,7 +182,7 @@ def check_NX(binary) -> bool:
182182
'''
183183
return binary.has_nx
184184

185-
def check_MACHO_control_flow(binary) -> bool:
185+
def check_MACHO_CONTROL_FLOW(binary) -> bool:
186186
'''
187187
Check for control flow instrumentation
188188
'''
@@ -192,7 +192,7 @@ def check_MACHO_control_flow(binary) -> bool:
192192
return True
193193
return False
194194

195-
def check_MACHO_branch_protection(binary) -> bool:
195+
def check_MACHO_BRANCH_PROTECTION(binary) -> bool:
196196
'''
197197
Check for branch protection instrumentation
198198
'''
@@ -206,8 +206,8 @@ def check_MACHO_branch_protection(binary) -> bool:
206206
('PIE', check_PIE),
207207
('NX', check_NX),
208208
('RELRO', check_ELF_RELRO),
209-
('Canary', check_ELF_Canary),
210-
('separate_code', check_ELF_separate_code),
209+
('CANARY', check_ELF_CANARY),
210+
('SEPARATE_CODE', check_ELF_SEPARATE_CODE),
211211
]
212212

213213
BASE_PE = [
@@ -216,19 +216,19 @@ def check_MACHO_branch_protection(binary) -> bool:
216216
('HIGH_ENTROPY_VA', check_PE_HIGH_ENTROPY_VA),
217217
('NX', check_NX),
218218
('RELOC_SECTION', check_PE_RELOC_SECTION),
219-
('CONTROL_FLOW', check_PE_control_flow),
220-
('Canary', check_PE_Canary),
219+
('CONTROL_FLOW', check_PE_CONTROL_FLOW),
220+
('CANARY', check_PE_CANARY),
221221
]
222222

223223
BASE_MACHO = [
224224
('NOUNDEFS', check_MACHO_NOUNDEFS),
225-
('Canary', check_MACHO_Canary),
225+
('CANARY', check_MACHO_CANARY),
226226
('FIXUP_CHAINS', check_MACHO_FIXUP_CHAINS),
227227
]
228228

229229
CHECKS = {
230230
lief.EXE_FORMATS.ELF: {
231-
lief.ARCHITECTURES.X86: BASE_ELF + [('CONTROL_FLOW', check_ELF_control_flow)],
231+
lief.ARCHITECTURES.X86: BASE_ELF + [('CONTROL_FLOW', check_ELF_CONTROL_FLOW)],
232232
lief.ARCHITECTURES.ARM: BASE_ELF,
233233
lief.ARCHITECTURES.ARM64: BASE_ELF,
234234
lief.ARCHITECTURES.PPC: BASE_ELF,
@@ -240,39 +240,24 @@ def check_MACHO_branch_protection(binary) -> bool:
240240
lief.EXE_FORMATS.MACHO: {
241241
lief.ARCHITECTURES.X86: BASE_MACHO + [('PIE', check_PIE),
242242
('NX', check_NX),
243-
('CONTROL_FLOW', check_MACHO_control_flow)],
244-
lief.ARCHITECTURES.ARM64: BASE_MACHO + [('BRANCH_PROTECTION', check_MACHO_branch_protection)],
243+
('CONTROL_FLOW', check_MACHO_CONTROL_FLOW)],
244+
lief.ARCHITECTURES.ARM64: BASE_MACHO + [('BRANCH_PROTECTION', check_MACHO_BRANCH_PROTECTION)],
245245
}
246246
}
247247

248248
if __name__ == '__main__':
249249
retval: int = 0
250250
for filename in sys.argv[1:]:
251-
try:
252-
binary = lief.parse(filename)
253-
etype = binary.format
254-
arch = binary.abstract.header.architecture
255-
binary.concrete
256-
257-
if etype == lief.EXE_FORMATS.UNKNOWN:
258-
print(f'{filename}: unknown executable format')
259-
retval = 1
260-
continue
261-
262-
if arch == lief.ARCHITECTURES.NONE:
263-
print(f'{filename}: unknown architecture')
264-
retval = 1
265-
continue
266-
267-
failed: list[str] = []
268-
for (name, func) in CHECKS[etype][arch]:
269-
if not func(binary):
270-
failed.append(name)
271-
if failed:
272-
print(f'{filename}: failed {" ".join(failed)}')
273-
retval = 1
274-
except IOError:
275-
print(f'{filename}: cannot open')
251+
binary = lief.parse(filename)
252+
etype = binary.format
253+
arch = binary.abstract.header.architecture
254+
binary.concrete
255+
256+
failed: list[str] = []
257+
for (name, func) in CHECKS[etype][arch]:
258+
if not func(binary):
259+
failed.append(name)
260+
if failed:
261+
print(f'{filename}: failed {" ".join(failed)}')
276262
retval = 1
277263
sys.exit(retval)
278-

contrib/devtools/symbol-check.py

+9-17
Original file line numberDiff line numberDiff line change
@@ -299,22 +299,14 @@ def check_ELF_ABI(binary) -> bool:
299299
if __name__ == '__main__':
300300
retval: int = 0
301301
for filename in sys.argv[1:]:
302-
try:
303-
binary = lief.parse(filename)
304-
etype = binary.format
305-
if etype == lief.EXE_FORMATS.UNKNOWN:
306-
print(f'{filename}: unknown executable format')
307-
retval = 1
308-
continue
309-
310-
failed: list[str] = []
311-
for (name, func) in CHECKS[etype]:
312-
if not func(binary):
313-
failed.append(name)
314-
if failed:
315-
print(f'{filename}: failed {" ".join(failed)}')
316-
retval = 1
317-
except IOError:
318-
print(f'{filename}: cannot open')
302+
binary = lief.parse(filename)
303+
etype = binary.format
304+
305+
failed: list[str] = []
306+
for (name, func) in CHECKS[etype]:
307+
if not func(binary):
308+
failed.append(name)
309+
if failed:
310+
print(f'{filename}: failed {" ".join(failed)}')
319311
retval = 1
320312
sys.exit(retval)

contrib/devtools/test-security-check.py

+37-60
Original file line numberDiff line numberDiff line change
@@ -59,33 +59,20 @@ def test_ELF(self):
5959
arch = get_arch(cxx, source, executable)
6060

6161
if arch == lief.ARCHITECTURES.X86:
62-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-zexecstack','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
63-
(1, executable+': failed PIE NX RELRO CONTROL_FLOW'))
64-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
65-
(1, executable+': failed PIE RELRO CONTROL_FLOW'))
66-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
67-
(1, executable+': failed PIE RELRO CONTROL_FLOW'))
68-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-znorelro','-pie','-fPIE', '-Wl,-z,separate-code']),
69-
(1, executable+': failed RELRO CONTROL_FLOW'))
70-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,noseparate-code']),
71-
(1, executable+': failed separate_code CONTROL_FLOW'))
72-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code']),
73-
(1, executable+': failed CONTROL_FLOW'))
74-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code', '-fcf-protection=full']),
75-
(0, ''))
62+
pass_flags = ['-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code', '-fcf-protection=full']
63+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-zexecstack']), (1, executable + ': failed NX'))
64+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-no-pie','-fno-PIE']), (1, executable + ': failed PIE'))
65+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-znorelro']), (1, executable + ': failed RELRO'))
66+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-z,noseparate-code']), (1, executable + ': failed SEPARATE_CODE'))
67+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fcf-protection=none']), (1, executable + ': failed CONTROL_FLOW'))
68+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
7669
else:
77-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-zexecstack','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
78-
(1, executable+': failed PIE NX RELRO'))
79-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
80-
(1, executable+': failed PIE RELRO'))
81-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-znorelro','-no-pie','-fno-PIE', '-Wl,-z,separate-code']),
82-
(1, executable+': failed PIE RELRO'))
83-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-znorelro','-pie','-fPIE', '-Wl,-z,separate-code']),
84-
(1, executable+': failed RELRO'))
85-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,noseparate-code']),
86-
(1, executable+': failed separate_code'))
87-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-znoexecstack','-Wl,-zrelro','-Wl,-z,now','-pie','-fPIE', '-Wl,-z,separate-code']),
88-
(0, ''))
70+
pass_flags = ['-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code']
71+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-zexecstack']), (1, executable + ': failed NX'))
72+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-no-pie','-fno-PIE']), (1, executable + ': failed PIE'))
73+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-znorelro']), (1, executable + ': failed RELRO'))
74+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-z,noseparate-code']), (1, executable + ': failed SEPARATE_CODE'))
75+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
8976

9077
clean_files(source, executable)
9178

@@ -95,20 +82,16 @@ def test_PE(self):
9582
cxx = determine_wellknown_cmd('CXX', 'x86_64-w64-mingw32-g++')
9683
write_testcode(source)
9784

98-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,--disable-nxcompat','-Wl,--disable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-no-pie','-fno-PIE','-fno-stack-protector']),
99-
(1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION CONTROL_FLOW Canary'))
100-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,--nxcompat','-Wl,--disable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-no-pie','-fno-PIE','-fstack-protector-all', '-lssp']),
101-
(1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION CONTROL_FLOW'))
102-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-no-pie','-fno-PIE','-fstack-protector-all', '-lssp']),
103-
(1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW'))
104-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-pie','-fPIE','-fstack-protector-all', '-lssp']),
105-
(1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW')) # -pie -fPIE does nothing unless --dynamicbase is also supplied
106-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--disable-high-entropy-va','-pie','-fPIE','-fstack-protector-all', '-lssp']),
107-
(1, executable+': failed HIGH_ENTROPY_VA CONTROL_FLOW'))
108-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE','-fstack-protector-all', '-lssp']),
109-
(1, executable+': failed CONTROL_FLOW'))
110-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE', '-fcf-protection=full','-fstack-protector-all', '-lssp']),
111-
(0, ''))
85+
pass_flags = ['-Wl,--nxcompat', '-Wl,--enable-reloc-section', '-Wl,--dynamicbase', '-Wl,--high-entropy-va', '-pie', '-fPIE', '-fcf-protection=full', '-fstack-protector-all', '-lssp']
86+
87+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fno-stack-protector']), (1, executable + ': failed CANARY'))
88+
# https://github.com/lief-project/LIEF/issues/1076 - in future, we could test this individually.
89+
# self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-reloc-section']), (1, executable + ': failed RELOC_SECTION'))
90+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-nxcompat']), (1, executable + ': failed NX'))
91+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-dynamicbase']), (1, executable + ': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA')) # -pie -fPIE does nothing without --dynamicbase
92+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-high-entropy-va']), (1, executable + ': failed HIGH_ENTROPY_VA'))
93+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fcf-protection=none']), (1, executable + ': failed CONTROL_FLOW'))
94+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
11295

11396
clean_files(source, executable)
11497

@@ -120,27 +103,21 @@ def test_MACHO(self):
120103
arch = get_arch(cxx, source, executable)
121104

122105
if arch == lief.ARCHITECTURES.X86:
123-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-fno-stack-protector', '-Wl,-no_fixup_chains']),
124-
(1, executable+': failed NOUNDEFS Canary FIXUP_CHAINS PIE CONTROL_FLOW'))
125-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-flat_namespace','-fno-stack-protector', '-Wl,-fixup_chains']),
126-
(1, executable+': failed NOUNDEFS Canary CONTROL_FLOW'))
127-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-flat_namespace','-fstack-protector-all', '-Wl,-fixup_chains']),
128-
(1, executable+': failed NOUNDEFS CONTROL_FLOW'))
129-
self.assertEqual(call_security_check(cxx, source, executable, ['-fstack-protector-all', '-Wl,-fixup_chains']),
130-
(1, executable+': failed CONTROL_FLOW'))
131-
self.assertEqual(call_security_check(cxx, source, executable, ['-fstack-protector-all', '-fcf-protection=full', '-Wl,-fixup_chains']),
132-
(0, ''))
106+
pass_flags = ['-Wl,-pie', '-fstack-protector-all', '-fcf-protection=full', '-Wl,-fixup_chains']
107+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-no_pie', '-Wl,-no_fixup_chains']), (1, executable+': failed FIXUP_CHAINS PIE')) # -fixup_chains is incompatible with -no_pie
108+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-no_fixup_chains']), (1, executable + ': failed FIXUP_CHAINS'))
109+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fno-stack-protector']), (1, executable + ': failed CANARY'))
110+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-flat_namespace']), (1, executable + ': failed NOUNDEFS'))
111+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fcf-protection=none']), (1, executable + ': failed CONTROL_FLOW'))
112+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
133113
else:
134-
# arm64 darwin doesn't support non-PIE binaries, control flow or executable stacks
135-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-flat_namespace','-fno-stack-protector', '-Wl,-no_fixup_chains']),
136-
(1, executable+': failed NOUNDEFS Canary FIXUP_CHAINS BRANCH_PROTECTION'))
137-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-flat_namespace','-fno-stack-protector', '-Wl,-fixup_chains', '-mbranch-protection=bti']),
138-
(1, executable+': failed NOUNDEFS Canary'))
139-
self.assertEqual(call_security_check(cxx, source, executable, ['-Wl,-flat_namespace','-fstack-protector-all', '-Wl,-fixup_chains', '-mbranch-protection=bti']),
140-
(1, executable+': failed NOUNDEFS'))
141-
self.assertEqual(call_security_check(cxx, source, executable, ['-fstack-protector-all', '-Wl,-fixup_chains', '-mbranch-protection=bti']),
142-
(0, ''))
143-
114+
# arm64 darwin doesn't support non-PIE binaries or executable stacks
115+
pass_flags = ['-fstack-protector-all', '-Wl,-fixup_chains', '-mbranch-protection=bti']
116+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-mbranch-protection=none']), (1, executable + ': failed BRANCH_PROTECTION'))
117+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-no_fixup_chains']), (1, executable + ': failed FIXUP_CHAINS'))
118+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fno-stack-protector']), (1, executable + ': failed CANARY'))
119+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-flat_namespace']), (1, executable + ': failed NOUNDEFS'))
120+
self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))
144121

145122
clean_files(source, executable)
146123

0 commit comments

Comments
 (0)