1#!/usr/bin/python3
2# Check ELF program headers for WX segments.
3# Copyright (C) 2020-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"""Check that the program headers do not contain write-exec segments."""
21
22import argparse
23import os.path
24import re
25import sys
26
27# Regular expression to extract the RWE flags field.  The
28# address/offset columns have varying width.
29RE_LOAD = re.compile(
30    r'^  LOAD +(?:0x[0-9a-fA-F]+ +){5}([R ][W ][ E]) +0x[0-9a-fA-F]+\n\Z')
31
32def process_file(path, inp, xfail):
33    """Analyze one input file."""
34
35    errors = 0
36    for line in inp:
37        error = None
38        if line.startswith('  LOAD '):
39            match = RE_LOAD.match(line)
40            if match is None:
41                error = 'Invalid LOAD line'
42            else:
43                flags, = match.groups()
44                if 'W' in flags and 'E' in flags:
45                    if xfail:
46                        print('{}: warning: WX segment (as expected)'.format(
47                            path))
48                    else:
49                        error = 'WX segment'
50
51        if error is not None:
52            print('{}: error: {}: {!r}'.format(path, error, line.strip()))
53            errors += 1
54
55    if xfail and errors == 0:
56        print('{}: warning: missing expected WX segment'.format(path))
57    return errors
58
59
60def main():
61    """The main entry point."""
62    parser = argparse.ArgumentParser(description=__doc__)
63    parser.add_argument('--xfail',
64                        help='Mark input files as XFAILed ("*" for all)',
65                        type=str, default='')
66    parser.add_argument('phdrs',
67                        help='Files containing readelf -Wl output',
68                        nargs='*')
69    opts = parser.parse_args(sys.argv)
70
71    xfails = set(opts.xfail.split(' '))
72    xfails_all = opts.xfail.strip() == '*'
73
74    errors = 0
75    for path in opts.phdrs:
76        xfail = ((os.path.basename(path) + '.phdrs') in xfails
77                 or xfails_all)
78        with open(path) as inp:
79            errors += process_file(path, inp, xfail)
80    if errors > 0:
81        sys.exit(1)
82
83
84if __name__ == '__main__':
85    main()
86