1#!/usr/bin/python3 2# Verify that certain symbols are covered by RELRO. 3# Copyright (C) 2022 Free Software Foundation, Inc. 4# This file is part of the GNU C Library. 5# 6# The GNU C Library is free software; you can redistribute it and/or 7# modify it under the terms of the GNU Lesser General Public 8# License as published by the Free Software Foundation; either 9# version 2.1 of the License, or (at your option) any later version. 10# 11# The GNU C Library is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14# Lesser General Public License for more details. 15# 16# You should have received a copy of the GNU Lesser General Public 17# License along with the GNU C Library; if not, see 18# <https://www.gnu.org/licenses/>. 19 20"""Analyze a (shared) object to verify that certain symbols are 21present and covered by the PT_GNU_RELRO segment. 22 23""" 24 25import argparse 26import os.path 27import sys 28 29# Make available glibc Python modules. 30sys.path.append(os.path.join( 31 os.path.dirname(os.path.realpath(__file__)), os.path.pardir, 'scripts')) 32 33import glibcelf 34 35def find_relro(path: str, img: glibcelf.Image) -> (int, int): 36 """Discover the address range of the PT_GNU_RELRO segment.""" 37 for phdr in img.phdrs(): 38 if phdr.p_type == glibcelf.Pt.PT_GNU_RELRO: 39 # The computation is not entirely accurate because 40 # _dl_protect_relro in elf/dl-reloc.c rounds both the 41 # start end and downwards using the run-time page size. 42 return phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz 43 sys.stdout.write('{}: error: no PT_GNU_RELRO segment\n'.format(path)) 44 sys.exit(1) 45 46def check_in_relro(kind, relro_begin, relro_end, name, start, size, error): 47 """Check if a section or symbol falls within in the RELRO segment.""" 48 end = start + size - 1 49 if not (relro_begin <= start < end < relro_end): 50 error( 51 '{} {!r} of size {} at 0x{:x} is not in RELRO range [0x{:x}, 0x{:x})'.format( 52 kind, name.decode('UTF-8'), start, size, 53 relro_begin, relro_end)) 54 55def get_parser(): 56 """Return an argument parser for this script.""" 57 parser = argparse.ArgumentParser(description=__doc__) 58 parser.add_argument('object', help='path to object file to check') 59 parser.add_argument('--required', metavar='NAME', default=(), 60 help='required symbol names', nargs='*') 61 parser.add_argument('--optional', metavar='NAME', default=(), 62 help='required symbol names', nargs='*') 63 return parser 64 65def main(argv): 66 """The main entry point.""" 67 parser = get_parser() 68 opts = parser.parse_args(argv) 69 img = glibcelf.Image.readfile(opts.object) 70 71 required_symbols = frozenset([sym.encode('UTF-8') 72 for sym in opts.required]) 73 optional_symbols = frozenset([sym.encode('UTF-8') 74 for sym in opts.optional]) 75 check_symbols = required_symbols | optional_symbols 76 77 # Tracks the symbols in check_symbols that have been found. 78 symbols_found = set() 79 80 # Discover the extent of the RELRO segment. 81 relro_begin, relro_end = find_relro(opts.object, img) 82 symbol_table_found = False 83 84 errors = False 85 def error(msg: str) -> None: 86 """Record an error condition and write a message to standard output.""" 87 nonlocal errors 88 errors = True 89 sys.stdout.write('{}: error: {}\n'.format(opts.object, msg)) 90 91 # Iterate over section headers to find the symbol table. 92 for shdr in img.shdrs(): 93 if shdr.sh_type == glibcelf.Sht.SHT_SYMTAB: 94 symbol_table_found = True 95 for sym in img.syms(shdr): 96 if sym.st_name in check_symbols: 97 symbols_found.add(sym.st_name) 98 99 # Validate symbol type, section, and size. 100 if sym.st_info.type != glibcelf.Stt.STT_OBJECT: 101 error('symbol {!r} has wrong type {}'.format( 102 sym.st_name.decode('UTF-8'), sym.st_info.type)) 103 if sym.st_shndx in glibcelf.Shn: 104 error('symbol {!r} has reserved section {}'.format( 105 sym.st_name.decode('UTF-8'), sym.st_shndx)) 106 continue 107 if sym.st_size == 0: 108 error('symbol {!r} has size zero'.format( 109 sym.st_name.decode('UTF-8'))) 110 continue 111 112 check_in_relro('symbol', relro_begin, relro_end, 113 sym.st_name, sym.st_value, sym.st_size, 114 error) 115 continue # SHT_SYMTAB 116 if shdr.sh_name == b'.data.rel.ro' \ 117 or shdr.sh_name.startswith(b'.data.rel.ro.'): 118 check_in_relro('section', relro_begin, relro_end, 119 shdr.sh_name, shdr.sh_addr, shdr.sh_size, 120 error) 121 continue 122 123 if required_symbols - symbols_found: 124 for sym in sorted(required_symbols - symbols_found): 125 error('symbol {!r} not found'.format(sym.decode('UTF-8'))) 126 127 if errors: 128 sys.exit(1) 129 130 if not symbol_table_found: 131 sys.stdout.write( 132 '{}: warning: no symbol table found (stripped object)\n'.format( 133 opts.object)) 134 sys.exit(77) 135 136if __name__ == '__main__': 137 main(sys.argv[1:]) 138