From 198abcbb94618730dae1b3f4393efaa49e0ec8c7 Mon Sep 17 00:00:00 2001 From: Florian Weimer Date: Mon, 11 Apr 2022 11:30:31 +0200 Subject: Default to --with-default-link=no (bug 25812) This is necessary to place the libio vtables into the RELRO segment. New tests elf/tst-relro-ldso and elf/tst-relro-libc are added to verify that this is what actually happens. The new tests fail on ia64 due to lack of (default) RELRO support inbutils, so they are XFAILed there. --- elf/Makefile | 33 ++++++++++++ elf/tst-relro-symbols.py | 137 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 elf/tst-relro-symbols.py (limited to 'elf') diff --git a/elf/Makefile b/elf/Makefile index 3d79f40..8ed6c3b 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -543,6 +543,39 @@ endif endif endif +tests-special += $(objpfx)tst-relro-ldso.out $(objpfx)tst-relro-libc.out +$(objpfx)tst-relro-ldso.out: tst-relro-symbols.py $(..)/scripts/glibcelf.py \ + $(objpfx)ld.so + $(PYTHON) tst-relro-symbols.py $(objpfx)ld.so \ + --required=_rtld_global_ro \ + > $@ 2>&1; $(evaluate-test) +# The optional symbols are present in libc only if the architecture has +# the GLIBC_2.0 symbol set in libc. +$(objpfx)tst-relro-libc.out: tst-relro-symbols.py $(..)/scripts/glibcelf.py \ + $(common-objpfx)libc.so + $(PYTHON) tst-relro-symbols.py $(common-objpfx)libc.so \ + --required=_IO_cookie_jumps \ + --required=_IO_file_jumps \ + --required=_IO_file_jumps_maybe_mmap \ + --required=_IO_file_jumps_mmap \ + --required=_IO_helper_jumps \ + --required=_IO_mem_jumps \ + --required=_IO_obstack_jumps \ + --required=_IO_proc_jumps \ + --required=_IO_str_chk_jumps \ + --required=_IO_str_jumps \ + --required=_IO_strn_jumps \ + --required=_IO_wfile_jumps \ + --required=_IO_wfile_jumps_maybe_mmap \ + --required=_IO_wfile_jumps_mmap \ + --required=_IO_wmem_jumps \ + --required=_IO_wstr_jumps \ + --required=_IO_wstrn_jumps \ + --optional=_IO_old_cookie_jumps \ + --optional=_IO_old_file_jumps \ + --optional=_IO_old_proc_jumps \ + > $@ 2>&1; $(evaluate-test) + ifeq ($(run-built-tests),yes) tests-special += $(objpfx)tst-valgrind-smoke.out endif diff --git a/elf/tst-relro-symbols.py b/elf/tst-relro-symbols.py new file mode 100644 index 0000000..368ea33 --- /dev/null +++ b/elf/tst-relro-symbols.py @@ -0,0 +1,137 @@ +#!/usr/bin/python3 +# Verify that certain symbols are covered by RELRO. +# Copyright (C) 2022 Free Software Foundation, Inc. +# This file is part of the GNU C Library. +# +# The GNU C Library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# The GNU C Library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with the GNU C Library; if not, see +# . + +"""Analyze a (shared) object to verify that certain symbols are +present and covered by the PT_GNU_RELRO segment. + +""" + +import argparse +import os.path +import sys + +# Make available glibc Python modules. +sys.path.append(os.path.join( + os.path.dirname(os.path.realpath(__file__)), os.path.pardir, 'scripts')) + +import glibcelf + +def find_relro(path: str, img: glibcelf.Image) -> (int, int): + """Discover the address range of the PT_GNU_RELRO segment.""" + for phdr in img.phdrs(): + if phdr.p_type == glibcelf.Pt.PT_GNU_RELRO: + # The computation is not entirely accurate because + # _dl_protect_relro in elf/dl-reloc.c rounds both the + # start end and downwards using the run-time page size. + return phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz + sys.stdout.write('{}: error: no PT_GNU_RELRO segment\n'.format(path)) + sys.exit(1) + +def check_in_relro(kind, relro_begin, relro_end, name, start, size, error): + """Check if a section or symbol falls within in the RELRO segment.""" + end = start + size - 1 + if not (relro_begin <= start < end < relro_end): + error( + '{} {!r} of size {} at 0x{:x} is not in RELRO range [0x{:x}, 0x{:x})'.format( + kind, name.decode('UTF-8'), start, size, + relro_begin, relro_end)) + +def get_parser(): + """Return an argument parser for this script.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('object', help='path to object file to check') + parser.add_argument('--required', metavar='NAME', default=(), + help='required symbol names', nargs='*') + parser.add_argument('--optional', metavar='NAME', default=(), + help='required symbol names', nargs='*') + return parser + +def main(argv): + """The main entry point.""" + parser = get_parser() + opts = parser.parse_args(argv) + img = glibcelf.Image.readfile(opts.object) + + required_symbols = frozenset([sym.encode('UTF-8') + for sym in opts.required]) + optional_symbols = frozenset([sym.encode('UTF-8') + for sym in opts.optional]) + check_symbols = required_symbols | optional_symbols + + # Tracks the symbols in check_symbols that have been found. + symbols_found = set() + + # Discover the extent of the RELRO segment. + relro_begin, relro_end = find_relro(opts.object, img) + symbol_table_found = False + + errors = False + def error(msg: str) -> None: + """Record an error condition and write a message to standard output.""" + nonlocal errors + errors = True + sys.stdout.write('{}: error: {}\n'.format(opts.object, msg)) + + # Iterate over section headers to find the symbol table. + for shdr in img.shdrs(): + if shdr.sh_type == glibcelf.Sht.SHT_SYMTAB: + symbol_table_found = True + for sym in img.syms(shdr): + if sym.st_name in check_symbols: + symbols_found.add(sym.st_name) + + # Validate symbol type, section, and size. + if sym.st_info.type != glibcelf.Stt.STT_OBJECT: + error('symbol {!r} has wrong type {}'.format( + sym.st_name.decode('UTF-8'), sym.st_info.type)) + if sym.st_shndx in glibcelf.Shn: + error('symbol {!r} has reserved section {}'.format( + sym.st_name.decode('UTF-8'), sym.st_shndx)) + continue + if sym.st_size == 0: + error('symbol {!r} has size zero'.format( + sym.st_name.decode('UTF-8'))) + continue + + check_in_relro('symbol', relro_begin, relro_end, + sym.st_name, sym.st_value, sym.st_size, + error) + continue # SHT_SYMTAB + if shdr.sh_name == b'.data.rel.ro' \ + or shdr.sh_name.startswith(b'.data.rel.ro.'): + check_in_relro('section', relro_begin, relro_end, + shdr.sh_name, shdr.sh_addr, shdr.sh_size, + error) + continue + + if required_symbols - symbols_found: + for sym in sorted(required_symbols - symbols_found): + error('symbol {!r} not found'.format(sym.decode('UTF-8'))) + + if errors: + sys.exit(1) + + if not symbol_table_found: + sys.stdout.write( + '{}: warning: no symbol table found (stripped object)\n'.format( + opts.object)) + sys.exit(77) + +if __name__ == '__main__': + main(sys.argv[1:]) -- cgit v1.1