Message ID | 87tv3buirr.fsf@oldenburg2.str.redhat.com |
---|---|
State | New |
Headers | show |
Series | [HELP,NEEDED] elf: Add elf/check-wx-segment, a test for the presence of WX segments | expand |
On 28/02/2020 07:07, Florian Weimer wrote: > Would you please help me to add proper comments to the xfails for the > hppa and sparc ports? > > Currently, both (sets of) targets have an writable and executable load > segment. Is this because the lack of secure PLT support, like > originally on POWER? > > The new test passes everywhere else. For sparc it seems to be the case from its documentation [1]. And checking with a simple programs that calls 'printf' on sparc64, its maps shows: 10000000000-10000002000 r-xp 00000000 fd:00 30858878 /home/azanella/glibc/build/sparc64-linux-gnu/test 10000100000-10000102000 r--p 00000000 fd:00 30858878 /home/azanella/glibc/build/sparc64-linux-gnu/test 10000102000-10000104000 rwxp 00002000 fd:00 30858878 /home/azanella/glibc/build/sparc64-linux-gnu/test 7fefffde000-7ff00000000 rw-p 00000000 00:00 0 [stack] fff8000100000000-fff8000100022000 r-xp 00000000 fd:00 131268 /lib/sparc64-linux-gnu/ld-2.29.so fff8000100022000-fff8000100024000 r--p 00000000 00:00 0 [vvar] fff8000100024000-fff8000100026000 r-xp 00000000 00:00 0 [vdso] fff8000100026000-fff800010002a000 rw-p 00000000 00:00 0 fff8000100122000-fff8000100124000 r--p 00022000 fd:00 131268 /lib/sparc64-linux-gnu/ld-2.29.so fff8000100124000-fff8000100126000 rwxp 00024000 fd:00 131268 /lib/sparc64-linux-gnu/ld-2.29.so fff8000100128000-fff8000100286000 r-xp 00000000 fd:00 131522 /lib/sparc64-linux-gnu/libc-2.29.so fff8000100286000-fff8000100384000 ---p 0015e000 fd:00 131522 /lib/sparc64-linux-gnu/libc-2.29.so fff8000100384000-fff8000100388000 r--p 0015c000 fd:00 131522 /lib/sparc64-linux-gnu/libc-2.29.so fff8000100388000-fff800010038c000 rwxp 00160000 fd:00 131522 /lib/sparc64-linux-gnu/libc-2.29.so fff800010038c000-fff8000100390000 rwxp 00000000 00:00 0 (gdb) disas Dump of assembler code for function puts@got.plt: => 0x00000100001021a0 <+0>: sethi %hi(0x28000), %g1 0x00000100001021a4 <+4>: b,a %xcc, 0x10000102120 It branches to the .PLT101 (as defined in reference documentation): (gdb) x/20i $pc => 0x10000102120: sethi %hi(0xfff80000), %g4 0x10000102124: sethi %hi(0x16400), %g5 0x10000102128: or %g4, 1, %g4 0x1000010212c: or %g5, 0x300, %g5 0x10000102130: sllx %g4, 0x20, %g4 0x10000102134: add %g4, %g5, %g5 0x10000102138: jmpl %g5, %g4 0x1000010213c: nop Then to ld.so: (gdb) _dl_runtime_resolve_1 () at ../sysdeps/sparc/sparc64/dl-trampoline.S:77 (gdb) si [...] 87 in ../sysdeps/sparc/sparc64/dl-trampoline.S (gdb) _dl_fixup (l=0xfff8000100125470, reloc_arg=24) at dl-runtime.c:66 (gdb) si [...] elf_machine_fixup_plt (value=18444492278192513952, reloc_addr=0x100001021a0 <puts@got.plt>, reloc=<optimized out>, sym=0xfff8000100131e68, refsym=<optimized out>, t=<optimized out>, map=0xfff8000100125470) (gdb) si [...] (gdb) sparc64_fixup_plt (t=1, high=0, value=18444492278192513952, reloc_addr=0x100001021a0 <puts@got.plt>, reloc=<optimized out>, map=0xfff8000100125470) at ../sysdeps/sparc/sparc64/dl-plt.h:44 And then it rewrites the expected PLT sequence on the 'puts@got.plt': sysdeps/sparc/sparc64/dl-plt.h 120 /* sethi %hh(value), %g1 121 sethi %lm(value), %g5 122 or %g1, %hm(value), %g1 123 or %g5, %lo(value), %g5 124 sllx %g1, 32, %g1 125 jmpl %g1 + %g5, %g0 126 nop */ 127 128 insns[5] = 0x81c04005; 129 __asm __volatile ("flush %0 + 20" : : "r" (insns)); 130 131 insns[4] = 0x83287020; 132 __asm __volatile ("flush %0 + 16" : : "r" (insns)); 133 134 insns[3] = 0x8a116000 | (low32 & 0x3ff); 135 __asm __volatile ("flush %0 + 12" : : "r" (insns)); 136 137 insns[2] = 0x82106000 | (high32 & 0x3ff); And even -z,now does not really help here, since sparc does not create a different LOAD segment for program data. For instance, making the program RWX segment on the program above just RX, triggers a segfault at: Program received signal SIGSEGV, Segmentation fault. 0x00000100000007f0 in __do_global_dtors_aux () (gdb) disas Dump of assembler code for function __do_global_dtors_aux: 0x0000010000000798 <+0>: save %sp, -176, %sp 0x000001000000079c <+4>: sethi %hi(0x101800), %l7 0x00000100000007a0 <+8>: call 0x100000006c0 <__sparc_get_pc_thunk.l7> 0x00000100000007a4 <+12>: add %l7, 0, %l7 ! 0x101800 0x00000100000007a8 <+16>: sethi %hi(0), %i5 0x00000100000007ac <+20>: xor %i5, 0x170, %i5 0x00000100000007b0 <+24>: add %l7, %i5, %i5 0x00000100000007b4 <+28>: ldub [ %i5 ], %g1 0x00000100000007b8 <+32>: cmp %g1, 0 0x00000100000007bc <+36>: bne,pn %icc, 0x100000007f4 <__do_global_dtors_aux+92> 0x00000100000007c0 <+40>: sethi %hi(0), %g1 0x00000100000007c4 <+44>: xor %g1, 0x10, %g1 0x00000100000007c8 <+48>: ldx [ %l7 + %g1 ], %g1 0x00000100000007cc <+52>: brz,pn %g1, 0x100000007e4 <__do_global_dtors_aux+76> 0x00000100000007d0 <+56>: sethi %hi(0), %g1 0x00000100000007d4 <+60>: xor %g1, 0x168, %g1 0x00000100000007d8 <+64>: add %l7, %g1, %l7 0x00000100000007dc <+68>: call 0x10000102080 <__cxa_finalize@got.plt> 0x00000100000007e0 <+72>: ldx [ %l7 ], %o0 0x00000100000007e4 <+76>: call 0x100000006e0 <deregister_tm_clones> 0x00000100000007e8 <+80>: nop 0x00000100000007ec <+84>: mov 1, %g1 ! 0x1 => 0x00000100000007f0 <+88>: stb %g1, [ %i5 ] 0x00000100000007f4 <+92>: return %i7 + 8 0x00000100000007f8 <+96>: nop Since __do_global_dtors_aux sets a 'static _Bool completed' at its completion. [1] https://docs.oracle.com/cd/E19455-01/816-0559/chapter6-1236/index.html > > Thanks, > Florian > 8<------------------------------------------------------------------8< > Writable, executable segments defeat security hardening. The > existing check for DT_TEXTREL does not catch this. > > ----- > elf/Makefile | 7 +++ > scripts/check-wx-segment.py | 85 +++++++++++++++++++++++++++++++++++ > sysdeps/sparc/Makefile | 4 ++ > sysdeps/unix/sysv/linux/hppa/Makefile | 7 ++- > 4 files changed, 101 insertions(+), 2 deletions(-) > > diff --git a/elf/Makefile b/elf/Makefile > index a137143db7..da689a2c7b 100644 > --- a/elf/Makefile > +++ b/elf/Makefile > @@ -402,6 +402,7 @@ tests-special += $(objpfx)tst-pathopt.out $(objpfx)tst-rtld-load-self.out \ > $(objpfx)tst-rtld-preload.out > endif > tests-special += $(objpfx)check-textrel.out $(objpfx)check-execstack.out \ > + $(objpfx)check-wx-segment.out \ > $(objpfx)check-localplt.out $(objpfx)check-initfini.out > endif > > @@ -1180,6 +1181,12 @@ $(objpfx)check-execstack.out: $(..)scripts/check-execstack.awk \ > $(evaluate-test) > generated += check-execstack.out > > +$(objpfx)check-wx-segment.out: $(..)scripts/check-wx-segment.py \ > + $(all-built-dso:=.phdr) > + $(PYTHON) $^ --xfail="$(check-wx-segment-xfail)" > $@; \ > + $(evaluate-test) > +generated += check-wx-segment.out > + > $(objpfx)tst-dlmodcount: $(libdl) > $(objpfx)tst-dlmodcount.out: $(test-modules) > > diff --git a/scripts/check-wx-segment.py b/scripts/check-wx-segment.py > new file mode 100644 > index 0000000000..c3553eeef9 > --- /dev/null > +++ b/scripts/check-wx-segment.py > @@ -0,0 +1,85 @@ > +#!/usr/bin/python3 > +# Check ELF program headers for WX segments. > +# Copyright (C) 2020 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 > +# <https://www.gnu.org/licenses/>. > + > +"""Check that the program headers do not contain write-exec segments.""" > + > +import argparse > +import os.path > +import re > +import sys > + > +# Regular expression to extract the RWE flags field. The > +# address/offset columns have varying width. > +RE_LOAD = re.compile( > + r'^ LOAD +(?:0x[0-9a-fA-F]+ +){5}([R ][W ][ E]) +0x[0-9a-fA-F]+\n\Z') > + > +def process_file(path, inp, xfail): > + """Analyze one input file.""" > + > + errors = 0 > + for line in inp: > + error = None > + if line.startswith(' LOAD '): > + match = RE_LOAD.match(line) > + if match is None: > + error = 'Invalid LOAD line' > + else: > + flags, = match.groups() > + if 'W' in flags and 'E' in flags: > + if xfail: > + print('{}: warning: WX segment (as expected)'.format( > + path)) > + else: > + error = 'WX segment' > + > + if error is not None: > + print('{}: error: {}: {!r}'.format(path, error, line.strip())) > + errors += 1 > + > + if xfail and errors == 0: > + print('{}: warning: missing expected WX segment'.format(path)) > + return errors > + > + > +def main(): > + """The main entry point.""" > + parser = argparse.ArgumentParser(description=__doc__) > + parser.add_argument('--xfail', > + help='Run this number of jobs in parallel', > + type=str, default='') > + parser.add_argument('phdrs', > + help='Files containing readelf -Wl output', > + nargs='*') > + opts = parser.parse_args(sys.argv) > + > + xfails = set(opts.xfail.split(' ')) > + xfails_all = opts.xfail.strip() == '*' > + > + errors = 0 > + for path in opts.phdrs: > + xfail = ((os.path.basename(path) + '.phdrs') in xfails > + or xfails_all) > + with open(path) as inp: > + errors += process_file(path, inp, xfail) > + if errors > 0: > + sys.exit(1) > + > + > +if __name__ == '__main__': > + main() > diff --git a/sysdeps/sparc/Makefile b/sysdeps/sparc/Makefile > index 38b33af6e0..3ceb8d0298 100644 > --- a/sysdeps/sparc/Makefile > +++ b/sysdeps/sparc/Makefile > @@ -16,6 +16,10 @@ CPPFLAGS-crti.S += -fPIC > CPPFLAGS-crtn.S += -fPIC > endif > > +ifeq ($(subdir),elf) > +test-xfail-check-wx-segment = * > +endif # $(subdir) == elf > + > # nscd uses atomic_spin_nop which in turn requires cpu_relax > ifeq ($(subdir),nscd) > routines += cpu_relax > diff --git a/sysdeps/unix/sysv/linux/hppa/Makefile b/sysdeps/unix/sysv/linux/hppa/Makefile > index e1637f54f5..1e7340418b 100644 > --- a/sysdeps/unix/sysv/linux/hppa/Makefile > +++ b/sysdeps/unix/sysv/linux/hppa/Makefile > @@ -3,9 +3,12 @@ ifeq ($(subdir),stdlib) > gen-as-const-headers += ucontext_i.sym > endif > > +ifeq ($(subdir),elf) > # Supporting non-executable stacks on HPPA requires changes to both > # the Linux kernel and glibc. The kernel currently needs an executable > # stack for syscall restarts and signal returns. > -ifeq ($(subdir),elf) > test-xfail-check-execstack = yes > -endif > + > +test-xfail-check-wx-segment = * > + > +endif # $(subdir) == elf >
On 2020-02-28 5:07 a.m., Florian Weimer wrote: > Would you please help me to add proper comments to the xfails for the > hppa and sparc ports? > > Currently, both (sets of) targets have an writable and executable load > segment. Is this because the lack of secure PLT support, like > originally on POWER? Currently, the PLT is executable on hppa because it contains an executable trampoline used during lazy binding. > The new test passes everywhere else. > Attached are test results without your change and with your change, respectively. Also attached is the output from the new test. The test results were: XFAIL: elf/check-wx-segment original exit status 1 Dave
* John David Anglin: > On 2020-02-28 5:07 a.m., Florian Weimer wrote: >> Would you please help me to add proper comments to the xfails for the >> hppa and sparc ports? >> >> Currently, both (sets of) targets have an writable and executable load >> segment. Is this because the lack of secure PLT support, like >> originally on POWER? > Currently, the PLT is executable on hppa because it contains an > executable trampoline used during lazy binding. Thanks, I've updated the patch. >> The new test passes everywhere else. >> > Attached are test results without your change and with your change, > respectively. Also attached is the output from the new test. The > test results were: > > XFAIL: elf/check-wx-segment > original exit status 1 It's a cross-test, so it runs during build-many-glibcs.py, too. Adhemerval, I've incorporated your comments as well. Okay for master? Thanks, Florian 8<------------------------------------------------------------------8< Writable, executable segments defeat security hardening. The existing check for DT_TEXTREL does not catch this. hppa and SPARC currently keep the PLT in an RWX load segment. ----- elf/Makefile | 7 +++ scripts/check-wx-segment.py | 85 +++++++++++++++++++++++++++++++++++ sysdeps/sparc/Makefile | 9 ++++ sysdeps/unix/sysv/linux/hppa/Makefile | 9 +++- 4 files changed, 108 insertions(+), 2 deletions(-) diff --git a/elf/Makefile b/elf/Makefile index a137143db7..da689a2c7b 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -402,6 +402,7 @@ tests-special += $(objpfx)tst-pathopt.out $(objpfx)tst-rtld-load-self.out \ $(objpfx)tst-rtld-preload.out endif tests-special += $(objpfx)check-textrel.out $(objpfx)check-execstack.out \ + $(objpfx)check-wx-segment.out \ $(objpfx)check-localplt.out $(objpfx)check-initfini.out endif @@ -1180,6 +1181,12 @@ $(objpfx)check-execstack.out: $(..)scripts/check-execstack.awk \ $(evaluate-test) generated += check-execstack.out +$(objpfx)check-wx-segment.out: $(..)scripts/check-wx-segment.py \ + $(all-built-dso:=.phdr) + $(PYTHON) $^ --xfail="$(check-wx-segment-xfail)" > $@; \ + $(evaluate-test) +generated += check-wx-segment.out + $(objpfx)tst-dlmodcount: $(libdl) $(objpfx)tst-dlmodcount.out: $(test-modules) diff --git a/scripts/check-wx-segment.py b/scripts/check-wx-segment.py new file mode 100644 index 0000000000..e1fa79387c --- /dev/null +++ b/scripts/check-wx-segment.py @@ -0,0 +1,85 @@ +#!/usr/bin/python3 +# Check ELF program headers for WX segments. +# Copyright (C) 2020 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 +# <https://www.gnu.org/licenses/>. + +"""Check that the program headers do not contain write-exec segments.""" + +import argparse +import os.path +import re +import sys + +# Regular expression to extract the RWE flags field. The +# address/offset columns have varying width. +RE_LOAD = re.compile( + r'^ LOAD +(?:0x[0-9a-fA-F]+ +){5}([R ][W ][ E]) +0x[0-9a-fA-F]+\n\Z') + +def process_file(path, inp, xfail): + """Analyze one input file.""" + + errors = 0 + for line in inp: + error = None + if line.startswith(' LOAD '): + match = RE_LOAD.match(line) + if match is None: + error = 'Invalid LOAD line' + else: + flags, = match.groups() + if 'W' in flags and 'E' in flags: + if xfail: + print('{}: warning: WX segment (as expected)'.format( + path)) + else: + error = 'WX segment' + + if error is not None: + print('{}: error: {}: {!r}'.format(path, error, line.strip())) + errors += 1 + + if xfail and errors == 0: + print('{}: warning: missing expected WX segment'.format(path)) + return errors + + +def main(): + """The main entry point.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--xfail', + help='Mark input files as XFAILed ("*" for all)', + type=str, default='') + parser.add_argument('phdrs', + help='Files containing readelf -Wl output', + nargs='*') + opts = parser.parse_args(sys.argv) + + xfails = set(opts.xfail.split(' ')) + xfails_all = opts.xfail.strip() == '*' + + errors = 0 + for path in opts.phdrs: + xfail = ((os.path.basename(path) + '.phdrs') in xfails + or xfails_all) + with open(path) as inp: + errors += process_file(path, inp, xfail) + if errors > 0: + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/sysdeps/sparc/Makefile b/sysdeps/sparc/Makefile index 38b33af6e0..c123b527b5 100644 --- a/sysdeps/sparc/Makefile +++ b/sysdeps/sparc/Makefile @@ -16,6 +16,15 @@ CPPFLAGS-crti.S += -fPIC CPPFLAGS-crtn.S += -fPIC endif +ifeq ($(subdir),elf) + +# Lazy binding on SPARC rewrites the PLT sequence. See the Solaris +# Linker and Libraries Guide, section SPARC: Procedure Linkage Table. +# <https://docs.oracle.com/cd/E19455-01/816-0559/chapter6-1236/index.html> +test-xfail-check-wx-segment = * + +endif # $(subdir) == elf + # nscd uses atomic_spin_nop which in turn requires cpu_relax ifeq ($(subdir),nscd) routines += cpu_relax diff --git a/sysdeps/unix/sysv/linux/hppa/Makefile b/sysdeps/unix/sysv/linux/hppa/Makefile index e1637f54f5..c89ec83182 100644 --- a/sysdeps/unix/sysv/linux/hppa/Makefile +++ b/sysdeps/unix/sysv/linux/hppa/Makefile @@ -3,9 +3,14 @@ ifeq ($(subdir),stdlib) gen-as-const-headers += ucontext_i.sym endif +ifeq ($(subdir),elf) # Supporting non-executable stacks on HPPA requires changes to both # the Linux kernel and glibc. The kernel currently needs an executable # stack for syscall restarts and signal returns. -ifeq ($(subdir),elf) test-xfail-check-execstack = yes -endif + +# On hppa, the PLT is executable because it contains an executable +# trampoline used during lazy binding. +test-xfail-check-wx-segment = * + +endif # $(subdir) == elf
On 02/03/2020 06:13, Florian Weimer wrote: > * John David Anglin: > >> On 2020-02-28 5:07 a.m., Florian Weimer wrote: >>> Would you please help me to add proper comments to the xfails for the >>> hppa and sparc ports? >>> >>> Currently, both (sets of) targets have an writable and executable load >>> segment. Is this because the lack of secure PLT support, like >>> originally on POWER? > >> Currently, the PLT is executable on hppa because it contains an >> executable trampoline used during lazy binding. > > Thanks, I've updated the patch. > >>> The new test passes everywhere else. >>> >> Attached are test results without your change and with your change, >> respectively. Also attached is the output from the new test. The >> test results were: >> >> XFAIL: elf/check-wx-segment >> original exit status 1 > > It's a cross-test, so it runs during build-many-glibcs.py, too. > > Adhemerval, I've incorporated your comments as well. > > Okay for master? LGTM, thanks. > > Thanks, > Florian > 8<------------------------------------------------------------------8< > Writable, executable segments defeat security hardening. The > existing check for DT_TEXTREL does not catch this. > > hppa and SPARC currently keep the PLT in an RWX load segment. > > ----- > elf/Makefile | 7 +++ > scripts/check-wx-segment.py | 85 +++++++++++++++++++++++++++++++++++ > sysdeps/sparc/Makefile | 9 ++++ > sysdeps/unix/sysv/linux/hppa/Makefile | 9 +++- > 4 files changed, 108 insertions(+), 2 deletions(-) > > diff --git a/elf/Makefile b/elf/Makefile > index a137143db7..da689a2c7b 100644 > --- a/elf/Makefile > +++ b/elf/Makefile > @@ -402,6 +402,7 @@ tests-special += $(objpfx)tst-pathopt.out $(objpfx)tst-rtld-load-self.out \ > $(objpfx)tst-rtld-preload.out > endif > tests-special += $(objpfx)check-textrel.out $(objpfx)check-execstack.out \ > + $(objpfx)check-wx-segment.out \ > $(objpfx)check-localplt.out $(objpfx)check-initfini.out > endif > > @@ -1180,6 +1181,12 @@ $(objpfx)check-execstack.out: $(..)scripts/check-execstack.awk \ > $(evaluate-test) > generated += check-execstack.out > > +$(objpfx)check-wx-segment.out: $(..)scripts/check-wx-segment.py \ > + $(all-built-dso:=.phdr) > + $(PYTHON) $^ --xfail="$(check-wx-segment-xfail)" > $@; \ > + $(evaluate-test) > +generated += check-wx-segment.out > + > $(objpfx)tst-dlmodcount: $(libdl) > $(objpfx)tst-dlmodcount.out: $(test-modules) > > diff --git a/scripts/check-wx-segment.py b/scripts/check-wx-segment.py > new file mode 100644 > index 0000000000..e1fa79387c > --- /dev/null > +++ b/scripts/check-wx-segment.py > @@ -0,0 +1,85 @@ > +#!/usr/bin/python3 > +# Check ELF program headers for WX segments. > +# Copyright (C) 2020 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 > +# <https://www.gnu.org/licenses/>. > + > +"""Check that the program headers do not contain write-exec segments.""" > + > +import argparse > +import os.path > +import re > +import sys > + > +# Regular expression to extract the RWE flags field. The > +# address/offset columns have varying width. > +RE_LOAD = re.compile( > + r'^ LOAD +(?:0x[0-9a-fA-F]+ +){5}([R ][W ][ E]) +0x[0-9a-fA-F]+\n\Z') > + > +def process_file(path, inp, xfail): > + """Analyze one input file.""" > + > + errors = 0 > + for line in inp: > + error = None > + if line.startswith(' LOAD '): > + match = RE_LOAD.match(line) > + if match is None: > + error = 'Invalid LOAD line' > + else: > + flags, = match.groups() > + if 'W' in flags and 'E' in flags: > + if xfail: > + print('{}: warning: WX segment (as expected)'.format( > + path)) > + else: > + error = 'WX segment' > + > + if error is not None: > + print('{}: error: {}: {!r}'.format(path, error, line.strip())) > + errors += 1 > + > + if xfail and errors == 0: > + print('{}: warning: missing expected WX segment'.format(path)) > + return errors > + > + > +def main(): > + """The main entry point.""" > + parser = argparse.ArgumentParser(description=__doc__) > + parser.add_argument('--xfail', > + help='Mark input files as XFAILed ("*" for all)', > + type=str, default='') > + parser.add_argument('phdrs', > + help='Files containing readelf -Wl output', > + nargs='*') > + opts = parser.parse_args(sys.argv) > + > + xfails = set(opts.xfail.split(' ')) > + xfails_all = opts.xfail.strip() == '*' > + > + errors = 0 > + for path in opts.phdrs: > + xfail = ((os.path.basename(path) + '.phdrs') in xfails > + or xfails_all) > + with open(path) as inp: > + errors += process_file(path, inp, xfail) > + if errors > 0: > + sys.exit(1) > + > + > +if __name__ == '__main__': > + main() > diff --git a/sysdeps/sparc/Makefile b/sysdeps/sparc/Makefile > index 38b33af6e0..c123b527b5 100644 > --- a/sysdeps/sparc/Makefile > +++ b/sysdeps/sparc/Makefile > @@ -16,6 +16,15 @@ CPPFLAGS-crti.S += -fPIC > CPPFLAGS-crtn.S += -fPIC > endif > > +ifeq ($(subdir),elf) > + > +# Lazy binding on SPARC rewrites the PLT sequence. See the Solaris > +# Linker and Libraries Guide, section SPARC: Procedure Linkage Table. > +# <https://docs.oracle.com/cd/E19455-01/816-0559/chapter6-1236/index.html> > +test-xfail-check-wx-segment = * > + > +endif # $(subdir) == elf > + > # nscd uses atomic_spin_nop which in turn requires cpu_relax > ifeq ($(subdir),nscd) > routines += cpu_relax > diff --git a/sysdeps/unix/sysv/linux/hppa/Makefile b/sysdeps/unix/sysv/linux/hppa/Makefile > index e1637f54f5..c89ec83182 100644 > --- a/sysdeps/unix/sysv/linux/hppa/Makefile > +++ b/sysdeps/unix/sysv/linux/hppa/Makefile > @@ -3,9 +3,14 @@ ifeq ($(subdir),stdlib) > gen-as-const-headers += ucontext_i.sym > endif > > +ifeq ($(subdir),elf) > # Supporting non-executable stacks on HPPA requires changes to both > # the Linux kernel and glibc. The kernel currently needs an executable > # stack for syscall restarts and signal returns. > -ifeq ($(subdir),elf) > test-xfail-check-execstack = yes > -endif > + > +# On hppa, the PLT is executable because it contains an executable > +# trampoline used during lazy binding. > +test-xfail-check-wx-segment = * > + > +endif # $(subdir) == elf >
diff --git a/elf/Makefile b/elf/Makefile index a137143db7..da689a2c7b 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -402,6 +402,7 @@ tests-special += $(objpfx)tst-pathopt.out $(objpfx)tst-rtld-load-self.out \ $(objpfx)tst-rtld-preload.out endif tests-special += $(objpfx)check-textrel.out $(objpfx)check-execstack.out \ + $(objpfx)check-wx-segment.out \ $(objpfx)check-localplt.out $(objpfx)check-initfini.out endif @@ -1180,6 +1181,12 @@ $(objpfx)check-execstack.out: $(..)scripts/check-execstack.awk \ $(evaluate-test) generated += check-execstack.out +$(objpfx)check-wx-segment.out: $(..)scripts/check-wx-segment.py \ + $(all-built-dso:=.phdr) + $(PYTHON) $^ --xfail="$(check-wx-segment-xfail)" > $@; \ + $(evaluate-test) +generated += check-wx-segment.out + $(objpfx)tst-dlmodcount: $(libdl) $(objpfx)tst-dlmodcount.out: $(test-modules) diff --git a/scripts/check-wx-segment.py b/scripts/check-wx-segment.py new file mode 100644 index 0000000000..c3553eeef9 --- /dev/null +++ b/scripts/check-wx-segment.py @@ -0,0 +1,85 @@ +#!/usr/bin/python3 +# Check ELF program headers for WX segments. +# Copyright (C) 2020 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 +# <https://www.gnu.org/licenses/>. + +"""Check that the program headers do not contain write-exec segments.""" + +import argparse +import os.path +import re +import sys + +# Regular expression to extract the RWE flags field. The +# address/offset columns have varying width. +RE_LOAD = re.compile( + r'^ LOAD +(?:0x[0-9a-fA-F]+ +){5}([R ][W ][ E]) +0x[0-9a-fA-F]+\n\Z') + +def process_file(path, inp, xfail): + """Analyze one input file.""" + + errors = 0 + for line in inp: + error = None + if line.startswith(' LOAD '): + match = RE_LOAD.match(line) + if match is None: + error = 'Invalid LOAD line' + else: + flags, = match.groups() + if 'W' in flags and 'E' in flags: + if xfail: + print('{}: warning: WX segment (as expected)'.format( + path)) + else: + error = 'WX segment' + + if error is not None: + print('{}: error: {}: {!r}'.format(path, error, line.strip())) + errors += 1 + + if xfail and errors == 0: + print('{}: warning: missing expected WX segment'.format(path)) + return errors + + +def main(): + """The main entry point.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--xfail', + help='Run this number of jobs in parallel', + type=str, default='') + parser.add_argument('phdrs', + help='Files containing readelf -Wl output', + nargs='*') + opts = parser.parse_args(sys.argv) + + xfails = set(opts.xfail.split(' ')) + xfails_all = opts.xfail.strip() == '*' + + errors = 0 + for path in opts.phdrs: + xfail = ((os.path.basename(path) + '.phdrs') in xfails + or xfails_all) + with open(path) as inp: + errors += process_file(path, inp, xfail) + if errors > 0: + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/sysdeps/sparc/Makefile b/sysdeps/sparc/Makefile index 38b33af6e0..3ceb8d0298 100644 --- a/sysdeps/sparc/Makefile +++ b/sysdeps/sparc/Makefile @@ -16,6 +16,10 @@ CPPFLAGS-crti.S += -fPIC CPPFLAGS-crtn.S += -fPIC endif +ifeq ($(subdir),elf) +test-xfail-check-wx-segment = * +endif # $(subdir) == elf + # nscd uses atomic_spin_nop which in turn requires cpu_relax ifeq ($(subdir),nscd) routines += cpu_relax diff --git a/sysdeps/unix/sysv/linux/hppa/Makefile b/sysdeps/unix/sysv/linux/hppa/Makefile index e1637f54f5..1e7340418b 100644 --- a/sysdeps/unix/sysv/linux/hppa/Makefile +++ b/sysdeps/unix/sysv/linux/hppa/Makefile @@ -3,9 +3,12 @@ ifeq ($(subdir),stdlib) gen-as-const-headers += ucontext_i.sym endif +ifeq ($(subdir),elf) # Supporting non-executable stacks on HPPA requires changes to both # the Linux kernel and glibc. The kernel currently needs an executable # stack for syscall restarts and signal returns. -ifeq ($(subdir),elf) test-xfail-check-execstack = yes -endif + +test-xfail-check-wx-segment = * + +endif # $(subdir) == elf