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
>
@@ -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)
new file mode 100644
@@ -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()
@@ -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
@@ -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