diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py index 46f9ee915f7e2..4c20685b51c94 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -7,6 +7,7 @@ Exit status will be 0 if successful, and the program will be silent. Otherwise the exit status will be 1 and it will log which executables failed which checks. ''' +import re import sys import lief @@ -116,6 +117,25 @@ def check_ELF_CONTROL_FLOW(binary) -> bool: return True return False +def check_ELF_FORTIFY(binary) -> bool: + + # bitcoin-util does not currently contain any fortified functions + if 'Bitcoin Core bitcoin-util utility version ' in binary.strings: + return True + + chk_funcs = set() + + for sym in binary.imported_symbols: + match = re.search(r'__[a-z]*_chk', sym.name) + if match: + chk_funcs.add(match.group(0)) + + # ignore stack-protector and bdb + chk_funcs.discard('__stack_chk') + chk_funcs.discard('__db_chk') + + return len(chk_funcs) >= 1 + def check_PE_DYNAMIC_BASE(binary) -> bool: '''PIE: DllCharacteristics bit 0x40 signifies dynamicbase (ASLR)''' return lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE in binary.optional_header.dll_characteristics_lists @@ -228,11 +248,11 @@ def check_MACHO_BRANCH_PROTECTION(binary) -> bool: CHECKS = { lief.EXE_FORMATS.ELF: { - lief.ARCHITECTURES.X86: BASE_ELF + [('CONTROL_FLOW', check_ELF_CONTROL_FLOW)], - lief.ARCHITECTURES.ARM: BASE_ELF, - lief.ARCHITECTURES.ARM64: BASE_ELF, - lief.ARCHITECTURES.PPC: BASE_ELF, - lief.ARCHITECTURES.RISCV: BASE_ELF, + lief.ARCHITECTURES.X86: BASE_ELF + [('CONTROL_FLOW', check_ELF_CONTROL_FLOW), ('FORTIFY', check_ELF_FORTIFY)], + lief.ARCHITECTURES.ARM: BASE_ELF + [('FORTIFY', check_ELF_FORTIFY)], + lief.ARCHITECTURES.ARM64: BASE_ELF + [('FORTIFY', check_ELF_FORTIFY)], + lief.ARCHITECTURES.PPC: BASE_ELF + [('FORTIFY', check_ELF_FORTIFY)], + lief.ARCHITECTURES.RISCV: BASE_ELF, # Skip FORTIFY. See https://github.com/lief-project/LIEF/issues/1082. }, lief.EXE_FORMATS.PE: { lief.ARCHITECTURES.X86: BASE_PE, diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index 4bec6bfe7c226..99f171608ddd2 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -59,17 +59,22 @@ def test_ELF(self): arch = get_arch(cxx, source, executable) if arch == lief.ARCHITECTURES.X86: - pass_flags = ['-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code', '-fcf-protection=full'] + pass_flags = ['-D_FORTIFY_SOURCE=3', '-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code', '-fcf-protection=full'] self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-zexecstack']), (1, executable + ': failed NX')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-no-pie','-fno-PIE']), (1, executable + ': failed PIE')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-znorelro']), (1, executable + ': failed RELRO')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-z,noseparate-code']), (1, executable + ': failed SEPARATE_CODE')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-fcf-protection=none']), (1, executable + ': failed CONTROL_FLOW')) + self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-U_FORTIFY_SOURCE']), (1, executable + ': failed FORTIFY')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, '')) else: - pass_flags = ['-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code'] + pass_flags = ['-D_FORTIFY_SOURCE=3', '-Wl,-znoexecstack', '-Wl,-zrelro', '-Wl,-z,now', '-pie', '-fPIE', '-Wl,-z,separate-code'] self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-zexecstack']), (1, executable + ': failed NX')) - self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-no-pie','-fno-PIE']), (1, executable + ': failed PIE')) + # LIEF fails to parse RISC-V with no PIE correctly, and doesn't detect the fortified function, + # so skip this test for RISC-V (for now). See https://github.com/lief-project/LIEF/issues/1082. + if arch != lief.ARCHITECTURES.RISCV: + self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-no-pie','-fno-PIE']), (1, executable + ': failed PIE')) + self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-U_FORTIFY_SOURCE']), (1, executable + ': failed FORTIFY')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-znorelro']), (1, executable + ': failed RELRO')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,-z,noseparate-code']), (1, executable + ': failed SEPARATE_CODE')) self.assertEqual(call_security_check(cxx, source, executable, pass_flags), (0, ''))