1d4c55
commit 198abcbb94618730dae1b3f4393efaa49e0ec8c7
1d4c55
Author: Florian Weimer <fweimer@redhat.com>
1d4c55
Date:   Mon Apr 11 11:30:31 2022 +0200
1d4c55
1d4c55
    Default to --with-default-link=no (bug 25812)
1d4c55
1d4c55
    This is necessary to place the libio vtables into the RELRO segment.
1d4c55
    New tests elf/tst-relro-ldso and elf/tst-relro-libc are added to
1d4c55
    verify that this is what actually happens.
1d4c55
1d4c55
    The new tests fail on ia64 due to lack of (default) RELRO support
1d4c55
    inbutils, so they are XFAILed there.
1d4c55
1d4c55
Conflicts:
1d4c55
	elf/Makefile
1d4c55
	  (missing valgrind smoke test)
1d4c55
1d4c55
diff --git a/INSTALL b/INSTALL
1d4c55
index b3a4370f592c5047..b69672b283c0b774 100644
1d4c55
--- a/INSTALL
1d4c55
+++ b/INSTALL
1d4c55
@@ -90,6 +90,12 @@ if 'CFLAGS' is specified it must enable optimization.  For example:
1d4c55
      library will still be usable, but functionality may be lost--for
1d4c55
      example, you can't build a shared libc with old binutils.
1d4c55
 
1d4c55
+'--with-default-link=FLAG'
1d4c55
+     With '--with-default-link=yes', the build system does not use a
1d4c55
+     custom linker script for linking shared objects.  The default for
1d4c55
+     FLAG is the opposite, 'no', because the custom linker script is
1d4c55
+     needed for full RELRO protection.
1d4c55
+
1d4c55
 '--with-nonshared-cflags=CFLAGS'
1d4c55
      Use additional compiler flags CFLAGS to build the parts of the
1d4c55
      library which are always statically linked into applications and
1d4c55
diff --git a/configure b/configure
1d4c55
index 8b3681d2e28310c8..c794cea4359b3da3 100755
1d4c55
--- a/configure
1d4c55
+++ b/configure
1d4c55
@@ -3339,7 +3339,7 @@ fi
1d4c55
 if test "${with_default_link+set}" = set; then :
1d4c55
   withval=$with_default_link; use_default_link=$withval
1d4c55
 else
1d4c55
-  use_default_link=default
1d4c55
+  use_default_link=no
1d4c55
 fi
1d4c55
 
1d4c55
 
1d4c55
@@ -5965,69 +5965,6 @@ fi
1d4c55
 $as_echo "$libc_cv_hashstyle" >&6; }
1d4c55
 
1d4c55
 
1d4c55
-# The linker's default -shared behavior is good enough if it
1d4c55
-# does these things that our custom linker scripts ensure that
1d4c55
-# all allocated NOTE sections come first.
1d4c55
-if test "$use_default_link" = default; then
1d4c55
-  { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sufficient default -shared layout" >&5
1d4c55
-$as_echo_n "checking for sufficient default -shared layout... " >&6; }
1d4c55
-if ${libc_cv_use_default_link+:} false; then :
1d4c55
-  $as_echo_n "(cached) " >&6
1d4c55
-else
1d4c55
-    libc_cv_use_default_link=no
1d4c55
-  cat > conftest.s <<\EOF
1d4c55
-	  .section .note.a,"a",%note
1d4c55
-	  .balign 4
1d4c55
-	  .long 4,4,9
1d4c55
-	  .string "GNU"
1d4c55
-	  .string "foo"
1d4c55
-	  .section .note.b,"a",%note
1d4c55
-	  .balign 4
1d4c55
-	  .long 4,4,9
1d4c55
-	  .string "GNU"
1d4c55
-	  .string "bar"
1d4c55
-EOF
1d4c55
-  if { ac_try='  ${CC-cc} $ASFLAGS -shared -o conftest.so conftest.s 1>&5'
1d4c55
-  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
1d4c55
-  (eval $ac_try) 2>&5
1d4c55
-  ac_status=$?
1d4c55
-  $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
1d4c55
-  test $ac_status = 0; }; } &&
1d4c55
-       ac_try=`$READELF -S conftest.so | sed -n \
1d4c55
-	 '${x;p;}
1d4c55
-	  s/^ *\[ *[1-9][0-9]*\]  *\([^ ][^ ]*\)  *\([^ ][^ ]*\) .*$/\2 \1/
1d4c55
-	  t a
1d4c55
-	  b
1d4c55
-	  : a
1d4c55
-	  H'`
1d4c55
-  then
1d4c55
-    libc_seen_a=no libc_seen_b=no
1d4c55
-    set -- $ac_try
1d4c55
-    while test $# -ge 2 -a "$1" = NOTE; do
1d4c55
-      case "$2" in
1d4c55
-      .note.a) libc_seen_a=yes ;;
1d4c55
-      .note.b) libc_seen_b=yes ;;
1d4c55
-      esac
1d4c55
-      shift 2
1d4c55
-    done
1d4c55
-    case "$libc_seen_a$libc_seen_b" in
1d4c55
-    yesyes)
1d4c55
-      libc_cv_use_default_link=yes
1d4c55
-      ;;
1d4c55
-    *)
1d4c55
-      echo >&5 "\
1d4c55
-$libc_seen_a$libc_seen_b from:
1d4c55
-$ac_try"
1d4c55
-      ;;
1d4c55
-    esac
1d4c55
-  fi
1d4c55
-  rm -f conftest*
1d4c55
-fi
1d4c55
-{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $libc_cv_use_default_link" >&5
1d4c55
-$as_echo "$libc_cv_use_default_link" >&6; }
1d4c55
-  use_default_link=$libc_cv_use_default_link
1d4c55
-fi
1d4c55
-
1d4c55
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for GLOB_DAT reloc" >&5
1d4c55
 $as_echo_n "checking for GLOB_DAT reloc... " >&6; }
