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 @@ -203,6 +203,21 @@ return binary.has_nx +def check_control_flow(executable) -> bool: + """ + Check for control flow instrumentation + """ + binary = lief.parse(executable) + + content = binary.get_content_from_virtual_address( + binary.entrypoint, 4, lief.Binary.VA_TYPES.AUTO + ) + + if content == [243, 15, 30, 250]: # endbr64 + return True + return False + + CHECKS = { "ELF": [ ("PIE", check_ELF_PIE), @@ -223,12 +238,13 @@ ("NOUNDEFS", check_MACHO_NOUNDEFS), ("NX", check_NX), ("Canary", check_MACHO_Canary), + ("CONTROL_FLOW", check_control_flow), ], } def identify_executable(executable) -> Optional[str]: - with open(filename, "rb") as f: + with open(executable, "rb") as f: magic = f.read(4) if magic.startswith(b"MZ"): return "PE" 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 @@ -30,16 +30,13 @@ def call_security_check(cc, source, executable, options): - subprocess.check_call([*cc, source, "-o", executable] + options) - p = subprocess.Popen( - ["./security-check.py", executable], + subprocess.run([*cc, source, "-o", executable] + options, check=True) + p = subprocess.run( + ["./contrib/devtools/security-check.py", executable], stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - stdin=subprocess.PIPE, universal_newlines=True, ) - (stdout, stderr) = p.communicate() - return (p.returncode, stdout.rstrip()) + return p.returncode, p.stdout.rstrip() class TestSecurityChecks(unittest.TestCase): @@ -163,13 +160,18 @@ executable, [ "-Wl,--no-nxcompat", + "-Wl,--disable-reloc-section", "-Wl,--no-dynamicbase", "-Wl,--no-high-entropy-va", "-no-pie", "-fno-PIE", ], ), - (1, executable + ": failed DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION"), + ( + 1, + executable + + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION", + ), ) self.assertEqual( call_security_check( @@ -178,13 +180,14 @@ executable, [ "-Wl,--nxcompat", + "-Wl,--disable-reloc-section", "-Wl,--no-dynamicbase", "-Wl,--no-high-entropy-va", "-no-pie", "-fno-PIE", ], ), - (1, executable + ": failed DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION"), + (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION"), ) self.assertEqual( call_security_check( @@ -193,13 +196,31 @@ executable, [ "-Wl,--nxcompat", - "-Wl,--dynamicbase", + "-Wl,--enable-reloc-section", + "-Wl,--no-dynamicbase", "-Wl,--no-high-entropy-va", "-no-pie", "-fno-PIE", ], ), - (1, executable + ": failed HIGH_ENTROPY_VA RELOC_SECTION"), + (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA"), + ) + self.assertEqual( + call_security_check( + cc, + source, + executable, + [ + "-Wl,--nxcompat", + "-Wl,--enable-reloc-section", + "-Wl,--no-dynamicbase", + "-Wl,--no-high-entropy-va", + # -pie -fPIE does nothing unless --dynamicbase is also supplied + "-pie", + "-fPIE", + ], + ), + (1, executable + ": failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA"), ) self.assertEqual( call_security_check( @@ -208,13 +229,14 @@ executable, [ "-Wl,--nxcompat", + "-Wl,--enable-reloc-section", "-Wl,--dynamicbase", - "-Wl,--high-entropy-va", - "-no-pie", - "-fno-PIE", + "-Wl,--no-high-entropy-va", + "-pie", + "-fPIE", ], ), - (1, executable + ": failed RELOC_SECTION"), + (1, executable + ": failed HIGH_ENTROPY_VA"), ) self.assertEqual( call_security_check( @@ -223,6 +245,7 @@ executable, [ "-Wl,--nxcompat", + "-Wl,--enable-reloc-section", "-Wl,--dynamicbase", "-Wl,--high-entropy-va", "-pie", @@ -252,7 +275,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( @@ -266,7 +289,7 @@ "-fstack-protector-all", ], ), - (1, executable + ": failed PIE NOUNDEFS NX"), + (1, executable + ": failed PIE NOUNDEFS NX CONTROL_FLOW"), ) self.assertEqual( call_security_check( @@ -275,17 +298,29 @@ 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( call_security_check( - cc, source, executable, ["-Wl,-pie", "-fstack-protector-all"] + cc, + source, + executable, + ["-Wl,-pie", "-fstack-protector-all", "-fcf-protection=full"], ), (0, ""), ) diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -87,20 +87,22 @@ ), ) - # finally, check a conforming file that simply uses a math function + # finally, check a simple conforming binary source = "test3.c" executable = "test3" + with open(source, "w", encoding="utf8") as f: f.write(""" - #include + #include int main() { - return (int)pow(2.0, 4.0); + printf("42"); + return 0; } """) - self.assertEqual(call_symbol_check(cc, source, executable, ["-lm"]), (0, "")) + self.assertEqual(call_symbol_check(cc, source, executable, []), (0, "")) def test_MACHO(self): source = "test1.c" diff --git a/depends/packages/boost.mk b/depends/packages/boost.mk --- a/depends/packages/boost.mk +++ b/depends/packages/boost.mk @@ -25,6 +25,7 @@ $(package)_config_libraries=atomic,date_time,thread,test $(package)_cxxflags=-std=c++17 -fvisibility=hidden $(package)_cxxflags_linux=-fPIC +$(package)_cxxflags_x86_64_darwin=-fcf-protection=full endef define $(package)_preprocess_cmds