diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -109,6 +109,18 @@ return True +def check_ELF_control_flow(binary) -> bool: + """ + Check for control flow instrumentation + """ + main = binary.get_function_address("main") + content = binary.get_content_from_virtual_address( + main, 4, lief.Binary.VA_TYPES.AUTO + ) + + return content == [243, 15, 30, 250] # endbr64 + + def check_PE_DYNAMIC_BASE(binary) -> bool: """PIE: DllCharacteristics bit 0x40 signifies dynamicbase (ASLR)""" return ( @@ -134,6 +146,22 @@ return binary.has_relocations +def check_PE_control_flow(binary) -> bool: + """ + Check for control flow instrumentation + """ + main = binary.get_symbol("main").value + + section_addr = binary.section_from_rva(main).virtual_address + virtual_address = binary.optional_header.imagebase + section_addr + main + + content = binary.get_content_from_virtual_address( + virtual_address, 4, lief.Binary.VA_TYPES.VA + ) + + return content == [243, 15, 30, 250] # endbr64 + + def check_MACHO_NOUNDEFS(binary) -> bool: """ Check for no undefined references. @@ -163,6 +191,16 @@ return binary.has_nx +def check_MACHO_control_flow(binary) -> bool: + """ + Check for control flow instrumentation + """ + content = binary.get_content_from_virtual_address( + binary.entrypoint, 4, lief.Binary.VA_TYPES.AUTO + ) + return content == [243, 15, 30, 250] # endbr64 + + BASE_ELF = [ ("PIE", check_PIE), ("NX", check_NX), @@ -177,6 +215,7 @@ ("HIGH_ENTROPY_VA", check_PE_HIGH_ENTROPY_VA), ("NX", check_NX), ("RELOC_SECTION", check_PE_RELOC_SECTION), + ("CONTROL_FLOW", check_PE_control_flow), ] BASE_MACHO = [ @@ -184,11 +223,12 @@ ("NOUNDEFS", check_MACHO_NOUNDEFS), ("NX", check_NX), ("Canary", check_MACHO_Canary), + ("CONTROL_FLOW", check_MACHO_control_flow), ] CHECKS = { lief.EXE_FORMATS.ELF: { - lief.ARCHITECTURES.X86: BASE_ELF, + lief.ARCHITECTURES.X86: BASE_ELF + [("CONTROL_FLOW", check_ELF_control_flow)], lief.ARCHITECTURES.ARM: BASE_ELF, lief.ARCHITECTURES.ARM64: BASE_ELF, }, diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -10,6 +10,7 @@ import unittest from typing import List +import lief # type:ignore from utils import determine_wellknown_cmd @@ -49,111 +50,238 @@ return p.returncode, p.stdout.rstrip() +def get_arch(cc, source, executable): + subprocess.run([*cc, source, "-o", executable], check=True) + binary = lief.parse(executable) + arch = binary.abstract.header.architecture + os.remove(executable) + return arch + + class TestSecurityChecks(unittest.TestCase): def test_ELF(self): source = "test1.c" executable = "test1" cc = determine_wellknown_cmd("CC", "gcc") write_testcode(source) + arch = get_arch(cc, source, executable) - self.assertEqual( - call_security_check( - cc, - source, - executable, - [ - "-Wl,-zexecstack", - "-fno-stack-protector", - "-Wl,-znorelro", - "-no-pie", - "-fno-PIE", - "-Wl,-z,separate-code", - ], - ), - (1, executable + ": failed PIE NX RELRO Canary"), - ) - self.assertEqual( - call_security_check( - cc, - source, - executable, - [ - "-Wl,-znoexecstack", - "-fno-stack-protector", - "-Wl,-znorelro", - "-no-pie", - "-fno-PIE", - "-Wl,-z,separate-code", - ], - ), - (1, executable + ": failed PIE RELRO Canary"), - ) - self.assertEqual( - call_security_check( - cc, - source, - executable, - [ - "-Wl,-znoexecstack", - "-fstack-protector-all", - "-Wl,-znorelro", - "-no-pie", - "-fno-PIE", - "-Wl,-z,separate-code", - ], - ), - (1, executable + ": failed PIE RELRO"), - ) - self.assertEqual( - call_security_check( - cc, - source, - executable, - [ - "-Wl,-znoexecstack", - "-fstack-protector-all", - "-Wl,-znorelro", - "-pie", - "-fPIE", - "-Wl,-z,separate-code", - ], - ), - (1, executable + ": failed RELRO"), - ) - self.assertEqual( - call_security_check( - cc, - source, - executable, - [ - "-Wl,-znoexecstack", - "-fstack-protector-all", - "-Wl,-zrelro", - "-Wl,-z,now", - "-pie", - "-fPIE", - "-Wl,-z,noseparate-code", - ], - ), - (1, executable + ": failed separate_code"), - ) - self.assertEqual( - call_security_check( - cc, - source, - executable, - [ - "-Wl,-znoexecstack", - "-fstack-protector-all", - "-Wl,-zrelro", - "-Wl,-z,now", - "-pie", - "-fPIE", - "-Wl,-z,separate-code", - ], - ), - (0, ""), - ) + if arch == lief.ARCHITECTURES.X86: + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-zexecstack", + "-fno-stack-protector", + "-Wl,-znorelro", + "-no-pie", + "-fno-PIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed PIE NX RELRO Canary CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fno-stack-protector", + "-Wl,-znorelro", + "-no-pie", + "-fno-PIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed PIE RELRO Canary CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-znorelro", + "-no-pie", + "-fno-PIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed PIE RELRO CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-znorelro", + "-pie", + "-fPIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed RELRO CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-zrelro", + "-Wl,-z,now", + "-pie", + "-fPIE", + "-Wl,-z,noseparate-code", + ], + ), + (1, executable + ": failed separate_code CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-zrelro", + "-Wl,-z,now", + "-pie", + "-fPIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-zrelro", + "-Wl,-z,now", + "-pie", + "-fPIE", + "-Wl,-z,separate-code", + "-fcf-protection=full", + ], + ), + (0, ""), + ) + else: + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-zexecstack", + "-fno-stack-protector", + "-Wl,-znorelro", + "-no-pie", + "-fno-PIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed PIE NX RELRO Canary"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fno-stack-protector", + "-Wl,-znorelro", + "-no-pie", + "-fno-PIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed PIE RELRO Canary"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-znorelro", + "-no-pie", + "-fno-PIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed PIE RELRO"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-znorelro", + "-pie", + "-fPIE", + "-Wl,-z,separate-code", + ], + ), + (1, executable + ": failed RELRO"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-zrelro", + "-Wl,-z,now", + "-pie", + "-fPIE", + "-Wl,-z,noseparate-code", + ], + ), + (1, executable + ": failed separate_code"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,-znoexecstack", + "-fstack-protector-all", + "-Wl,-zrelro", + "-Wl,-z,now", + "-pie", + "-fPIE", + "-Wl,-z,separate-code", + ], + ), + (0, ""), + ) clean_files(source, executable) @@ -180,7 +308,8 @@ ( 1, executable - + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION", + + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION" + " CONTROL_FLOW", ), ) self.assertEqual( @@ -197,7 +326,12 @@ "-fno-PIE", ], ), - (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION"), + ( + 1, + executable + + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION" + " CONTROL_FLOW", + ), ) self.assertEqual( call_security_check( @@ -213,7 +347,7 @@ "-fno-PIE", ], ), - (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA"), + (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW"), ) self.assertEqual( call_security_check( @@ -230,7 +364,7 @@ "-fPIE", ], ), - (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA"), + (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW"), ) self.assertEqual( call_security_check( @@ -246,7 +380,7 @@ "-fPIE", ], ), - (1, executable + ": failed HIGH_ENTROPY_VA"), + (1, executable + ": failed HIGH_ENTROPY_VA CONTROL_FLOW"), ) self.assertEqual( call_security_check( @@ -262,6 +396,23 @@ "-fPIE", ], ), + (1, executable + ": failed CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,--nxcompat", + "-Wl,--enable-reloc-section", + "-Wl,--dynamicbase", + "-Wl,--high-entropy-va", + "-pie", + "-fPIE", + "-fcf-protection=full", + ], + ), (0, ""), ) @@ -285,7 +436,7 @@ "-fno-stack-protector", ], ), - (1, executable + ": failed PIE NOUNDEFS NX Canary"), + (1, executable + ": failed PIE NOUNDEFS NX Canary CONTROL_FLOW"), ) self.assertEqual( call_security_check( @@ -299,7 +450,7 @@ "-fstack-protector-all", ], ), - (1, executable + ": failed PIE NOUNDEFS NX"), + (1, executable + ": failed PIE NOUNDEFS NX CONTROL_FLOW"), ) self.assertEqual( call_security_check( @@ -308,12 +459,21 @@ executable, ["-Wl,-no_pie", "-Wl,-flat_namespace", "-fstack-protector-all"], ), - (1, executable + ": failed PIE NOUNDEFS"), + (1, executable + ": failed PIE NOUNDEFS CONTROL_FLOW"), ) self.assertEqual( call_security_check( cc, source, executable, ["-Wl,-no_pie", "-fstack-protector-all"] ), + (1, executable + ": failed PIE CONTROL_FLOW"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + ["-Wl,-no_pie", "-fstack-protector-all", "-fcf-protection=full"], + ), (1, executable + ": failed PIE"), ) self.assertEqual( @@ -321,7 +481,7 @@ cc, source, executable, - ["-Wl,-pie", "-fstack-protector-all"], + ["-Wl,-pie", "-fstack-protector-all", "-fcf-protection=full"], ), (0, ""), ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -187,6 +187,9 @@ # Enable stack protection add_cxx_compiler_flags(-fstack-protector-all -Wstack-protector) + # Enable control-flow enforcement + add_cxx_compiler_flags(-fcf-protection=full) + # Enable some buffer overflow checking, except in -O0 builds which # do not support them add_compiler_flags(-U_FORTIFY_SOURCE)