]>
Commit | Line | Data |
---|---|---|
198abcbb FW |
1 | #!/usr/bin/python3 |
2 | # Verify that certain symbols are covered by RELRO. | |
6d7e8eda | 3 | # Copyright (C) 2022-2023 Free Software Foundation, Inc. |
198abcbb FW |
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 | |
21 | present and covered by the PT_GNU_RELRO segment. | |
22 | ||
23 | """ | |
24 | ||
25 | import argparse | |
26 | import os.path | |
27 | import sys | |
28 | ||
29 | # Make available glibc Python modules. | |
30 | sys.path.append(os.path.join( | |
31 | os.path.dirname(os.path.realpath(__file__)), os.path.pardir, 'scripts')) | |
32 | ||
33 | import glibcelf | |
34 | ||
35 | def 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 | ||
46 | def 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 | ||
55 | def 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') | |
71e408e4 AZ |
59 | parser.add_argument('--required', metavar='NAME', action='append', |
60 | default=[], help='required symbol names') | |
61 | parser.add_argument('--optional', metavar='NAME', action='append', | |
62 | default=[], help='required symbol names') | |
198abcbb FW |
63 | return parser |
64 | ||
65 | def 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 | ||
136 | if __name__ == '__main__': | |
137 | main(sys.argv[1:]) |