[HELP,NEEDED] elf: Add elf/check-wx-segment, a test for the presence of WX segments

Message ID 87tv3buirr.fsf@oldenburg2.str.redhat.com
State Superseded
Headers

Commit Message

Florian Weimer Feb. 28, 2020, 10:07 a.m. UTC
  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.

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(-)
  

Comments

Adhemerval Zanella Feb. 28, 2020, 1:29 p.m. UTC | #1
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
>
  
John David Anglin March 2, 2020, 2:20 a.m. UTC | #2
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
  

Patch

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