1d4c55
 if ${libc_cv_has_glob_dat+:} false; then :
1d4c55
diff --git a/configure.ac b/configure.ac
1d4c55
index 82d9ab2fb67145bb..52429d82344954b3 100644
1d4c55
--- a/configure.ac
1d4c55
+++ b/configure.ac
1d4c55
@@ -152,7 +152,7 @@ AC_ARG_WITH([default-link],
1d4c55
 	    AC_HELP_STRING([--with-default-link],
1d4c55
 			   [do not use explicit linker scripts]),
1d4c55
 	    [use_default_link=$withval],
1d4c55
-	    [use_default_link=default])
1d4c55
+	    [use_default_link=no])
1d4c55
 
1d4c55
 dnl Additional build flags injection.
1d4c55
 AC_ARG_WITH([nonshared-cflags],
1d4c55
@@ -1352,59 +1352,6 @@ fi
1d4c55
 rm -f conftest*])
1d4c55
 AC_SUBST(libc_cv_hashstyle)
1d4c55
 
1d4c55
-# The linker's default -shared behavior is good enough if it
1d4c55
-# does these things that our custom linker scripts ensure that
1d4c55
-# all allocated NOTE sections come first.
1d4c55
-if test "$use_default_link" = default; then
1d4c55
-  AC_CACHE_CHECK([for sufficient default -shared layout],
1d4c55
-		  libc_cv_use_default_link, [dnl
1d4c55
-  libc_cv_use_default_link=no
1d4c55
-  cat > conftest.s <<\EOF
1d4c55
-	  .section .note.a,"a",%note
1d4c55
-	  .balign 4
1d4c55
-	  .long 4,4,9
1d4c55
-	  .string "GNU"
1d4c55
-	  .string "foo"
1d4c55
-	  .section .note.b,"a",%note
1d4c55
-	  .balign 4
1d4c55
-	  .long 4,4,9
1d4c55
-	  .string "GNU"
1d4c55
-	  .string "bar"
1d4c55
-EOF
1d4c55
-  if AC_TRY_COMMAND([dnl
1d4c55
-  ${CC-cc} $ASFLAGS -shared -o conftest.so conftest.s 1>&AS_MESSAGE_LOG_FD]) &&
1d4c55
-       ac_try=`$READELF -S conftest.so | sed -n \
1d4c55
-	 ['${x;p;}
1d4c55
-	  s/^ *\[ *[1-9][0-9]*\]  *\([^ ][^ ]*\)  *\([^ ][^ ]*\) .*$/\2 \1/
1d4c55
-	  t a
1d4c55
-	  b
1d4c55
-	  : a
1d4c55
-	  H']`
1d4c55
-  then
1d4c55
-    libc_seen_a=no libc_seen_b=no
1d4c55
-    set -- $ac_try
1d4c55
-    while test $# -ge 2 -a "$1" = NOTE; do
1d4c55
-      case "$2" in
1d4c55
-      .note.a) libc_seen_a=yes ;;
1d4c55
-      .note.b) libc_seen_b=yes ;;
1d4c55
-      esac
1d4c55
-      shift 2
1d4c55
-    done
1d4c55
-    case "$libc_seen_a$libc_seen_b" in
1d4c55
-    yesyes)
1d4c55
-      libc_cv_use_default_link=yes
1d4c55
-      ;;
1d4c55
-    *)
1d4c55
-      echo >&AS_MESSAGE_LOG_FD "\
1d4c55
-$libc_seen_a$libc_seen_b from:
1d4c55
-$ac_try"
1d4c55
-      ;;
1d4c55
-    esac
1d4c55
-  fi
1d4c55
-  rm -f conftest*])
1d4c55
-  use_default_link=$libc_cv_use_default_link
1d4c55
-fi
1d4c55
-
1d4c55
 AC_CACHE_CHECK(for GLOB_DAT reloc,
1d4c55
 	       libc_cv_has_glob_dat, [dnl
1d4c55
 cat > conftest.c <
1d4c55
diff --git a/elf/Makefile b/elf/Makefile
1d4c55
index 89ce4f5196e5eb39..1fdf40cbd49e233e 100644
1d4c55
--- a/elf/Makefile
1d4c55
+++ b/elf/Makefile
1d4c55
@@ -477,6 +477,40 @@ tests-execstack-yes = \
1d4c55
   # tests-execstack-yes
1d4c55
 endif
1d4c55
 endif
1d4c55
+
1d4c55
+tests-special += $(objpfx)tst-relro-ldso.out $(objpfx)tst-relro-libc.out
1d4c55
+$(objpfx)tst-relro-ldso.out: tst-relro-symbols.py $(..)/scripts/glibcelf.py \
1d4c55
+  $(objpfx)ld.so
1d4c55
+	$(PYTHON) tst-relro-symbols.py $(objpfx)ld.so \
1d4c55
+	  --required=_rtld_global_ro \
1d4c55
+	  > $@ 2>&1; $(evaluate-test)
1d4c55
+# The optional symbols are present in libc only if the architecture has
1d4c55
+# the GLIBC_2.0 symbol set in libc.
1d4c55
+$(objpfx)tst-relro-libc.out: tst-relro-symbols.py $(..)/scripts/glibcelf.py \
1d4c55
+  $(common-objpfx)libc.so
1d4c55
+	$(PYTHON) tst-relro-symbols.py $(common-objpfx)libc.so \
1d4c55
+	    --required=_IO_cookie_jumps \
1d4c55
+	    --required=_IO_file_jumps \
1d4c55
+	    --required=_IO_file_jumps_maybe_mmap \
1d4c55
+	    --required=_IO_file_jumps_mmap \
1d4c55
+	    --required=_IO_helper_jumps \
1d4c55
+	    --required=_IO_mem_jumps \
1d4c55
+	    --required=_IO_obstack_jumps \
1d4c55
+	    --required=_IO_proc_jumps \
1d4c55
+	    --required=_IO_str_chk_jumps \
1d4c55
+	    --required=_IO_str_jumps \
1d4c55
+	    --required=_IO_strn_jumps \
1d4c55
+	    --required=_IO_wfile_jumps \
1d4c55
+	    --required=_IO_wfile_jumps_maybe_mmap \
1d4c55
+	    --required=_IO_wfile_jumps_mmap \
1d4c55
+	    --required=_IO_wmem_jumps \
1d4c55
+	    --required=_IO_wstr_jumps \
1d4c55
+	    --required=_IO_wstrn_jumps \
1d4c55
+	    --optional=_IO_old_cookie_jumps \
1d4c55
+	    --optional=_IO_old_file_jumps \
1d4c55
+	    --optional=_IO_old_proc_jumps \
1d4c55
+	  > $@ 2>&1; $(evaluate-test)
1d4c55
+
1d4c55
 tests += $(tests-execstack-$(have-z-execstack))
1d4c55
 ifeq ($(run-built-tests),yes)
1d4c55
 tests-special += \
1d4c55
diff --git a/elf/tst-relro-symbols.py b/elf/tst-relro-symbols.py
1d4c55
new file mode 100644
1d4c55
index 0000000000000000..368ea3349f86bd81
1d4c55
--- /dev/null
1d4c55
+++ b/elf/tst-relro-symbols.py
1d4c55
@@ -0,0 +1,137 @@
1d4c55
+#!/usr/bin/python3
1d4c55
+# Verify that certain symbols are covered by RELRO.
1d4c55
+# Copyright (C) 2022 Free Software Foundation, Inc.
1d4c55
+# This file is part of the GNU C Library.
1d4c55
+#
1d4c55
+# The GNU C Library is free software; you can redistribute it and/or
1d4c55
+# modify it under the terms of the GNU Lesser General Public
1d4c55
+# License as published by the Free Software Foundation; either
1d4c55
+# version 2.1 of the License, or (at your option) any later version.
1d4c55
+#
1d4c55
+# The GNU C Library is distributed in the hope that it will be useful,
1d4c55
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
1d4c55
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
1d4c55
+# Lesser General Public License for more details.
1d4c55
+#
1d4c55
+# You should have received a copy of the GNU Lesser General Public
1d4c55
+# License along with the GNU C Library; if not, see
1d4c55
+# <https://www.gnu.org/licenses/>.
1d4c55
+
1d4c55
+"""Analyze a (shared) object to verify that certain symbols are
1d4c55
+present and covered by the PT_GNU_RELRO segment.
1d4c55
+
1d4c55
+"""
1d4c55
+
1d4c55
+import argparse
1d4c55
+import os.path
1d4c55
+import sys
1d4c55
+
1d4c55
+# Make available glibc Python modules.
1d4c55
+sys.path.append(os.path.join(
1d4c55
+    os.path.dirname(os.path.realpath(__file__)), os.path.pardir, 'scripts'))
1d4c55
+
1d4c55
+import glibcelf
1d4c55
+
1d4c55
+def find_relro(path: str, img: glibcelf.Image) -> (int, int):
1d4c55
+    """Discover the address range of the PT_GNU_RELRO segment."""
1d4c55
+    for phdr in img.phdrs():
1d4c55
+        if phdr.p_type == glibcelf.Pt.PT_GNU_RELRO:
1d4c55
+            # The computation is not entirely accurate because
1d4c55
+            # _dl_protect_relro in elf/dl-reloc.c rounds both the
1d4c55
+            # start end and downwards using the run-time page size.
1d4c55
+            return phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz
1d4c55
+    sys.stdout.write('{}: error: no PT_GNU_RELRO segment\n'.format(path))
1d4c55
+    sys.exit(1)
1d4c55
+
1d4c55
+def check_in_relro(kind, relro_begin, relro_end, name, start, size, error):
1d4c55
+    """Check if a section or symbol falls within in the RELRO segment."""
1d4c55
+    end = start + size - 1
1d4c55
+    if not (relro_begin <= start < end < relro_end):
1d4c55
+        error(
1d4c55
+            '{} {!r} of size {} at 0x{:x} is not in RELRO range [0x{:x}, 0x{:x})'.format(
1d4c55
+                kind, name.decode('UTF-8'), start, size,
1d4c55
+                relro_begin, relro_end))
1d4c55
+
1d4c55
+def get_parser():
1d4c55
+    """Return an argument parser for this script."""
1d4c55
+    parser = argparse.ArgumentParser(description=__doc__)
1d4c55
+    parser.add_argument('object', help='path to object file to check')
1d4c55
+    parser.add_argument('--required', metavar='NAME', default=(),
1d4c55
+                        help='required symbol names', nargs='*')
1d4c55
+    parser.add_argument('--optional', metavar='NAME', default=(),
1d4c55
+                        help='required symbol names', nargs='*')
1d4c55
+    return parser
1d4c55
+
1d4c55
+def main(argv):
1d4c55
+    """The main entry point."""
1d4c55
+    parser = get_parser()
1d4c55
+    opts = parser.parse_args(argv)
1d4c55
+    img = glibcelf.Image.readfile(opts.object)
1d4c55
+
1d4c55
+    required_symbols = frozenset([sym.encode('UTF-8')
1d4c55
+                                  for sym in opts.required])
1d4c55
+    optional_symbols = frozenset([sym.encode('UTF-8')
1d4c55
+                                  for sym in opts.optional])
1d4c55
+    check_symbols = required_symbols | optional_symbols
1d4c55
+
1d4c55
+    # Tracks the symbols in check_symbols that have been found.
1d4c55
+    symbols_found = set()
1d4c55
+
1d4c55
+    # Discover the extent of the RELRO segment.
1d4c55
+    relro_begin, relro_end = find_relro(opts.object, img)
1d4c55
+    symbol_table_found = False
1d4c55
+
1d4c55
+    errors = False
1d4c55
+    def error(msg: str) -> None:
1d4c55
+        """Record an error condition and write a message to standard output."""
1d4c55
+        nonlocal errors
1d4c55
+        errors = True
1d4c55
+        sys.stdout.write('{}: error: {}\n'.format(opts.object, msg))
1d4c55
+
1d4c55
+    # Iterate over section headers to find the symbol table.
1d4c55
+    for shdr in img.shdrs():
1d4c55
+        if shdr.sh_type == glibcelf.Sht.SHT_SYMTAB:
1d4c55
+            symbol_table_found = True
1d4c55
+            for sym in img.syms(shdr):
1d4c55
+                if sym.st_name in check_symbols:
1d4c55
+                    symbols_found.add(sym.st_name)
1d4c55
+
1d4c55
+                    # Validate symbol type, section, and size.
1d4c55
+                    if sym.st_info.type != glibcelf.Stt.STT_OBJECT:
1d4c55
+                        error('symbol {!r} has wrong type {}'.format(
1d4c55
+                            sym.st_name.decode('UTF-8'), sym.st_info.type))
1d4c55
+                    if sym.st_shndx in glibcelf.Shn:
1d4c55
+                        error('symbol {!r} has reserved section {}'.format(
1d4c55
+                            sym.st_name.decode('UTF-8'), sym.st_shndx))
1d4c55
+                        continue
1d4c55
+                    if sym.st_size == 0:
1d4c55
+                        error('symbol {!r} has size zero'.format(
1d4c55
+                            sym.st_name.decode('UTF-8')))
1d4c55
+                        continue
1d4c55
+
1d4c55
+                    check_in_relro('symbol', relro_begin, relro_end,
1d4c55
+                                   sym.st_name, sym.st_value, sym.st_size,
1d4c55
+                                   error)
1d4c55
+            continue # SHT_SYMTAB
1d4c55
+        if shdr.sh_name == b'.data.rel.ro' \
1d4c55
+           or shdr.sh_name.startswith(b'.data.rel.ro.'):
1d4c55
+            check_in_relro('section', relro_begin, relro_end,
1d4c55
+                           shdr.sh_name, shdr.sh_addr, shdr.sh_size,
1d4c55
+                           error)
1d4c55
+            continue
1d4c55
+
1d4c55
+    if required_symbols - symbols_found:
1d4c55
+        for sym in sorted(required_symbols - symbols_found):
1d4c55
+            error('symbol {!r} not found'.format(sym.decode('UTF-8')))
1d4c55
+
1d4c55
+    if errors:
1d4c55
+        sys.exit(1)
1d4c55
+
1d4c55
+    if not symbol_table_found:
1d4c55
+        sys.stdout.write(
1d4c55
+            '{}: warning: no symbol table found (stripped object)\n'.format(
1d4c55
+                opts.object))
1d4c55
+        sys.exit(77)
1d4c55
+
1d4c55
+if __name__ == '__main__':
1d4c55
+    main(sys.argv[1:])
1d4c55
diff --git a/manual/install.texi b/manual/install.texi
1d4c55
index c262fd56d0cef67b..a2c43bd692de7825 100644
1d4c55
--- a/manual/install.texi
1d4c55
+++ b/manual/install.texi
1d4c55
@@ -117,6 +117,12 @@ problem and suppress these constructs, so that the library will still be
1d4c55
 usable, but functionality may be lost---for example, you can't build a
1d4c55
 shared libc with old binutils.
1d4c55
 
1d4c55
+@item --with-default-link=@var{FLAG}
1d4c55
+With @code{--with-default-link=yes}, the build system does not use a
1d4c55
+custom linker script for linking shared objects.  The default for
1d4c55
+@var{FLAG} is the opposite, @samp{no}, because the custom linker script
1d4c55
+is needed for full RELRO protection.
1d4c55
+
1d4c55
 @item --with-nonshared-cflags=@var{cflags}
1d4c55
 Use additional compiler flags @var{cflags} to build the parts of the
1d4c55
 library which are always statically linked into applications and
1d4c55
diff --git a/sysdeps/unix/sysv/linux/ia64/Makefile b/sysdeps/unix/sysv/linux/ia64/Makefile
1d4c55
index 97fc7df0b122d6a0..b1ad1ab7b1efa34c 100644
1d4c55
--- a/sysdeps/unix/sysv/linux/ia64/Makefile
1d4c55
+++ b/sysdeps/unix/sysv/linux/ia64/Makefile
1d4c55
@@ -1,3 +1,9 @@
1d4c55
+ifeq ($(subdir),elf)
1d4c55
+# ia64 does not support PT_GNU_RELRO.
1d4c55
+test-xfail-tst-relro-ldso = yes
1d4c55
+test-xfail-tst-relro-libc = yes
1d4c55
+endif
1d4c55
+
1d4c55
 ifeq ($(subdir),misc)
1d4c55
 sysdep_headers += sys/rse.h
1d4c55
 endif