2018-04-13 23:37:53 +08:00
#!/usr/bin/env python3
2022-12-24 23:49:50 +00:00
# Copyright (c) 2015-2022 The Bitcoin Core developers
2016-09-11 15:32:22 -06:00
# Distributed under the MIT software license, see the accompanying
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
2015-10-19 14:53:56 +02:00
'''
Test script for security - check . py
'''
2023-04-21 10:28:53 +01:00
import lief
2021-03-13 18:27:11 +02:00
import os
2015-10-19 14:53:56 +02:00
import subprocess
import unittest
2021-01-21 13:52:40 -05:00
from utils import determine_wellknown_cmd
2015-10-19 14:53:56 +02:00
def write_testcode ( filename ) :
2018-06-12 17:49:20 +02:00
with open ( filename , ' w ' , encoding = " utf8 " ) as f :
2015-10-19 14:53:56 +02:00
f . write ( '''
2024-07-04 20:23:51 +00:00
#include <cstdio>
2015-10-19 14:53:56 +02:00
int main ( )
{
2024-07-04 20:23:51 +00:00
std : : printf ( " the quick brown fox jumps over the lazy god \\ n " ) ;
2015-10-19 14:53:56 +02:00
return 0 ;
}
''' )
2021-03-13 18:27:11 +02:00
def clean_files ( source , executable ) :
os . remove ( source )
os . remove ( executable )
2024-05-09 23:44:31 +08:00
def env_flags ( ) - > list [ str ] :
2021-09-30 20:17:53 -04:00
# This should behave the same as AC_TRY_LINK, so arrange well-known flags
# in the same order as autoconf would.
#
# See the definitions for ac_link in autoconf's lib/autoconf/c.m4 file for
# reference.
2024-05-09 23:44:31 +08:00
flags : list [ str ] = [ ]
2024-07-03 19:38:01 +00:00
for var in [ ' CXXFLAGS ' , ' CPPFLAGS ' , ' LDFLAGS ' ] :
2024-05-09 23:44:31 +08:00
flags + = filter ( None , os . environ . get ( var , ' ' ) . split ( ' ' ) )
return flags
2021-09-30 20:17:53 -04:00
2024-07-03 19:41:12 +00:00
def call_security_check ( cxx : str , source : str , executable : str , options ) - > tuple :
subprocess . run ( [ * cxx , source , ' -o ' , executable ] + env_flags ( ) + options , check = True )
2023-01-17 21:46:35 +01:00
p = subprocess . run ( [ os . path . join ( os . path . dirname ( __file__ ) , ' security-check.py ' ) , executable ] , stdout = subprocess . PIPE , text = True )
2020-05-15 10:52:20 +08:00
return ( p . returncode , p . stdout . rstrip ( ) )
2015-10-19 14:53:56 +02:00
2024-07-03 19:41:12 +00:00
def get_arch ( cxx , source , executable ) :
subprocess . run ( [ * cxx , source , ' -o ' , executable ] + env_flags ( ) , check = True )
2021-10-15 13:41:49 +08:00
binary = lief . parse ( executable )
arch = binary . abstract . header . architecture
os . remove ( executable )
return arch
2015-10-19 14:53:56 +02:00
class TestSecurityChecks ( unittest . TestCase ) :
def test_ELF ( self ) :
2024-07-03 19:38:01 +00:00
source = ' test1.cpp '
2015-10-19 14:53:56 +02:00
executable = ' test1 '
2024-07-03 19:41:12 +00:00
cxx = determine_wellknown_cmd ( ' CXX ' , ' g++ ' )
2015-10-19 14:53:56 +02:00
write_testcode ( source )
2024-07-03 19:41:12 +00:00
arch = get_arch ( cxx , source , executable )
2015-10-19 14:53:56 +02:00
2021-10-15 13:41:49 +08:00
if arch == lief . ARCHITECTURES . X86 :
2023-02-03 16:16:56 +00:00
pass_flags = [ ' -D_FORTIFY_SOURCE=3 ' , ' -Wl,-znoexecstack ' , ' -Wl,-zrelro ' , ' -Wl,-z,now ' , ' -pie ' , ' -fPIE ' , ' -Wl,-z,separate-code ' , ' -fcf-protection=full ' ]
2024-07-10 16:37:14 +01:00
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 ' ) )
2023-02-03 16:16:56 +00:00
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -U_FORTIFY_SOURCE ' ] ) , ( 1 , executable + ' : failed FORTIFY ' ) )
2024-07-10 16:37:14 +01:00
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags ) , ( 0 , ' ' ) )
2021-10-15 13:41:49 +08:00
else :
2023-02-03 16:16:56 +00:00
pass_flags = [ ' -D_FORTIFY_SOURCE=3 ' , ' -Wl,-znoexecstack ' , ' -Wl,-zrelro ' , ' -Wl,-z,now ' , ' -pie ' , ' -fPIE ' , ' -Wl,-z,separate-code ' ]
2024-07-10 16:37:14 +01:00
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -Wl,-zexecstack ' ] ) , ( 1 , executable + ' : failed NX ' ) )
2023-02-03 16:16:56 +00:00
# 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 ' ) )
2024-07-10 16:37:14 +01:00
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 , ' ' ) )
2015-10-19 14:53:56 +02:00
2021-03-13 18:27:11 +02:00
clean_files ( source , executable )
2020-03-26 11:37:32 +08:00
def test_PE ( self ) :
2024-07-03 19:38:01 +00:00
source = ' test1.cpp '
2018-04-27 00:43:01 +08:00
executable = ' test1.exe '
2024-07-03 19:41:12 +00:00
cxx = determine_wellknown_cmd ( ' CXX ' , ' x86_64-w64-mingw32-g++ ' )
2018-04-27 00:43:01 +08:00
write_testcode ( source )
2024-07-10 16:17:21 +01:00
pass_flags = [ ' -Wl,--nxcompat ' , ' -Wl,--enable-reloc-section ' , ' -Wl,--dynamicbase ' , ' -Wl,--high-entropy-va ' , ' -pie ' , ' -fPIE ' , ' -fcf-protection=full ' , ' -fstack-protector-all ' , ' -lssp ' ]
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -fno-stack-protector ' ] ) , ( 1 , executable + ' : failed CANARY ' ) )
# https://github.com/lief-project/LIEF/issues/1076 - in future, we could test this individually.
# self.assertEqual(call_security_check(cxx, source, executable, pass_flags + ['-Wl,--disable-reloc-section']), (1, executable + ': failed RELOC_SECTION'))
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -Wl,--disable-nxcompat ' ] ) , ( 1 , executable + ' : failed NX ' ) )
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
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -Wl,--disable-high-entropy-va ' ] ) , ( 1 , executable + ' : failed HIGH_ENTROPY_VA ' ) )
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 ) , ( 0 , ' ' ) )
2015-10-19 14:53:56 +02:00
2021-03-13 18:27:11 +02:00
clean_files ( source , executable )
2020-03-24 12:37:57 +08:00
def test_MACHO ( self ) :
2024-07-03 19:38:01 +00:00
source = ' test1.cpp '
2020-03-24 12:37:57 +08:00
executable = ' test1 '
2024-07-03 19:41:12 +00:00
cxx = determine_wellknown_cmd ( ' CXX ' , ' clang++ ' )
2020-03-24 12:37:57 +08:00
write_testcode ( source )
2024-07-03 19:41:12 +00:00
arch = get_arch ( cxx , source , executable )
2022-01-11 21:18:24 +08:00
if arch == lief . ARCHITECTURES . X86 :
2024-07-10 15:46:55 +01:00
pass_flags = [ ' -Wl,-pie ' , ' -fstack-protector-all ' , ' -fcf-protection=full ' , ' -Wl,-fixup_chains ' ]
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
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -Wl,-no_fixup_chains ' ] ) , ( 1 , executable + ' : failed FIXUP_CHAINS ' ) )
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -fno-stack-protector ' ] ) , ( 1 , executable + ' : failed CANARY ' ) )
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -Wl,-flat_namespace ' ] ) , ( 1 , executable + ' : failed NOUNDEFS ' ) )
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 ) , ( 0 , ' ' ) )
2022-01-11 21:18:24 +08:00
else :
2024-07-10 15:46:55 +01:00
# arm64 darwin doesn't support non-PIE binaries or executable stacks
pass_flags = [ ' -fstack-protector-all ' , ' -Wl,-fixup_chains ' , ' -mbranch-protection=bti ' ]
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -mbranch-protection=none ' ] ) , ( 1 , executable + ' : failed BRANCH_PROTECTION ' ) )
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -Wl,-no_fixup_chains ' ] ) , ( 1 , executable + ' : failed FIXUP_CHAINS ' ) )
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -fno-stack-protector ' ] ) , ( 1 , executable + ' : failed CANARY ' ) )
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags + [ ' -Wl,-flat_namespace ' ] ) , ( 1 , executable + ' : failed NOUNDEFS ' ) )
self . assertEqual ( call_security_check ( cxx , source , executable , pass_flags ) , ( 0 , ' ' ) )
2020-03-24 12:37:57 +08:00
2021-03-13 18:27:11 +02:00
clean_files ( source , executable )
2015-10-19 14:53:56 +02:00
if __name__ == ' __main__ ' :
unittest . main ( )