mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-08 14:20:07 +00:00
elf: Handle ld.so with LOAD segment gaps in _dl_find_object (bug 31943)
This commit is contained in:
parent
9fc639f654
commit
a2f53a7755
@ -260,6 +260,9 @@
|
||||
any external dependencies such as making a function call. */
|
||||
#define HAVE_BUILTIN_TRAP 0
|
||||
|
||||
/* Define if ld may produce gaps in load segments. */
|
||||
#undef HAVE_LD_LOAD_GAPS
|
||||
|
||||
/* ports/sysdeps/mips/configure.in */
|
||||
/* Define if using the IEEE 754-2008 NaN encoding on the MIPS target. */
|
||||
#undef HAVE_MIPS_NAN2008
|
||||
|
@ -98,6 +98,7 @@ CXX = @CXX@
|
||||
BUILD_CC = @BUILD_CC@
|
||||
CFLAGS = @CFLAGS@
|
||||
CPPFLAGS-config = @CPPFLAGS@
|
||||
have-ld-load-gaps = @with_ld_load_gaps@
|
||||
extra-nonshared-cflags = @extra_nonshared_cflags@
|
||||
rtld-early-cflags = @rtld_early_cflags@
|
||||
ASFLAGS-config = @ASFLAGS_config@
|
||||
|
38
configure
vendored
38
configure
vendored
@ -610,6 +610,7 @@ PACKAGE_URL='https://www.gnu.org/software/glibc/'
|
||||
|
||||
ac_unique_file="include/features.h"
|
||||
enable_option_checking=no
|
||||
with_ld_load_gaps=check
|
||||
ac_subst_vars='LTLIBOBJS
|
||||
LIBOBJS
|
||||
pthread_in_libc
|
||||
@ -679,6 +680,7 @@ SED
|
||||
MAKEINFO
|
||||
MSGFMT
|
||||
MAKE
|
||||
with_ld_load_gaps
|
||||
LD
|
||||
NM
|
||||
OBJDUMP
|
||||
@ -807,6 +809,7 @@ enable_cet
|
||||
enable_scv
|
||||
enable_fortify_source
|
||||
with_cpu
|
||||
with_ld_load_gaps
|
||||
'
|
||||
ac_precious_vars='build_alias
|
||||
host_alias
|
||||
@ -1509,6 +1512,8 @@ Optional Packages:
|
||||
--with-timeoutfactor=NUM
|
||||
specify an integer to scale the timeout
|
||||
--with-cpu=CPU select code for CPU variant
|
||||
--with-ld-load-gaps support linker with LOAD segment gaps bug
|
||||
[default=check]
|
||||
|
||||
Some influential environment variables:
|
||||
CC C compiler command
|
||||
@ -5302,6 +5307,39 @@ esac
|
||||
config_vars="$config_vars
|
||||
with-lld = $libc_cv_with_lld"
|
||||
|
||||
|
||||
# Check whether --with-ld_load_gaps was given.
|
||||
if test ${with_ld_load_gaps+y}
|
||||
then :
|
||||
withval=$with_ld_load_gaps;
|
||||
else case e in #(
|
||||
e) : ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if test "x$with_ld_load_gaps" = xcheck
|
||||
then :
|
||||
echo "Checking binutils ld version:" >&5
|
||||
if LC_ALL=C $LD --version | grep -E '^GNU ld version 2\.(2[0-9]|3[0-8])[^0-9]' >&5
|
||||
then :
|
||||
with_ld_load_gaps=yes
|
||||
else case e in #(
|
||||
e) with_ld_load_gaps=no
|
||||
echo "(linker not binutils or not impacted)" >&5 ;;
|
||||
esac
|
||||
fi
|
||||
fi
|
||||
if test "x$with_ld_load_gaps" != xyes && test "x$with_ld_load_gaps" != xno
|
||||
then :
|
||||
as_fn_error $? "invalid --with-ld-load-gaps argument: $with_ld_load_gaps" "$LINENO" 5
|
||||
fi
|
||||
if test "x$with_ld_load_gaps" = xyes
|
||||
then :
|
||||
printf "%s\n" "#define HAVE_LD_LOAD_GAPS 1" >>confdefs.h
|
||||
|
||||
fi
|
||||
|
||||
|
||||
# These programs are version sensitive.
|
||||
for ac_prog in gnumake gmake make
|
||||
do
|
||||
|
20
configure.ac
20
configure.ac
@ -526,6 +526,26 @@ case $($LD --version) in
|
||||
esac
|
||||
LIBC_CONFIG_VAR([with-lld], [$libc_cv_with_lld])
|
||||
|
||||
dnl Workaround for binutils LOAD segment gaps bug (swbz#28743)
|
||||
dnl Fixed in commit 9833b7757d246f22db4eb24b8e5db7eb5e05b6d9
|
||||
dnl ("PR28824, relro security issues"), part of binutils 2.39.
|
||||
AC_ARG_WITH([ld_load_gaps],
|
||||
[AS_HELP_STRING([--with-ld-load-gaps],
|
||||
[support linker with LOAD segment gaps bug @<:@default=check@:>@])],
|
||||
[],
|
||||
[: m4_divert_text([DEFAULTS], [with_ld_load_gaps=check])])
|
||||
AS_IF([test "x$with_ld_load_gaps" = xcheck],
|
||||
[echo "Checking binutils ld version:" >&AS_MESSAGE_LOG_FD
|
||||
AS_IF([LC_ALL=C $LD --version | grep -E '^GNU ld version 2\.(2[[0-9]]|3[[0-8]])[[^0-9]]' >&AS_MESSAGE_LOG_FD],
|
||||
[with_ld_load_gaps=yes],
|
||||
[with_ld_load_gaps=no
|
||||
echo "(linker not binutils or not impacted)" >&AS_MESSAGE_LOG_FD])])
|
||||
AS_IF([test "x$with_ld_load_gaps" != xyes && test "x$with_ld_load_gaps" != xno],
|
||||
AC_MSG_ERROR([invalid --with-ld-load-gaps argument: $with_ld_load_gaps]))
|
||||
AS_IF([test "x$with_ld_load_gaps" = xyes],
|
||||
[AC_DEFINE(HAVE_LD_LOAD_GAPS)])
|
||||
AC_SUBST(with_ld_load_gaps)
|
||||
|
||||
# These programs are version sensitive.
|
||||
AC_CHECK_PROG_VER(MAKE, gnumake gmake make, --version,
|
||||
[GNU Make[^0-9]*\([0-9][0-9.]*\)],
|
||||
|
@ -64,8 +64,8 @@ do_test (void)
|
||||
|
||||
do
|
||||
{
|
||||
printf ("info: checking link map %p (%p) for \"%s\"\n",
|
||||
l, l->l_phdr, l->l_name);
|
||||
printf ("info: checking link map %p (%p %p) for \"%s\"\n",
|
||||
l, l->l_phdr, (void *) l->l_addr, l->l_name);
|
||||
|
||||
/* Cause dlerror () to return an error message. */
|
||||
dlsym (RTLD_DEFAULT, "does-not-exist");
|
||||
|
@ -615,6 +615,14 @@ $(objpfx)tst-relro-libc.out: tst-relro-symbols.py $(..)/scripts/glibcelf.py \
|
||||
--required=_IO_wfile_jumps \
|
||||
--required=__io_vtables \
|
||||
> $@ 2>&1; $(evaluate-test)
|
||||
tests-special += $(objpfx)tst-gaps-ldso.out
|
||||
$(objpfx)tst-gaps-ldso.out: tst-load-segment-gaps.py \
|
||||
$(..)/scripts/glibcelf.py $(objpfx)ld.so
|
||||
$(PYTHON) tst-load-segment-gaps.py $(objpfx)ld.so \
|
||||
> $@ 2>&1; $(evaluate-test)
|
||||
ifeq ($(have-ld-load-gaps),yes)
|
||||
test-xfail-tst-gaps-ldso = yes
|
||||
endif
|
||||
|
||||
ifeq ($(run-built-tests),yes)
|
||||
tests-special += $(objpfx)tst-valgrind-smoke.out
|
||||
|
@ -466,6 +466,38 @@ __dl_find_object (void *pc1, struct dl_find_object *result)
|
||||
hidden_def (__dl_find_object)
|
||||
weak_alias (__dl_find_object, _dl_find_object)
|
||||
|
||||
/* Subroutine of _dlfo_process_initial to split out noncontigous link
|
||||
maps. NODELETE is the number of used _dlfo_nodelete_mappings
|
||||
elements. It is incremented as needed, and the new NODELETE value
|
||||
is returned. */
|
||||
static size_t
|
||||
_dlfo_process_initial_noncontiguous_map (struct link_map *map,
|
||||
size_t nodelete)
|
||||
{
|
||||
struct dl_find_object_internal dlfo;
|
||||
_dl_find_object_from_map (map, &dlfo);
|
||||
|
||||
/* PT_LOAD segments for a non-contiguous link map are added to the
|
||||
non-closeable mappings. */
|
||||
const ElfW(Phdr) *ph = map->l_phdr;
|
||||
const ElfW(Phdr) *ph_end = map->l_phdr + map->l_phnum;
|
||||
for (; ph < ph_end; ++ph)
|
||||
if (ph->p_type == PT_LOAD)
|
||||
{
|
||||
if (_dlfo_nodelete_mappings != NULL)
|
||||
{
|
||||
/* Second pass only. */
|
||||
_dlfo_nodelete_mappings[nodelete] = dlfo;
|
||||
_dlfo_nodelete_mappings[nodelete].map_start
|
||||
= ph->p_vaddr + map->l_addr;
|
||||
_dlfo_nodelete_mappings[nodelete].map_end
|
||||
= _dlfo_nodelete_mappings[nodelete].map_start + ph->p_memsz;
|
||||
}
|
||||
++nodelete;
|
||||
}
|
||||
return nodelete;
|
||||
}
|
||||
|
||||
/* _dlfo_process_initial is called twice. First to compute the array
|
||||
sizes from the initial loaded mappings. Second to fill in the
|
||||
bases and infos arrays with the (still unsorted) data. Returns the
|
||||
@ -477,29 +509,8 @@ _dlfo_process_initial (void)
|
||||
|
||||
size_t nodelete = 0;
|
||||
if (!main_map->l_contiguous)
|
||||
{
|
||||
struct dl_find_object_internal dlfo;
|
||||
_dl_find_object_from_map (main_map, &dlfo);
|
||||
|
||||
/* PT_LOAD segments for a non-contiguous are added to the
|
||||
non-closeable mappings. */
|
||||
for (const ElfW(Phdr) *ph = main_map->l_phdr,
|
||||
*ph_end = main_map->l_phdr + main_map->l_phnum;
|
||||
ph < ph_end; ++ph)
|
||||
if (ph->p_type == PT_LOAD)
|
||||
{
|
||||
if (_dlfo_nodelete_mappings != NULL)
|
||||
{
|
||||
/* Second pass only. */
|
||||
_dlfo_nodelete_mappings[nodelete] = dlfo;
|
||||
_dlfo_nodelete_mappings[nodelete].map_start
|
||||
= ph->p_vaddr + main_map->l_addr;
|
||||
_dlfo_nodelete_mappings[nodelete].map_end
|
||||
= _dlfo_nodelete_mappings[nodelete].map_start + ph->p_memsz;
|
||||
}
|
||||
++nodelete;
|
||||
}
|
||||
}
|
||||
/* Contiguous case already handled in _dl_find_object_init. */
|
||||
nodelete = _dlfo_process_initial_noncontiguous_map (main_map, nodelete);
|
||||
|
||||
size_t loaded = 0;
|
||||
for (Lmid_t ns = 0; ns < GL(dl_nns); ++ns)
|
||||
@ -511,11 +522,20 @@ _dlfo_process_initial (void)
|
||||
/* lt_library link maps are implicitly NODELETE. */
|
||||
if (l->l_type == lt_library || l->l_nodelete_active)
|
||||
{
|
||||
if (_dlfo_nodelete_mappings != NULL)
|
||||
/* Second pass only. */
|
||||
_dl_find_object_from_map
|
||||
(l, _dlfo_nodelete_mappings + nodelete);
|
||||
++nodelete;
|
||||
#if defined HAVE_LD_LOAD_GAPS && defined SHARED
|
||||
/* The kernel may have loaded ld.so with gaps. */
|
||||
if (!l->l_contiguous && l == &GL(dl_rtld_map))
|
||||
nodelete
|
||||
= _dlfo_process_initial_noncontiguous_map (l, nodelete);
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (_dlfo_nodelete_mappings != NULL)
|
||||
/* Second pass only. */
|
||||
_dl_find_object_from_map
|
||||
(l, _dlfo_nodelete_mappings + nodelete);
|
||||
++nodelete;
|
||||
}
|
||||
}
|
||||
else if (l->l_type == lt_loaded)
|
||||
{
|
||||
|
24
elf/rtld.c
24
elf/rtld.c
@ -1766,6 +1766,30 @@ dl_main (const ElfW(Phdr) *phdr,
|
||||
|
||||
GL(dl_rtld_map).l_phdr = rtld_phdr;
|
||||
GL(dl_rtld_map).l_phnum = rtld_ehdr->e_phnum;
|
||||
GL(dl_rtld_map).l_contiguous = 1;
|
||||
#ifdef HAVE_LD_LOAD_GAPS
|
||||
/* The linker may not have produced a contiguous object. The kernel
|
||||
will load the object with actual gaps (unlike the glibc loader
|
||||
for shared objects, which always produces a contiguous mapping).
|
||||
See similar logic in rtld_setup_main_map. */
|
||||
{
|
||||
ElfW(Addr) expected_load_address = 0;
|
||||
for (const ElfW(Phdr) *ph = rtld_phdr; ph < &phdr[rtld_ehdr->e_phnum];
|
||||
++ph)
|
||||
if (ph->p_type == PT_LOAD)
|
||||
{
|
||||
ElfW(Addr) mapstart = ph->p_vaddr & ~(GLRO(dl_pagesize) - 1);
|
||||
if (GL(dl_rtld_map).l_contiguous && expected_load_address != 0
|
||||
&& expected_load_address != mapstart)
|
||||
GL(dl_rtld_map).l_contiguous = 0;
|
||||
ElfW(Addr) allocend = ph->p_vaddr + ph->p_memsz;
|
||||
/* The next expected address is the page following this load
|
||||
segment. */
|
||||
expected_load_address = ((allocend + GLRO(dl_pagesize) - 1)
|
||||
& ~(GLRO(dl_pagesize) - 1));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* PT_GNU_RELRO is usually the last phdr. */
|
||||
|
74
elf/tst-load-segment-gaps.py
Normal file
74
elf/tst-load-segment-gaps.py
Normal file
@ -0,0 +1,74 @@
|
||||
#!/usr/bin/python3
|
||||
# Verify that objects do not contain gaps in load segments.
|
||||
# Copyright (C) 2024 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/>.
|
||||
|
||||
import argparse
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
# Make available glibc Python modules.
|
||||
sys.path.append(os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), os.path.pardir, 'scripts'))
|
||||
|
||||
import glibcelf
|
||||
|
||||
def rounddown(val, align):
|
||||
assert (align & (align - 1)) == 0, align
|
||||
return val & -align
|
||||
def roundup(val, align):
|
||||
assert (align & (align - 1)) == 0, align
|
||||
return (val + align - 1) & -align
|
||||
|
||||
errors = False
|
||||
|
||||
def process(path, img):
|
||||
global errors
|
||||
loads = [phdr for phdr in img.phdrs()
|
||||
if phdr.p_type == glibcelf.Pt.PT_LOAD]
|
||||
if not loads:
|
||||
# Nothing ot check.
|
||||
return
|
||||
alignments = [phdr.p_align for phdr in loads if phdr.p_align > 0]
|
||||
if alignments:
|
||||
align = min(alignments)
|
||||
else:
|
||||
print('error: cannot infer page size')
|
||||
errors = True
|
||||
align = 4096
|
||||
print('info: inferred page size:', align)
|
||||
current_address = None
|
||||
for idx, phdr in enumerate(loads):
|
||||
this_address = rounddown(phdr.p_vaddr, align)
|
||||
next_address = roundup(phdr.p_vaddr + phdr.p_memsz, align)
|
||||
print('info: LOAD segment {}: address 0x{:x}, size {},'
|
||||
' range [0x{:x},0x{:x})'
|
||||
.format(idx, phdr.p_vaddr, phdr.p_memsz,
|
||||
this_address, next_address))
|
||||
if current_address is not None:
|
||||
gap = this_address - current_address
|
||||
if gap != 0:
|
||||
errors = True
|
||||
print('error: gap between load segments: {} bytes'.format(gap))
|
||||
current_address = next_address
|
||||
|
||||
for path in sys.argv[1:]:
|
||||
img = glibcelf.Image.readfile(path)
|
||||
process(path, img)
|
||||
|
||||
if errors:
|
||||
sys.exit(1)
|
Loading…
Reference in New Issue
Block a user