mirror of
https://github.com/dashpay/dash.git
synced 2024-12-24 19:42:46 +01:00
merge bitcoin#22392: use LIEF for ELF security & symbol checks
This commit is contained in:
parent
d28ba33136
commit
110dbf82e8
15
Makefile.am
15
Makefile.am
@ -57,8 +57,7 @@ DIST_SHARE = \
|
||||
|
||||
BIN_CHECKS=$(top_srcdir)/contrib/devtools/symbol-check.py \
|
||||
$(top_srcdir)/contrib/devtools/security-check.py \
|
||||
$(top_srcdir)/contrib/devtools/utils.py \
|
||||
$(top_srcdir)/contrib/devtools/pixie.py
|
||||
$(top_srcdir)/contrib/devtools/utils.py
|
||||
|
||||
WINDOWS_PACKAGING = $(top_srcdir)/share/pixmaps/dash.ico \
|
||||
$(top_srcdir)/share/pixmaps/nsis-header.bmp \
|
||||
@ -343,14 +342,14 @@ clean-local: clean-docs
|
||||
|
||||
test-security-check:
|
||||
if TARGET_DARWIN
|
||||
$(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_MACHO
|
||||
$(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_MACHO
|
||||
$(AM_V_at) CC='$(CC)' CFLAGS='$(CFLAGS)' CPPFLAGS='$(CPPFLAGS)' LDFLAGS='$(LDFLAGS)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_MACHO
|
||||
$(AM_V_at) CC='$(CC)' CFLAGS='$(CFLAGS)' CPPFLAGS='$(CPPFLAGS)' LDFLAGS='$(LDFLAGS)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_MACHO
|
||||
endif
|
||||
if TARGET_WINDOWS
|
||||
$(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_PE
|
||||
$(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_PE
|
||||
$(AM_V_at) CC='$(CC)' CFLAGS='$(CFLAGS)' CPPFLAGS='$(CPPFLAGS)' LDFLAGS='$(LDFLAGS)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_PE
|
||||
$(AM_V_at) CC='$(CC)' CFLAGS='$(CFLAGS)' CPPFLAGS='$(CPPFLAGS)' LDFLAGS='$(LDFLAGS)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_PE
|
||||
endif
|
||||
if TARGET_LINUX
|
||||
$(AM_V_at) CC='$(CC)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_ELF
|
||||
$(AM_V_at) CC='$(CC)' CPPFILT='$(CPPFILT)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_ELF
|
||||
$(AM_V_at) CC='$(CC)' CFLAGS='$(CFLAGS)' CPPFLAGS='$(CPPFLAGS)' LDFLAGS='$(LDFLAGS)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-security-check.py TestSecurityChecks.test_ELF
|
||||
$(AM_V_at) CC='$(CC)' CFLAGS='$(CFLAGS)' CPPFLAGS='$(CPPFLAGS)' LDFLAGS='$(LDFLAGS)' $(PYTHON) $(top_srcdir)/contrib/devtools/test-symbol-check.py TestSymbolChecks.test_ELF
|
||||
endif
|
||||
|
@ -106,7 +106,6 @@ AC_PATH_PROG([GIT], [git])
|
||||
AC_PATH_PROG(CCACHE,ccache)
|
||||
AC_PATH_PROG(XGETTEXT,xgettext)
|
||||
AC_PATH_PROG(HEXDUMP,hexdump)
|
||||
AC_PATH_TOOL(CPPFILT, c++filt)
|
||||
AC_PATH_TOOL(OBJCOPY, objcopy)
|
||||
AC_PATH_TOOL(DSYMUTIL, dsymutil)
|
||||
AC_PATH_PROG(DOXYGEN, doxygen)
|
||||
|
@ -1,323 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) 2020 Wladimir J. van der Laan
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
||||
'''
|
||||
Compact, self-contained ELF implementation for bitcoin-core security checks.
|
||||
'''
|
||||
import struct
|
||||
import types
|
||||
from typing import Dict, List, Optional, Union, Tuple
|
||||
|
||||
# you can find all these values in elf.h
|
||||
EI_NIDENT = 16
|
||||
|
||||
# Byte indices in e_ident
|
||||
EI_CLASS = 4 # ELFCLASSxx
|
||||
EI_DATA = 5 # ELFDATAxxxx
|
||||
|
||||
ELFCLASS32 = 1 # 32-bit
|
||||
ELFCLASS64 = 2 # 64-bit
|
||||
|
||||
ELFDATA2LSB = 1 # little endian
|
||||
ELFDATA2MSB = 2 # big endian
|
||||
|
||||
# relevant values for e_machine
|
||||
EM_386 = 3
|
||||
EM_PPC64 = 21
|
||||
EM_ARM = 40
|
||||
EM_AARCH64 = 183
|
||||
EM_X86_64 = 62
|
||||
EM_RISCV = 243
|
||||
|
||||
# relevant values for e_type
|
||||
ET_DYN = 3
|
||||
|
||||
# relevant values for sh_type
|
||||
SHT_PROGBITS = 1
|
||||
SHT_STRTAB = 3
|
||||
SHT_DYNAMIC = 6
|
||||
SHT_DYNSYM = 11
|
||||
SHT_GNU_verneed = 0x6ffffffe
|
||||
SHT_GNU_versym = 0x6fffffff
|
||||
|
||||
# relevant values for p_type
|
||||
PT_LOAD = 1
|
||||
PT_GNU_STACK = 0x6474e551
|
||||
PT_GNU_RELRO = 0x6474e552
|
||||
|
||||
# relevant values for p_flags
|
||||
PF_X = (1 << 0)
|
||||
PF_W = (1 << 1)
|
||||
PF_R = (1 << 2)
|
||||
|
||||
# relevant values for d_tag
|
||||
DT_NEEDED = 1
|
||||
DT_FLAGS = 30
|
||||
|
||||
# relevant values of `d_un.d_val' in the DT_FLAGS entry
|
||||
DF_BIND_NOW = 0x00000008
|
||||
|
||||
# relevant d_tags with string payload
|
||||
STRING_TAGS = {DT_NEEDED}
|
||||
|
||||
# rrlevant values for ST_BIND subfield of st_info (symbol binding)
|
||||
STB_LOCAL = 0
|
||||
|
||||
class ELFRecord(types.SimpleNamespace):
|
||||
'''Unified parsing for ELF records.'''
|
||||
def __init__(self, data: bytes, offset: int, eh: 'ELFHeader', total_size: Optional[int]) -> None:
|
||||
hdr_struct = self.STRUCT[eh.ei_class][0][eh.ei_data]
|
||||
if total_size is not None and hdr_struct.size > total_size:
|
||||
raise ValueError(f'{self.__class__.__name__} header size too small ({total_size} < {hdr_struct.size})')
|
||||
for field, value in zip(self.STRUCT[eh.ei_class][1], hdr_struct.unpack(data[offset:offset + hdr_struct.size])):
|
||||
setattr(self, field, value)
|
||||
|
||||
def BiStruct(chars: str) -> Dict[int, struct.Struct]:
|
||||
'''Compile a struct parser for both endians.'''
|
||||
return {
|
||||
ELFDATA2LSB: struct.Struct('<' + chars),
|
||||
ELFDATA2MSB: struct.Struct('>' + chars),
|
||||
}
|
||||
|
||||
class ELFHeader(ELFRecord):
|
||||
FIELDS = ['e_type', 'e_machine', 'e_version', 'e_entry', 'e_phoff', 'e_shoff', 'e_flags', 'e_ehsize', 'e_phentsize', 'e_phnum', 'e_shentsize', 'e_shnum', 'e_shstrndx']
|
||||
STRUCT = {
|
||||
ELFCLASS32: (BiStruct('HHIIIIIHHHHHH'), FIELDS),
|
||||
ELFCLASS64: (BiStruct('HHIQQQIHHHHHH'), FIELDS),
|
||||
}
|
||||
|
||||
def __init__(self, data: bytes, offset: int) -> None:
|
||||
self.e_ident = data[offset:offset + EI_NIDENT]
|
||||
if self.e_ident[0:4] != b'\x7fELF':
|
||||
raise ValueError('invalid ELF magic')
|
||||
self.ei_class = self.e_ident[EI_CLASS]
|
||||
self.ei_data = self.e_ident[EI_DATA]
|
||||
|
||||
super().__init__(data, offset + EI_NIDENT, self, None)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Header(e_ident={self.e_ident!r}, e_type={self.e_type}, e_machine={self.e_machine}, e_version={self.e_version}, e_entry={self.e_entry}, e_phoff={self.e_phoff}, e_shoff={self.e_shoff}, e_flags={self.e_flags}, e_ehsize={self.e_ehsize}, e_phentsize={self.e_phentsize}, e_phnum={self.e_phnum}, e_shentsize={self.e_shentsize}, e_shnum={self.e_shnum}, e_shstrndx={self.e_shstrndx})'
|
||||
|
||||
class Section(ELFRecord):
|
||||
name: Optional[bytes] = None
|
||||
FIELDS = ['sh_name', 'sh_type', 'sh_flags', 'sh_addr', 'sh_offset', 'sh_size', 'sh_link', 'sh_info', 'sh_addralign', 'sh_entsize']
|
||||
STRUCT = {
|
||||
ELFCLASS32: (BiStruct('IIIIIIIIII'), FIELDS),
|
||||
ELFCLASS64: (BiStruct('IIQQQQIIQQ'), FIELDS),
|
||||
}
|
||||
|
||||
def __init__(self, data: bytes, offset: int, eh: ELFHeader) -> None:
|
||||
super().__init__(data, offset, eh, eh.e_shentsize)
|
||||
self._data = data
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Section(sh_name={self.sh_name}({self.name!r}), sh_type=0x{self.sh_type:x}, sh_flags={self.sh_flags}, sh_addr=0x{self.sh_addr:x}, sh_offset=0x{self.sh_offset:x}, sh_size={self.sh_size}, sh_link={self.sh_link}, sh_info={self.sh_info}, sh_addralign={self.sh_addralign}, sh_entsize={self.sh_entsize})'
|
||||
|
||||
def contents(self) -> bytes:
|
||||
'''Return section contents.'''
|
||||
return self._data[self.sh_offset:self.sh_offset + self.sh_size]
|
||||
|
||||
class ProgramHeader(ELFRecord):
|
||||
STRUCT = {
|
||||
# different ELF classes have the same fields, but in a different order to optimize space versus alignment
|
||||
ELFCLASS32: (BiStruct('IIIIIIII'), ['p_type', 'p_offset', 'p_vaddr', 'p_paddr', 'p_filesz', 'p_memsz', 'p_flags', 'p_align']),
|
||||
ELFCLASS64: (BiStruct('IIQQQQQQ'), ['p_type', 'p_flags', 'p_offset', 'p_vaddr', 'p_paddr', 'p_filesz', 'p_memsz', 'p_align']),
|
||||
}
|
||||
|
||||
def __init__(self, data: bytes, offset: int, eh: ELFHeader) -> None:
|
||||
super().__init__(data, offset, eh, eh.e_phentsize)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'ProgramHeader(p_type={self.p_type}, p_offset={self.p_offset}, p_vaddr={self.p_vaddr}, p_paddr={self.p_paddr}, p_filesz={self.p_filesz}, p_memsz={self.p_memsz}, p_flags={self.p_flags}, p_align={self.p_align})'
|
||||
|
||||
class Symbol(ELFRecord):
|
||||
STRUCT = {
|
||||
# different ELF classes have the same fields, but in a different order to optimize space versus alignment
|
||||
ELFCLASS32: (BiStruct('IIIBBH'), ['st_name', 'st_value', 'st_size', 'st_info', 'st_other', 'st_shndx']),
|
||||
ELFCLASS64: (BiStruct('IBBHQQ'), ['st_name', 'st_info', 'st_other', 'st_shndx', 'st_value', 'st_size']),
|
||||
}
|
||||
|
||||
def __init__(self, data: bytes, offset: int, eh: ELFHeader, symtab: Section, strings: bytes, version: Optional[bytes]) -> None:
|
||||
super().__init__(data, offset, eh, symtab.sh_entsize)
|
||||
self.name = _lookup_string(strings, self.st_name)
|
||||
self.version = version
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Symbol(st_name={self.st_name}({self.name!r}), st_value={self.st_value}, st_size={self.st_size}, st_info={self.st_info}, st_other={self.st_other}, st_shndx={self.st_shndx}, version={self.version!r})'
|
||||
|
||||
@property
|
||||
def is_import(self) -> bool:
|
||||
'''Returns whether the symbol is an imported symbol.'''
|
||||
return self.st_bind != STB_LOCAL and self.st_shndx == 0
|
||||
|
||||
@property
|
||||
def is_export(self) -> bool:
|
||||
'''Returns whether the symbol is an exported symbol.'''
|
||||
return self.st_bind != STB_LOCAL and self.st_shndx != 0
|
||||
|
||||
@property
|
||||
def st_bind(self) -> int:
|
||||
'''Returns STB_*.'''
|
||||
return self.st_info >> 4
|
||||
|
||||
class Verneed(ELFRecord):
|
||||
DEF = (BiStruct('HHIII'), ['vn_version', 'vn_cnt', 'vn_file', 'vn_aux', 'vn_next'])
|
||||
STRUCT = { ELFCLASS32: DEF, ELFCLASS64: DEF }
|
||||
|
||||
def __init__(self, data: bytes, offset: int, eh: ELFHeader) -> None:
|
||||
super().__init__(data, offset, eh, None)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Verneed(vn_version={self.vn_version}, vn_cnt={self.vn_cnt}, vn_file={self.vn_file}, vn_aux={self.vn_aux}, vn_next={self.vn_next})'
|
||||
|
||||
class Vernaux(ELFRecord):
|
||||
DEF = (BiStruct('IHHII'), ['vna_hash', 'vna_flags', 'vna_other', 'vna_name', 'vna_next'])
|
||||
STRUCT = { ELFCLASS32: DEF, ELFCLASS64: DEF }
|
||||
|
||||
def __init__(self, data: bytes, offset: int, eh: ELFHeader, strings: bytes) -> None:
|
||||
super().__init__(data, offset, eh, None)
|
||||
self.name = _lookup_string(strings, self.vna_name)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'Veraux(vna_hash={self.vna_hash}, vna_flags={self.vna_flags}, vna_other={self.vna_other}, vna_name={self.vna_name}({self.name!r}), vna_next={self.vna_next})'
|
||||
|
||||
class DynTag(ELFRecord):
|
||||
STRUCT = {
|
||||
ELFCLASS32: (BiStruct('II'), ['d_tag', 'd_val']),
|
||||
ELFCLASS64: (BiStruct('QQ'), ['d_tag', 'd_val']),
|
||||
}
|
||||
|
||||
def __init__(self, data: bytes, offset: int, eh: ELFHeader, section: Section) -> None:
|
||||
super().__init__(data, offset, eh, section.sh_entsize)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'DynTag(d_tag={self.d_tag}, d_val={self.d_val})'
|
||||
|
||||
def _lookup_string(data: bytes, index: int) -> bytes:
|
||||
'''Look up string by offset in ELF string table.'''
|
||||
endx = data.find(b'\x00', index)
|
||||
assert endx != -1
|
||||
return data[index:endx]
|
||||
|
||||
VERSYM_S = BiStruct('H') # .gnu_version section has a single 16-bit integer per symbol in the linked section
|
||||
def _parse_symbol_table(section: Section, strings: bytes, eh: ELFHeader, versym: bytes, verneed: Dict[int, bytes]) -> List[Symbol]:
|
||||
'''Parse symbol table, return a list of symbols.'''
|
||||
data = section.contents()
|
||||
symbols = []
|
||||
versym_iter = (verneed.get(v[0]) for v in VERSYM_S[eh.ei_data].iter_unpack(versym))
|
||||
for ofs, version in zip(range(0, len(data), section.sh_entsize), versym_iter):
|
||||
symbols.append(Symbol(data, ofs, eh, section, strings, version))
|
||||
return symbols
|
||||
|
||||
def _parse_verneed(section: Section, strings: bytes, eh: ELFHeader) -> Dict[int, bytes]:
|
||||
'''Parse .gnu.version_r section, return a dictionary of {versym: 'GLIBC_...'}.'''
|
||||
data = section.contents()
|
||||
ofs = 0
|
||||
result = {}
|
||||
while True:
|
||||
verneed = Verneed(data, ofs, eh)
|
||||
aofs = ofs + verneed.vn_aux
|
||||
while True:
|
||||
vernaux = Vernaux(data, aofs, eh, strings)
|
||||
result[vernaux.vna_other] = vernaux.name
|
||||
if not vernaux.vna_next:
|
||||
break
|
||||
aofs += vernaux.vna_next
|
||||
|
||||
if not verneed.vn_next:
|
||||
break
|
||||
ofs += verneed.vn_next
|
||||
|
||||
return result
|
||||
|
||||
def _parse_dyn_tags(section: Section, strings: bytes, eh: ELFHeader) -> List[Tuple[int, Union[bytes, int]]]:
|
||||
'''Parse dynamic tags. Return array of tuples.'''
|
||||
data = section.contents()
|
||||
ofs = 0
|
||||
result = []
|
||||
for ofs in range(0, len(data), section.sh_entsize):
|
||||
tag = DynTag(data, ofs, eh, section)
|
||||
val = _lookup_string(strings, tag.d_val) if tag.d_tag in STRING_TAGS else tag.d_val
|
||||
result.append((tag.d_tag, val))
|
||||
|
||||
return result
|
||||
|
||||
class ELFFile:
|
||||
sections: List[Section]
|
||||
program_headers: List[ProgramHeader]
|
||||
dyn_symbols: List[Symbol]
|
||||
dyn_tags: List[Tuple[int, Union[bytes, int]]]
|
||||
|
||||
def __init__(self, data: bytes) -> None:
|
||||
self.data = data
|
||||
self.hdr = ELFHeader(self.data, 0)
|
||||
self._load_sections()
|
||||
self._load_program_headers()
|
||||
self._load_dyn_symbols()
|
||||
self._load_dyn_tags()
|
||||
self._section_to_segment_mapping()
|
||||
|
||||
def _load_sections(self) -> None:
|
||||
self.sections = []
|
||||
for idx in range(self.hdr.e_shnum):
|
||||
offset = self.hdr.e_shoff + idx * self.hdr.e_shentsize
|
||||
self.sections.append(Section(self.data, offset, self.hdr))
|
||||
|
||||
shstr = self.sections[self.hdr.e_shstrndx].contents()
|
||||
for section in self.sections:
|
||||
section.name = _lookup_string(shstr, section.sh_name)
|
||||
|
||||
def _load_program_headers(self) -> None:
|
||||
self.program_headers = []
|
||||
for idx in range(self.hdr.e_phnum):
|
||||
offset = self.hdr.e_phoff + idx * self.hdr.e_phentsize
|
||||
self.program_headers.append(ProgramHeader(self.data, offset, self.hdr))
|
||||
|
||||
def _load_dyn_symbols(self) -> None:
|
||||
# first, load 'verneed' section
|
||||
verneed = None
|
||||
for section in self.sections:
|
||||
if section.sh_type == SHT_GNU_verneed:
|
||||
strtab = self.sections[section.sh_link].contents() # associated string table
|
||||
assert verneed is None # only one section of this kind please
|
||||
verneed = _parse_verneed(section, strtab, self.hdr)
|
||||
assert verneed is not None
|
||||
|
||||
# then, correlate GNU versym sections with dynamic symbol sections
|
||||
versym = {}
|
||||
for section in self.sections:
|
||||
if section.sh_type == SHT_GNU_versym:
|
||||
versym[section.sh_link] = section
|
||||
|
||||
# finally, load dynsym sections
|
||||
self.dyn_symbols = []
|
||||
for idx, section in enumerate(self.sections):
|
||||
if section.sh_type == SHT_DYNSYM: # find dynamic symbol tables
|
||||
strtab_data = self.sections[section.sh_link].contents() # associated string table
|
||||
versym_data = versym[idx].contents() # associated symbol version table
|
||||
self.dyn_symbols += _parse_symbol_table(section, strtab_data, self.hdr, versym_data, verneed)
|
||||
|
||||
def _load_dyn_tags(self) -> None:
|
||||
self.dyn_tags = []
|
||||
for idx, section in enumerate(self.sections):
|
||||
if section.sh_type == SHT_DYNAMIC: # find dynamic tag tables
|
||||
strtab = self.sections[section.sh_link].contents() # associated string table
|
||||
self.dyn_tags += _parse_dyn_tags(section, strtab, self.hdr)
|
||||
|
||||
def _section_to_segment_mapping(self) -> None:
|
||||
for ph in self.program_headers:
|
||||
ph.sections = []
|
||||
for section in self.sections:
|
||||
if ph.p_vaddr <= section.sh_addr < (ph.p_vaddr + ph.p_memsz):
|
||||
ph.sections.append(section)
|
||||
|
||||
def query_dyn_tags(self, tag_in: int) -> List[Union[int, bytes]]:
|
||||
'''Return the values of all dyn tags with the specified tag.'''
|
||||
return [val for (tag, val) in self.dyn_tags if tag == tag_in]
|
||||
|
||||
|
||||
def load(filename: str) -> ELFFile:
|
||||
with open(filename, 'rb') as f:
|
||||
data = f.read()
|
||||
return ELFFile(data)
|
@ -8,192 +8,155 @@ 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 sys
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
import lief
|
||||
import pixie
|
||||
|
||||
def check_ELF_PIE(executable) -> bool:
|
||||
'''
|
||||
Check for position independent executable (PIE), allowing for address space randomization.
|
||||
'''
|
||||
elf = pixie.load(executable)
|
||||
return elf.hdr.e_type == pixie.ET_DYN
|
||||
|
||||
def check_ELF_NX(executable) -> bool:
|
||||
'''
|
||||
Check that no sections are writable and executable (including the stack)
|
||||
'''
|
||||
elf = pixie.load(executable)
|
||||
have_wx = False
|
||||
have_gnu_stack = False
|
||||
for ph in elf.program_headers:
|
||||
if ph.p_type == pixie.PT_GNU_STACK:
|
||||
have_gnu_stack = True
|
||||
if (ph.p_flags & pixie.PF_W) != 0 and (ph.p_flags & pixie.PF_X) != 0: # section is both writable and executable
|
||||
have_wx = True
|
||||
return have_gnu_stack and not have_wx
|
||||
|
||||
def check_ELF_RELRO(executable) -> bool:
|
||||
def check_ELF_RELRO(binary) -> bool:
|
||||
'''
|
||||
Check for read-only relocations.
|
||||
GNU_RELRO program header must exist
|
||||
Dynamic section must have BIND_NOW flag
|
||||
'''
|
||||
elf = pixie.load(executable)
|
||||
have_gnu_relro = False
|
||||
for ph in elf.program_headers:
|
||||
for segment in binary.segments:
|
||||
# Note: not checking p_flags == PF_R: here as linkers set the permission differently
|
||||
# This does not affect security: the permission flags of the GNU_RELRO program
|
||||
# header are ignored, the PT_LOAD header determines the effective permissions.
|
||||
# However, the dynamic linker need to write to this area so these are RW.
|
||||
# Glibc itself takes care of mprotecting this area R after relocations are finished.
|
||||
# See also https://marc.info/?l=binutils&m=1498883354122353
|
||||
if ph.p_type == pixie.PT_GNU_RELRO:
|
||||
if segment.type == lief.ELF.SEGMENT_TYPES.GNU_RELRO:
|
||||
have_gnu_relro = True
|
||||
|
||||
have_bindnow = False
|
||||
for flags in elf.query_dyn_tags(pixie.DT_FLAGS):
|
||||
assert isinstance(flags, int)
|
||||
if flags & pixie.DF_BIND_NOW:
|
||||
try:
|
||||
flags = binary.get(lief.ELF.DYNAMIC_TAGS.FLAGS)
|
||||
if flags.value & lief.ELF.DYNAMIC_FLAGS.BIND_NOW:
|
||||
have_bindnow = True
|
||||
except:
|
||||
have_bindnow = False
|
||||
|
||||
return have_gnu_relro and have_bindnow
|
||||
|
||||
def check_ELF_Canary(executable) -> bool:
|
||||
def check_ELF_Canary(binary) -> bool:
|
||||
'''
|
||||
Check for use of stack canary
|
||||
'''
|
||||
elf = pixie.load(executable)
|
||||
ok = False
|
||||
for symbol in elf.dyn_symbols:
|
||||
if symbol.name == b'__stack_chk_fail':
|
||||
ok = True
|
||||
return ok
|
||||
return binary.has_symbol('__stack_chk_fail')
|
||||
|
||||
def check_ELF_separate_code(executable):
|
||||
def check_ELF_separate_code(binary):
|
||||
'''
|
||||
Check that sections are appropriately separated in virtual memory,
|
||||
based on their permissions. This checks for missing -Wl,-z,separate-code
|
||||
and potentially other problems.
|
||||
'''
|
||||
elf = pixie.load(executable)
|
||||
R = pixie.PF_R
|
||||
W = pixie.PF_W
|
||||
E = pixie.PF_X
|
||||
R = lief.ELF.SEGMENT_FLAGS.R
|
||||
W = lief.ELF.SEGMENT_FLAGS.W
|
||||
E = lief.ELF.SEGMENT_FLAGS.X
|
||||
EXPECTED_FLAGS = {
|
||||
# Read + execute
|
||||
b'.init': R | E,
|
||||
b'.plt': R | E,
|
||||
b'.plt.got': R | E,
|
||||
b'.plt.sec': R | E,
|
||||
b'.text': R | E,
|
||||
b'.fini': R | E,
|
||||
'.init': R | E,
|
||||
'.plt': R | E,
|
||||
'.plt.got': R | E,
|
||||
'.plt.sec': R | E,
|
||||
'.text': R | E,
|
||||
'.fini': R | E,
|
||||
# Read-only data
|
||||
b'.interp': R,
|
||||
b'.note.gnu.property': R,
|
||||
b'.note.gnu.build-id': R,
|
||||
b'.note.ABI-tag': R,
|
||||
b'.gnu.hash': R,
|
||||
b'.dynsym': R,
|
||||
b'.dynstr': R,
|
||||
b'.gnu.version': R,
|
||||
b'.gnu.version_r': R,
|
||||
b'.rela.dyn': R,
|
||||
b'.rela.plt': R,
|
||||
b'.rodata': R,
|
||||
b'.eh_frame_hdr': R,
|
||||
b'.eh_frame': R,
|
||||
b'.qtmetadata': R,
|
||||
b'.gcc_except_table': R,
|
||||
b'.stapsdt.base': R,
|
||||
'.interp': R,
|
||||
'.note.gnu.property': R,
|
||||
'.note.gnu.build-id': R,
|
||||
'.note.ABI-tag': R,
|
||||
'.gnu.hash': R,
|
||||
'.dynsym': R,
|
||||
'.dynstr': R,
|
||||
'.gnu.version': R,
|
||||
'.gnu.version_r': R,
|
||||
'.rela.dyn': R,
|
||||
'.rela.plt': R,
|
||||
'.rodata': R,
|
||||
'.eh_frame_hdr': R,
|
||||
'.eh_frame': R,
|
||||
'.qtmetadata': R,
|
||||
'.gcc_except_table': R,
|
||||
'.stapsdt.base': R,
|
||||
# Writable data
|
||||
b'.init_array': R | W,
|
||||
b'.fini_array': R | W,
|
||||
b'.dynamic': R | W,
|
||||
b'.got': R | W,
|
||||
b'.data': R | W,
|
||||
b'.bss': R | W,
|
||||
'.init_array': R | W,
|
||||
'.fini_array': R | W,
|
||||
'.dynamic': R | W,
|
||||
'.got': R | W,
|
||||
'.data': R | W,
|
||||
'.bss': R | W,
|
||||
}
|
||||
if elf.hdr.e_machine == pixie.EM_PPC64:
|
||||
if binary.header.machine_type == lief.ELF.ARCH.PPC64:
|
||||
# .plt is RW on ppc64 even with separate-code
|
||||
EXPECTED_FLAGS[b'.plt'] = R | W
|
||||
EXPECTED_FLAGS['.plt'] = R | W
|
||||
# For all LOAD program headers get mapping to the list of sections,
|
||||
# and for each section, remember the flags of the associated program header.
|
||||
flags_per_section = {}
|
||||
for ph in elf.program_headers:
|
||||
if ph.p_type == pixie.PT_LOAD:
|
||||
for section in ph.sections:
|
||||
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] = ph.p_flags
|
||||
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
|
||||
for (section, flags) in flags_per_section.items():
|
||||
if section in EXPECTED_FLAGS:
|
||||
if EXPECTED_FLAGS[section] != flags:
|
||||
if int(EXPECTED_FLAGS[section]) != int(flags):
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_PE_DYNAMIC_BASE(executable) -> bool:
|
||||
def check_PE_DYNAMIC_BASE(binary) -> bool:
|
||||
'''PIE: DllCharacteristics bit 0x40 signifies dynamicbase (ASLR)'''
|
||||
binary = lief.parse(executable)
|
||||
return lief.PE.DLL_CHARACTERISTICS.DYNAMIC_BASE in binary.optional_header.dll_characteristics_lists
|
||||
|
||||
# Must support high-entropy 64-bit address space layout randomization
|
||||
# in addition to DYNAMIC_BASE to have secure ASLR.
|
||||
def check_PE_HIGH_ENTROPY_VA(executable) -> bool:
|
||||
def check_PE_HIGH_ENTROPY_VA(binary) -> bool:
|
||||
'''PIE: DllCharacteristics bit 0x20 signifies high-entropy ASLR'''
|
||||
binary = lief.parse(executable)
|
||||
return lief.PE.DLL_CHARACTERISTICS.HIGH_ENTROPY_VA in binary.optional_header.dll_characteristics_lists
|
||||
|
||||
def check_PE_RELOC_SECTION(executable) -> bool:
|
||||
def check_PE_RELOC_SECTION(binary) -> bool:
|
||||
'''Check for a reloc section. This is required for functional ASLR.'''
|
||||
binary = lief.parse(executable)
|
||||
return binary.has_relocations
|
||||
|
||||
def check_MACHO_NOUNDEFS(executable) -> bool:
|
||||
def check_MACHO_NOUNDEFS(binary) -> bool:
|
||||
'''
|
||||
Check for no undefined references.
|
||||
'''
|
||||
binary = lief.parse(executable)
|
||||
return binary.header.has(lief.MachO.HEADER_FLAGS.NOUNDEFS)
|
||||
|
||||
def check_MACHO_LAZY_BINDINGS(executable) -> bool:
|
||||
def check_MACHO_LAZY_BINDINGS(binary) -> bool:
|
||||
'''
|
||||
Check for no lazy bindings.
|
||||
We don't use or check for MH_BINDATLOAD. See #18295.
|
||||
'''
|
||||
binary = lief.parse(executable)
|
||||
return binary.dyld_info.lazy_bind == (0,0)
|
||||
|
||||
def check_MACHO_Canary(executable) -> bool:
|
||||
def check_MACHO_Canary(binary) -> bool:
|
||||
'''
|
||||
Check for use of stack canary
|
||||
'''
|
||||
binary = lief.parse(executable)
|
||||
return binary.has_symbol('___stack_chk_fail')
|
||||
|
||||
def check_PIE(executable) -> bool:
|
||||
def check_PIE(binary) -> bool:
|
||||
'''
|
||||
Check for position independent executable (PIE),
|
||||
allowing for address space randomization.
|
||||
'''
|
||||
binary = lief.parse(executable)
|
||||
return binary.is_pie
|
||||
|
||||
def check_NX(executable) -> bool:
|
||||
def check_NX(binary) -> bool:
|
||||
'''
|
||||
Check for no stack execution
|
||||
'''
|
||||
binary = lief.parse(executable)
|
||||
return binary.has_nx
|
||||
|
||||
def check_control_flow(executable) -> bool:
|
||||
def check_control_flow(binary) -> 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
|
||||
@ -203,8 +166,8 @@ def check_control_flow(executable) -> bool:
|
||||
|
||||
CHECKS = {
|
||||
'ELF': [
|
||||
('PIE', check_ELF_PIE),
|
||||
('NX', check_ELF_NX),
|
||||
('PIE', check_PIE),
|
||||
('NX', check_NX),
|
||||
('RELRO', check_ELF_RELRO),
|
||||
('Canary', check_ELF_Canary),
|
||||
('separate_code', check_ELF_separate_code),
|
||||
@ -226,30 +189,20 @@ CHECKS = {
|
||||
]
|
||||
}
|
||||
|
||||
def identify_executable(executable) -> Optional[str]:
|
||||
with open(filename, 'rb') as f:
|
||||
magic = f.read(4)
|
||||
if magic.startswith(b'MZ'):
|
||||
return 'PE'
|
||||
elif magic.startswith(b'\x7fELF'):
|
||||
return 'ELF'
|
||||
elif magic.startswith(b'\xcf\xfa'):
|
||||
return 'MACHO'
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
retval: int = 0
|
||||
for filename in sys.argv[1:]:
|
||||
try:
|
||||
etype = identify_executable(filename)
|
||||
if etype is None:
|
||||
print(f'{filename}: unknown format')
|
||||
binary = lief.parse(filename)
|
||||
etype = binary.format.name
|
||||
if etype == lief.EXE_FORMATS.UNKNOWN:
|
||||
print(f'{filename}: unknown executable format')
|
||||
retval = 1
|
||||
continue
|
||||
|
||||
failed: List[str] = []
|
||||
for (name, func) in CHECKS[etype]:
|
||||
if not func(filename):
|
||||
if not func(binary):
|
||||
failed.append(name)
|
||||
if failed:
|
||||
print(f'{filename}: failed {" ".join(failed)}')
|
||||
|
@ -10,14 +10,13 @@ Example usage:
|
||||
|
||||
find ../path/to/binaries -type f -executable | xargs python3 contrib/devtools/symbol-check.py
|
||||
'''
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import lief
|
||||
import pixie
|
||||
|
||||
from utils import determine_wellknown_cmd
|
||||
# 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 9 (Stretch) EOL: 2022. https://wiki.debian.org/DebianReleases#Production_Releases
|
||||
#
|
||||
@ -45,12 +44,12 @@ from utils import determine_wellknown_cmd
|
||||
MAX_VERSIONS = {
|
||||
'GCC': (4,8,0),
|
||||
'GLIBC': {
|
||||
pixie.EM_386: (2,28),
|
||||
pixie.EM_X86_64: (2,18),
|
||||
pixie.EM_ARM: (2,28),
|
||||
pixie.EM_AARCH64:(2,18),
|
||||
pixie.EM_PPC64: (2,18),
|
||||
pixie.EM_RISCV: (2,27),
|
||||
lief.ELF.ARCH.i386: (2,28),
|
||||
lief.ELF.ARCH.x86_64: (2,18),
|
||||
lief.ELF.ARCH.ARM: (2,28),
|
||||
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 (bitcoin-qt only)
|
||||
@ -60,7 +59,8 @@ MAX_VERSIONS = {
|
||||
|
||||
# Ignore symbols that are exported as part of every executable
|
||||
IGNORE_EXPORTS = {
|
||||
'_edata', '_end', '__end__', '_init', '__bss_start', '__bss_start__', '_bss_end__', '__bss_end__', '_fini', '_IO_stdin_used', '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',
|
||||
# Used in stacktraces.cpp
|
||||
'__cxa_demangle'
|
||||
@ -140,31 +140,8 @@ PE_ALLOWED_LIBRARIES = {
|
||||
'WTSAPI32.dll',
|
||||
}
|
||||
|
||||
class CPPFilt(object):
|
||||
'''
|
||||
Demangle C++ symbol names.
|
||||
|
||||
Use a pipe to the 'c++filt' command.
|
||||
'''
|
||||
def __init__(self):
|
||||
self.proc = subprocess.Popen(determine_wellknown_cmd('CPPFILT', 'c++filt'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, universal_newlines=True)
|
||||
|
||||
def __call__(self, mangled):
|
||||
self.proc.stdin.write(mangled + '\n')
|
||||
self.proc.stdin.flush()
|
||||
return self.proc.stdout.readline().rstrip()
|
||||
|
||||
def close(self):
|
||||
self.proc.stdin.close()
|
||||
self.proc.stdout.close()
|
||||
self.proc.wait()
|
||||
|
||||
def check_version(max_versions, version, arch) -> bool:
|
||||
if '_' in version:
|
||||
(lib, _, ver) = version.rpartition('_')
|
||||
else:
|
||||
lib = version
|
||||
ver = '0'
|
||||
(lib, _, ver) = version.rpartition('_')
|
||||
ver = tuple([int(x) for x in ver.split('.')])
|
||||
if not lib in max_versions:
|
||||
return False
|
||||
@ -173,48 +150,45 @@ def check_version(max_versions, version, arch) -> bool:
|
||||
else:
|
||||
return ver <= max_versions[lib][arch]
|
||||
|
||||
def check_imported_symbols(filename) -> bool:
|
||||
elf = pixie.load(filename)
|
||||
cppfilt = CPPFilt()
|
||||
def check_imported_symbols(binary) -> bool:
|
||||
ok = True
|
||||
|
||||
for symbol in elf.dyn_symbols:
|
||||
if not symbol.is_import:
|
||||
for symbol in binary.imported_symbols:
|
||||
if not symbol.imported:
|
||||
continue
|
||||
sym = symbol.name.decode()
|
||||
version = symbol.version.decode() if symbol.version is not None else None
|
||||
if version and not check_version(MAX_VERSIONS, version, elf.hdr.e_machine):
|
||||
print('{}: symbol {} from unsupported version {}'.format(filename, cppfilt(sym), version))
|
||||
ok = False
|
||||
|
||||
version = symbol.symbol_version if symbol.has_version else None
|
||||
|
||||
if version:
|
||||
aux_version = version.symbol_version_auxiliary.name if version.has_auxiliary_version else None
|
||||
if aux_version and not check_version(MAX_VERSIONS, aux_version, binary.header.machine_type):
|
||||
print(f'{filename}: symbol {symbol.name} from unsupported version {version}')
|
||||
ok = False
|
||||
return ok
|
||||
|
||||
def check_exported_symbols(filename) -> bool:
|
||||
elf = pixie.load(filename)
|
||||
cppfilt = CPPFilt()
|
||||
def check_exported_symbols(binary) -> bool:
|
||||
ok = True
|
||||
for symbol in elf.dyn_symbols:
|
||||
if not symbol.is_export:
|
||||
|
||||
for symbol in binary.dynamic_symbols:
|
||||
if not symbol.exported:
|
||||
continue
|
||||
sym = symbol.name.decode()
|
||||
if elf.hdr.e_machine == pixie.EM_RISCV or sym in IGNORE_EXPORTS:
|
||||
name = symbol.name
|
||||
if binary.header.machine_type == LIEF_ELF_ARCH_RISCV or name in IGNORE_EXPORTS:
|
||||
continue
|
||||
print('{}: export of symbol {} not allowed'.format(filename, cppfilt(sym)))
|
||||
print(f'{binary.name}: export of symbol {name} not allowed!')
|
||||
ok = False
|
||||
return ok
|
||||
|
||||
def check_ELF_libraries(filename) -> bool:
|
||||
def check_ELF_libraries(binary) -> bool:
|
||||
ok = True
|
||||
elf = pixie.load(filename)
|
||||
for library_name in elf.query_dyn_tags(pixie.DT_NEEDED):
|
||||
assert(isinstance(library_name, bytes))
|
||||
if library_name.decode() not in ELF_ALLOWED_LIBRARIES:
|
||||
print('{}: NEEDED library {} is not allowed'.format(filename, library_name.decode()))
|
||||
for library in binary.libraries:
|
||||
if library not in ELF_ALLOWED_LIBRARIES:
|
||||
print(f'{filename}: {library} is not in ALLOWED_LIBRARIES!')
|
||||
ok = False
|
||||
return ok
|
||||
|
||||
def check_MACHO_libraries(filename) -> bool:
|
||||
def check_MACHO_libraries(binary) -> bool:
|
||||
ok = True
|
||||
binary = lief.parse(filename)
|
||||
for dylib in binary.libraries:
|
||||
split = dylib.name.split('/')
|
||||
if split[-1] not in MACHO_ALLOWED_LIBRARIES:
|
||||
@ -222,29 +196,25 @@ def check_MACHO_libraries(filename) -> bool:
|
||||
ok = False
|
||||
return ok
|
||||
|
||||
def check_MACHO_min_os(filename) -> bool:
|
||||
binary = lief.parse(filename)
|
||||
def check_MACHO_min_os(binary) -> bool:
|
||||
if binary.build_version.minos == [10,15,0]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_MACHO_sdk(filename) -> bool:
|
||||
binary = lief.parse(filename)
|
||||
def check_MACHO_sdk(binary) -> bool:
|
||||
if binary.build_version.sdk == [10, 15, 6]:
|
||||
return True
|
||||
return False
|
||||
|
||||
def check_PE_libraries(filename) -> bool:
|
||||
def check_PE_libraries(binary) -> bool:
|
||||
ok = True
|
||||
binary = lief.parse(filename)
|
||||
for dylib in binary.libraries:
|
||||
if dylib not in PE_ALLOWED_LIBRARIES:
|
||||
print(f'{dylib} is not in ALLOWED_LIBRARIES!')
|
||||
ok = False
|
||||
return ok
|
||||
|
||||
def check_PE_subsystem_version(filename) -> bool:
|
||||
binary = lief.parse(filename)
|
||||
def check_PE_subsystem_version(binary) -> bool:
|
||||
major: int = binary.optional_header.major_subsystem_version
|
||||
minor: int = binary.optional_header.minor_subsystem_version
|
||||
if major == 6 and minor == 1:
|
||||
@ -268,30 +238,20 @@ CHECKS = {
|
||||
]
|
||||
}
|
||||
|
||||
def identify_executable(executable) -> Optional[str]:
|
||||
with open(filename, 'rb') as f:
|
||||
magic = f.read(4)
|
||||
if magic.startswith(b'MZ'):
|
||||
return 'PE'
|
||||
elif magic.startswith(b'\x7fELF'):
|
||||
return 'ELF'
|
||||
elif magic.startswith(b'\xcf\xfa'):
|
||||
return 'MACHO'
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
retval = 0
|
||||
for filename in sys.argv[1:]:
|
||||
try:
|
||||
etype = identify_executable(filename)
|
||||
if etype is None:
|
||||
print(f'{filename}: unknown format')
|
||||
binary = lief.parse(filename)
|
||||
etype = binary.format.name
|
||||
if etype == lief.EXE_FORMATS.UNKNOWN:
|
||||
print(f'{filename}: unknown executable format')
|
||||
retval = 1
|
||||
continue
|
||||
|
||||
failed = []
|
||||
for (name, func) in CHECKS[etype]:
|
||||
if not func(filename):
|
||||
if not func(binary):
|
||||
failed.append(name)
|
||||
if failed:
|
||||
print(f'{filename}: failed {" ".join(failed)}')
|
||||
|
@ -7,6 +7,7 @@ Test script for security-check.py
|
||||
'''
|
||||
import os
|
||||
import subprocess
|
||||
from typing import List
|
||||
import unittest
|
||||
|
||||
from utils import determine_wellknown_cmd
|
||||
@ -27,7 +28,16 @@ def clean_files(source, executable):
|
||||
os.remove(executable)
|
||||
|
||||
def call_security_check(cc, source, executable, options):
|
||||
subprocess.run([*cc,source,'-o',executable] + options, check=True)
|
||||
# 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] = []
|
||||
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(['./contrib/devtools/security-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
return (p.returncode, p.stdout.rstrip())
|
||||
|
||||
|
@ -13,7 +13,16 @@ import unittest
|
||||
from utils import determine_wellknown_cmd
|
||||
|
||||
def call_symbol_check(cc: List[str], source, executable, options):
|
||||
subprocess.run([*cc,source,'-o',executable] + options, check=True)
|
||||
# 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] = []
|
||||
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(['./contrib/devtools/symbol-check.py',executable], stdout=subprocess.PIPE, universal_newlines=True)
|
||||
os.remove(source)
|
||||
os.remove(executable)
|
||||
@ -56,7 +65,7 @@ class TestSymbolChecks(unittest.TestCase):
|
||||
''')
|
||||
|
||||
self.assertEqual(call_symbol_check(cc, source, executable, ['-lm']),
|
||||
(1, executable + ': symbol nextup from unsupported version GLIBC_2.24\n' +
|
||||
(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
|
||||
@ -75,7 +84,7 @@ class TestSymbolChecks(unittest.TestCase):
|
||||
''')
|
||||
|
||||
self.assertEqual(call_symbol_check(cc, source, executable, ['-lutil']),
|
||||
(1, executable + ': NEEDED library libutil.so.1 is not allowed\n' +
|
||||
(1, executable + ': libutil.so.1 is not in ALLOWED_LIBRARIES!\n' +
|
||||
executable + ': failed LIBRARY_DEPENDENCIES'))
|
||||
|
||||
# finally, check a simple conforming binary
|
||||
|
@ -933,20 +933,8 @@ clean-local:
|
||||
$(AM_V_GEN) $(WINDRES) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(CPPFLAGS) -DWINDRES_PREPROC -i $< -o $@
|
||||
|
||||
check-symbols: $(bin_PROGRAMS)
|
||||
if TARGET_DARWIN
|
||||
@echo "Checking macOS dynamic libraries..."
|
||||
@echo "Running symbol and dynamic library checks..."
|
||||
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
|
||||
endif
|
||||
|
||||
if TARGET_WINDOWS
|
||||
@echo "Checking Windows dynamic libraries..."
|
||||
$(AM_V_at) $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
|
||||
endif
|
||||
|
||||
if TARGET_LINUX
|
||||
@echo "Checking glibc back compat..."
|
||||
$(AM_V_at) CPPFILT='$(CPPFILT)' $(PYTHON) $(top_srcdir)/contrib/devtools/symbol-check.py $(bin_PROGRAMS)
|
||||
endif
|
||||
|
||||
check-security: $(bin_PROGRAMS)
|
||||
if HARDEN
|
||||
|
Loading…
Reference in New Issue
Block a user