From c89c55365be72617b8607b9bc13fdac247ac004a Mon Sep 17 00:00:00 2001 From: Jared Tate <13957390+JaredTate@users.noreply.github.com> Date: Thu, 28 Mar 2024 18:02:17 -0600 Subject: [PATCH] More GUIX Test Fix --- contrib/devtools/security-check.py | 44 ++++------ contrib/devtools/symbol-check.py | 106 ++++++++++-------------- contrib/devtools/test-security-check.py | 65 +++++++-------- contrib/devtools/test-symbol-check.py | 42 ++++++++-- 4 files changed, 125 insertions(+), 132 deletions(-) diff --git a/contrib/devtools/security-check.py b/contrib/devtools/security-check.py index 100da966eb..f0d781a6b9 100755 --- a/contrib/devtools/security-check.py +++ b/contrib/devtools/security-check.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2022 The Bitcoin Core developers +# Copyright (c) 2015-2021 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' @@ -8,8 +8,9 @@ Otherwise the exit status will be 1 and it will log which executables failed which checks. ''' import sys +from typing import List -import lief +import lief #type:ignore # temporary constant, to be replaced with lief.ELF.ARCH.RISCV # https://github.com/lief-project/LIEF/pull/562 @@ -37,7 +38,7 @@ def check_ELF_RELRO(binary) -> bool: flags = binary.get(lief.ELF.DYNAMIC_TAGS.FLAGS) if flags.value & lief.ELF.DYNAMIC_FLAGS.BIND_NOW: have_bindnow = True - except Exception: + except: have_bindnow = False return have_gnu_relro and have_bindnow @@ -100,6 +101,7 @@ def check_ELF_separate_code(binary): for segment in binary.segments: if segment.type == lief.ELF.SEGMENT_TYPES.LOAD: for section in segment.sections: + assert(section.name not in flags_per_section) flags_per_section[section.name] = segment.flags # Spot-check ELF LOAD program header flags per section # If these sections exist, check them against the expected R/W/E flags @@ -116,7 +118,7 @@ def check_ELF_control_flow(binary) -> bool: main = binary.get_function_address('main') content = binary.get_content_from_virtual_address(main, 4, lief.Binary.VA_TYPES.AUTO) - if content.tolist() == [243, 15, 30, 250]: # endbr64 + if content == [243, 15, 30, 250]: # endbr64 return True return False @@ -145,27 +147,22 @@ def check_PE_control_flow(binary) -> bool: content = binary.get_content_from_virtual_address(virtual_address, 4, lief.Binary.VA_TYPES.VA) - if content.tolist() == [243, 15, 30, 250]: # endbr64 + if content == [243, 15, 30, 250]: # endbr64 return True return False -def check_PE_Canary(binary) -> bool: - ''' - Check for use of stack canary - ''' - return binary.has_symbol('__stack_chk_fail') - def check_MACHO_NOUNDEFS(binary) -> bool: ''' Check for no undefined references. ''' return binary.header.has(lief.MachO.HEADER_FLAGS.NOUNDEFS) -def check_MACHO_FIXUP_CHAINS(binary) -> bool: +def check_MACHO_LAZY_BINDINGS(binary) -> bool: ''' - Check for use of chained fixups. + Check for no lazy bindings. + We don't use or check for MH_BINDATLOAD. See #18295. ''' - return binary.has_dyld_chained_fixups + return binary.dyld_info.lazy_bind == (0,0) def check_MACHO_Canary(binary) -> bool: ''' @@ -192,17 +189,7 @@ def check_MACHO_control_flow(binary) -> bool: ''' content = binary.get_content_from_virtual_address(binary.entrypoint, 4, lief.Binary.VA_TYPES.AUTO) - if content.tolist() == [243, 15, 30, 250]: # endbr64 - return True - return False - -def check_MACHO_branch_protection(binary) -> bool: - ''' - Check for branch protection instrumentation - ''' - content = binary.get_content_from_virtual_address(binary.entrypoint, 4, lief.Binary.VA_TYPES.AUTO) - - if content.tolist() == [95, 36, 3, 213]: # bti + if content == [243, 15, 30, 250]: # endbr64 return True return False @@ -221,13 +208,12 @@ def check_MACHO_branch_protection(binary) -> bool: ('NX', check_NX), ('RELOC_SECTION', check_PE_RELOC_SECTION), ('CONTROL_FLOW', check_PE_control_flow), - ('Canary', check_PE_Canary), ] BASE_MACHO = [ ('NOUNDEFS', check_MACHO_NOUNDEFS), + ('LAZY_BINDINGS', check_MACHO_LAZY_BINDINGS), ('Canary', check_MACHO_Canary), - ('FIXUP_CHAINS', check_MACHO_FIXUP_CHAINS), ] CHECKS = { @@ -245,7 +231,7 @@ def check_MACHO_branch_protection(binary) -> bool: lief.ARCHITECTURES.X86: BASE_MACHO + [('PIE', check_PIE), ('NX', check_NX), ('CONTROL_FLOW', check_MACHO_control_flow)], - lief.ARCHITECTURES.ARM64: BASE_MACHO + [('BRANCH_PROTECTION', check_MACHO_branch_protection)], + lief.ARCHITECTURES.ARM64: BASE_MACHO, } } @@ -271,7 +257,7 @@ def check_MACHO_branch_protection(binary) -> bool: retval = 1 continue - failed: list[str] = [] + failed: List[str] = [] for (name, func) in CHECKS[etype][arch]: if not func(binary): failed.append(name) diff --git a/contrib/devtools/symbol-check.py b/contrib/devtools/symbol-check.py index 601516c980..128fa2590b 100755 --- a/contrib/devtools/symbol-check.py +++ b/contrib/devtools/symbol-check.py @@ -1,3 +1,4 @@ + #!/usr/bin/env python3 # Copyright (c) 2014 Wladimir J. van der Laan # Distributed under the MIT software license, see the accompanying @@ -11,48 +12,60 @@ find ../path/to/binaries -type f -executable | xargs python3 contrib/devtools/symbol-check.py ''' import sys +from typing import List, Dict + +import lief #type:ignore -import lief +# temporary constant, to be replaced with lief.ELF.ARCH.RISCV +# https://github.com/lief-project/LIEF/pull/562 +LIEF_ELF_ARCH_RISCV = lief.ELF.ARCH(243) -# Debian 10 (Buster) EOL: 2024. https://wiki.debian.org/LTS +# Debian 9 (Stretch) EOL: 2022. https://wiki.debian.org/DebianReleases#Production_Releases # -# - libgcc version 8.3.0 (https://packages.debian.org/search?suite=buster&arch=any&searchon=names&keywords=libgcc1) -# - libc version 2.28 (https://packages.debian.org/search?suite=buster&arch=any&searchon=names&keywords=libc6) +# - g++ version 6.3.0 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=g%2B%2B) +# - libc version 2.24 (https://packages.debian.org/search?suite=stretch&arch=any&searchon=names&keywords=libc6) # -# Ubuntu 18.04 (Bionic) EOL: 2028. https://wiki.ubuntu.com/ReleaseTeam +# Ubuntu 16.04 (Xenial) EOL: 2026. https://wiki.ubuntu.com/Releases # -# - libgcc version 8.4.0 (https://packages.ubuntu.com/bionic/libgcc1) -# - libc version 2.27 (https://packages.ubuntu.com/bionic/libc6) +# - g++ version 5.3.1 +# - libc version 2.23 # # CentOS Stream 8 EOL: 2024. https://wiki.centos.org/About/Product # -# - libgcc version 8.5.0 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) +# - g++ version 8.5.0 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) # - libc version 2.28 (http://mirror.centos.org/centos/8-stream/AppStream/x86_64/os/Packages/) # # See https://gcc.gnu.org/onlinedocs/libstdc++/manual/abi.html for more info. MAX_VERSIONS = { -'GCC': (4,3,0), +'GCC': (4,8,0), 'GLIBC': { - lief.ELF.ARCH.x86_64: (2,27), - lief.ELF.ARCH.ARM: (2,27), - lief.ELF.ARCH.AARCH64:(2,27), - lief.ELF.ARCH.PPC64: (2,27), - lief.ELF.ARCH.RISCV: (2,27), + lief.ELF.ARCH.i386: (2,18), + lief.ELF.ARCH.x86_64: (2,18), + lief.ELF.ARCH.ARM: (2,18), + lief.ELF.ARCH.AARCH64:(2,18), + lief.ELF.ARCH.PPC64: (2,18), + LIEF_ELF_ARCH_RISCV: (2,27), }, 'LIBATOMIC': (1,0), -'V': (0,5,0), # xkb (digibyte-qt only) +'V': (0,5,0), # xkb (bitcoin-qt only) } +# See here for a description of _IO_stdin_used: +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=634261#109 # Ignore symbols that are exported as part of every executable IGNORE_EXPORTS = { -'environ', '_environ', '__environ', '_fini', '_init', 'stdin', -'stdout', 'stderr', +'_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__', +'__bss_end__', '_fini', '_IO_stdin_used', 'stdin', 'stdout', 'stderr', +'environ', '_environ', '__environ', } # Expected linker-loader names can be found here: # https://sourceware.org/glibc/wiki/ABIList?action=recall&rev=16 -ELF_INTERPRETER_NAMES: dict[lief.ELF.ARCH, dict[lief.ENDIANNESS, str]] = { +ELF_INTERPRETER_NAMES: Dict[lief.ELF.ARCH, Dict[lief.ENDIANNESS, str]] = { + lief.ELF.ARCH.i386: { + lief.ENDIANNESS.LITTLE: "/lib/ld-linux.so.2", + }, lief.ELF.ARCH.x86_64: { lief.ENDIANNESS.LITTLE: "/lib64/ld-linux-x86-64.so.2", }, @@ -66,30 +79,11 @@ lief.ENDIANNESS.BIG: "/lib64/ld64.so.1", lief.ENDIANNESS.LITTLE: "/lib64/ld64.so.2", }, - lief.ELF.ARCH.RISCV: { + LIEF_ELF_ARCH_RISCV: { lief.ENDIANNESS.LITTLE: "/lib/ld-linux-riscv64-lp64d.so.1", }, } -ELF_ABIS: dict[lief.ELF.ARCH, dict[lief.ENDIANNESS, list[int]]] = { - lief.ELF.ARCH.x86_64: { - lief.ENDIANNESS.LITTLE: [3,2,0], - }, - lief.ELF.ARCH.ARM: { - lief.ENDIANNESS.LITTLE: [3,2,0], - }, - lief.ELF.ARCH.AARCH64: { - lief.ENDIANNESS.LITTLE: [3,7,0], - }, - lief.ELF.ARCH.PPC64: { - lief.ENDIANNESS.LITTLE: [3,10,0], - lief.ENDIANNESS.BIG: [3,2,0], - }, - lief.ELF.ARCH.RISCV: { - lief.ENDIANNESS.LITTLE: [4,15,0], - }, -} - # Allowed NEEDED libraries ELF_ALLOWED_LIBRARIES = { # digibyted and digibyte-qt @@ -97,6 +91,7 @@ 'libc.so.6', # C library 'libpthread.so.0', # threading 'libm.so.6', # math library +'librt.so.1', # real-time (clock) 'libatomic.so.1', 'ld-linux-x86-64.so.2', # 64-bit dynamic linker 'ld-linux.so.2', # 32-bit dynamic linker @@ -156,21 +151,21 @@ 'KERNEL32.dll', # win32 base APIs 'msvcrt.dll', # C standard library for MSVC 'SHELL32.dll', # shell API +'USER32.dll', # user interface 'WS2_32.dll', # sockets -# digibyte-qt only +# bitcoin-qt only 'dwmapi.dll', # desktop window manager 'GDI32.dll', # graphics device interface 'IMM32.dll', # input method editor -'NETAPI32.dll', # network management +'NETAPI32.dll', 'ole32.dll', # component object model 'OLEAUT32.dll', # OLE Automation API 'SHLWAPI.dll', # light weight shell API -'USER32.dll', # user interface -'USERENV.dll', # user management -'UxTheme.dll', # visual style +'USERENV.dll', +'UxTheme.dll', 'VERSION.dll', # version checking 'WINMM.dll', # WinMM audio API -'WTSAPI32.dll', # Remote Desktop +'WTSAPI32.dll', } def check_version(max_versions, version, arch) -> bool: @@ -206,7 +201,7 @@ def check_exported_symbols(binary) -> bool: if not symbol.exported: continue name = symbol.name - if binary.header.machine_type == lief.ELF.ARCH.RISCV or name in IGNORE_EXPORTS: + if binary.header.machine_type == LIEF_ELF_ARCH_RISCV or name in IGNORE_EXPORTS: continue print(f'{binary.name}: export of symbol {name} not allowed!') ok = False @@ -230,17 +225,12 @@ def check_MACHO_libraries(binary) -> bool: return ok def check_MACHO_min_os(binary) -> bool: - if binary.build_version.minos == [11,0,0]: + if binary.build_version.minos == [10,15,0]: return True return False def check_MACHO_sdk(binary) -> bool: - if binary.build_version.sdk == [14, 0, 0]: - return True - return False - -def check_MACHO_ld64(binary) -> bool: - if binary.build_version.tools[0].version == [711, 0, 0]: + if binary.build_version.sdk == [11, 0, 0]: return True return False @@ -264,25 +254,17 @@ def check_ELF_interpreter(binary) -> bool: return binary.concrete.interpreter == expected_interpreter -def check_ELF_ABI(binary) -> bool: - expected_abi = ELF_ABIS[binary.header.machine_type][binary.abstract.header.endianness] - note = binary.concrete.get(lief.ELF.NOTE_TYPES.ABI_TAG) - assert note.details.abi == lief.ELF.NOTE_ABIS.LINUX - return note.details.version == expected_abi - CHECKS = { lief.EXE_FORMATS.ELF: [ ('IMPORTED_SYMBOLS', check_imported_symbols), ('EXPORTED_SYMBOLS', check_exported_symbols), ('LIBRARY_DEPENDENCIES', check_ELF_libraries), ('INTERPRETER_NAME', check_ELF_interpreter), - ('ABI', check_ELF_ABI), ], lief.EXE_FORMATS.MACHO: [ ('DYNAMIC_LIBRARIES', check_MACHO_libraries), ('MIN_OS', check_MACHO_min_os), ('SDK', check_MACHO_sdk), - ('LD64', check_MACHO_ld64), ], lief.EXE_FORMATS.PE: [ ('DYNAMIC_LIBRARIES', check_PE_libraries), @@ -301,7 +283,7 @@ def check_ELF_ABI(binary) -> bool: retval = 1 continue - failed: list[str] = [] + failed: List[str] = [] for (name, func) in CHECKS[etype]: if not func(binary): failed.append(name) @@ -311,4 +293,4 @@ def check_ELF_ABI(binary) -> bool: except IOError: print(f'{filename}: cannot open') retval = 1 - sys.exit(retval) \ No newline at end of file + sys.exit(retval) diff --git a/contrib/devtools/test-security-check.py b/contrib/devtools/test-security-check.py index 7c4a80aabd..d8efa28ce3 100755 --- a/contrib/devtools/test-security-check.py +++ b/contrib/devtools/test-security-check.py @@ -1,13 +1,14 @@ #!/usr/bin/env python3 -# Copyright (c) 2015-2022 The Bitcoin Core developers +# Copyright (c) 2015-2021 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' Test script for security-check.py ''' -import lief +import lief #type:ignore import os import subprocess +from typing import List import unittest from utils import determine_wellknown_cmd @@ -27,18 +28,18 @@ def clean_files(source, executable): os.remove(source) os.remove(executable) -def call_security_check(cc: str, source: str, executable: str, options) -> tuple: +def call_security_check(cc, source, executable, options): # 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. - env_flags: list[str] = [] + env_flags: List[str] = [] for var in ['CFLAGS', 'CPPFLAGS', 'LDFLAGS']: env_flags += filter(None, os.environ.get(var, '').split(' ')) subprocess.run([*cc,source,'-o',executable] + env_flags + options, check=True) - p = subprocess.run([os.path.join(os.path.dirname(__file__), 'security-check.py'), executable], stdout=subprocess.PIPE, text=True) + p = subprocess.run(['./contrib/devtools/security-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) return (p.returncode, p.stdout.rstrip()) def get_arch(cc, source, executable): @@ -93,19 +94,19 @@ def test_PE(self): cc = determine_wellknown_cmd('CC', 'x86_64-w64-mingw32-gcc') write_testcode(source) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--no-nxcompat','-Wl,--disable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE','-fno-stack-protector']), - (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION CONTROL_FLOW Canary')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--disable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE','-fstack-protector-all', '-lssp']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--disable-nxcompat','-Wl,--disable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-no-pie','-fno-PIE']), + (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA NX RELOC_SECTION CONTROL_FLOW')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--disable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-no-pie','-fno-PIE']), (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA RELOC_SECTION CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-no-pie','-fno-PIE','-fstack-protector-all', '-lssp']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-no-pie','-fno-PIE']), (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--no-dynamicbase','-Wl,--no-high-entropy-va','-pie','-fPIE','-fstack-protector-all', '-lssp']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--disable-dynamicbase','-Wl,--disable-high-entropy-va','-pie','-fPIE']), (1, executable+': failed PIE DYNAMIC_BASE HIGH_ENTROPY_VA CONTROL_FLOW')) # -pie -fPIE does nothing unless --dynamicbase is also supplied - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--no-high-entropy-va','-pie','-fPIE','-fstack-protector-all', '-lssp']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--disable-high-entropy-va','-pie','-fPIE']), (1, executable+': failed HIGH_ENTROPY_VA CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-fPIE','-fstack-protector-all', '-lssp']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,--nxcompat','-Wl,--enable-reloc-section','-Wl,--dynamicbase','-Wl,--high-entropy-va','-pie','-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','-fstack-protector-all', '-lssp']), + 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, '')) clean_files(source, executable) @@ -118,31 +119,29 @@ def test_MACHO(self): arch = get_arch(cc, source, executable) if arch == lief.ARCHITECTURES.X86: - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fno-stack-protector', '-Wl,-no_fixup_chains']), - (1, executable+': failed NOUNDEFS Canary FIXUP_CHAINS PIE NX CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fno-stack-protector', '-Wl,-fixup_chains']), - (1, executable+': failed NOUNDEFS Canary PIE NX CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fstack-protector-all', '-Wl,-fixup_chains']), - (1, executable+': failed NOUNDEFS PIE NX CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-fstack-protector-all', '-Wl,-fixup_chains']), - (1, executable+': failed NOUNDEFS PIE CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-fstack-protector-all', '-Wl,-fixup_chains']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fno-stack-protector']), + (1, executable+': failed NOUNDEFS LAZY_BINDINGS Canary PIE NX CONTROL_FLOW')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-Wl,-allow_stack_execute','-fstack-protector-all']), + (1, executable+': failed NOUNDEFS LAZY_BINDINGS PIE NX CONTROL_FLOW')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-flat_namespace','-fstack-protector-all']), + (1, executable+': failed NOUNDEFS LAZY_BINDINGS PIE CONTROL_FLOW')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-fstack-protector-all']), + (1, executable+': failed LAZY_BINDINGS PIE CONTROL_FLOW')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-bind_at_load','-fstack-protector-all']), (1, executable+': failed PIE CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-fstack-protector-all', '-Wl,-fixup_chains']), - (1, executable+': failed PIE CONTROL_FLOW')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-fstack-protector-all', '-fcf-protection=full', '-Wl,-fixup_chains']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-no_pie','-Wl,-bind_at_load','-fstack-protector-all', '-fcf-protection=full']), (1, executable+': failed PIE')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-pie','-fstack-protector-all', '-fcf-protection=full', '-Wl,-fixup_chains']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-pie','-Wl,-bind_at_load','-fstack-protector-all', '-fcf-protection=full']), (0, '')) else: # arm64 darwin doesn't support non-PIE binaries, control flow or executable stacks - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-flat_namespace','-fno-stack-protector', '-Wl,-no_fixup_chains']), - (1, executable+': failed NOUNDEFS Canary FIXUP_CHAINS BRANCH_PROTECTION')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-flat_namespace','-fno-stack-protector', '-Wl,-fixup_chains', '-mbranch-protection=bti']), - (1, executable+': failed NOUNDEFS Canary')) - self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-flat_namespace','-fstack-protector-all', '-Wl,-fixup_chains', '-mbranch-protection=bti']), - (1, executable+': failed NOUNDEFS')) - self.assertEqual(call_security_check(cc, source, executable, ['-fstack-protector-all', '-Wl,-fixup_chains', '-mbranch-protection=bti']), + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-flat_namespace','-fno-stack-protector']), + (1, executable+': failed NOUNDEFS LAZY_BINDINGS Canary')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-flat_namespace','-fstack-protector-all']), + (1, executable+': failed NOUNDEFS LAZY_BINDINGS')) + self.assertEqual(call_security_check(cc, source, executable, ['-fstack-protector-all']), + (1, executable+': failed LAZY_BINDINGS')) + self.assertEqual(call_security_check(cc, source, executable, ['-Wl,-bind_at_load','-fstack-protector-all']), (0, '')) diff --git a/contrib/devtools/test-symbol-check.py b/contrib/devtools/test-symbol-check.py index a49137228c..6d02ac523d 100755 --- a/contrib/devtools/test-symbol-check.py +++ b/contrib/devtools/test-symbol-check.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# Copyright (c) 2020-2022 The Bitcoin Core developers +# Copyright (c) 2020-2021 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. ''' @@ -7,28 +7,29 @@ ''' import os import subprocess +from typing import List import unittest from utils import determine_wellknown_cmd -def call_symbol_check(cc: list[str], source, executable, options): +def call_symbol_check(cc: List[str], source, executable, options): # 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. - env_flags: list[str] = [] + env_flags: List[str] = [] for var in ['CFLAGS', 'CPPFLAGS', 'LDFLAGS']: env_flags += filter(None, os.environ.get(var, '').split(' ')) subprocess.run([*cc,source,'-o',executable] + env_flags + options, check=True) - p = subprocess.run([os.path.join(os.path.dirname(__file__), 'symbol-check.py'), executable], stdout=subprocess.PIPE, text=True) + p = subprocess.run(['./contrib/devtools/symbol-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True) os.remove(source) os.remove(executable) return (p.returncode, p.stdout.rstrip()) -def get_machine(cc: list[str]): - p = subprocess.run([*cc,'-dumpmachine'], stdout=subprocess.PIPE, text=True) +def get_machine(cc: List[str]): + p = subprocess.run([*cc,'-dumpmachine'], stdout=subprocess.PIPE, universal_newlines=True) return p.stdout.rstrip() class TestSymbolChecks(unittest.TestCase): @@ -37,6 +38,31 @@ def test_ELF(self): executable = 'test1' cc = determine_wellknown_cmd('CC', 'gcc') + # there's no way to do this test for RISC-V at the moment; we build for + # RISC-V in a glibc 2.27 envinonment and we allow all symbols from 2.27. + if 'riscv' in get_machine(cc): + self.skipTest("test not available for RISC-V") + + # nextup was introduced in GLIBC 2.24, so is newer than our supported + # glibc (2.18), and available in our release build environment (2.24). + with open(source, 'w', encoding="utf8") as f: + f.write(''' + #define _GNU_SOURCE + #include + + double nextup(double x); + + int main() + { + nextup(3.14); + return 0; + } + ''') + + self.assertEqual(call_symbol_check(cc, source, executable, ['-lm']), + (1, executable + ': symbol nextup from unsupported version GLIBC_2.24(3)\n' + + executable + ': failed IMPORTED_SYMBOLS')) + # -lutil is part of the libc6 package so a safe bet that it's installed # it's also out of context enough that it's unlikely to ever become a real dependency source = 'test2.c' @@ -120,7 +146,7 @@ def test_MACHO(self): } ''') - self.assertEqual(call_symbol_check(cc, source, executable, ['-Wl,-platform_version','-Wl,macos', '-Wl,11.0', '-Wl,11.4']), + self.assertEqual(call_symbol_check(cc, source, executable, ['-Wl,-platform_version','-Wl,macos', '-Wl,10.15', '-Wl,11.4']), (1, f'{executable}: failed SDK')) def test_PE(self): @@ -161,7 +187,7 @@ def test_PE(self): executable = 'test3.exe' with open(source, 'w', encoding="utf8") as f: f.write(''' - #include + #include int main() {