diff --git a/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/main.fmf b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/main.fmf new file mode 100644 index 0000000..160592a --- /dev/null +++ b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/main.fmf @@ -0,0 +1,9 @@ +summary: Avoid creating gaps between adjacent LOAD segments + +duration: 24h + +require+: + - rpm-build + +link+: + - verifies: https://issues.redhat.com/browse/RHEL-45873 diff --git a/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/repro.sh b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/repro.sh new file mode 100755 index 0000000..a0729b0 --- /dev/null +++ b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/repro.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env sh + +ITER=${1:-10000} + +echo "Testing with $(rpm -q binutils)..." + +pagesz=$(getconf PAGESIZE) +echo "Testing with a page size of $pagesz" + +GLIBC_SCRIPTS="$(readlink -f "$(rpm --define="_topdir $TmpDir" --eval=%_builddir)/glibc-*/scripts")" + +test() { + y=$(($1 * 7)) + f=obj$y + gcc -O2 -fpie -pie -DN=$y t.c -Wl,-z,relro,-z,now -o $f + + PYTHONPATH="$GLIBC_SCRIPTS:$PYTHONPATH" python tst-load-segment-gaps.py $f >> verbose.log + ret=$? + + rm $f + return $ret +} + +rm -f verbose.log + +cat > t.c < +#include +#include +#include +#include + +const char *const array[] = { + [N] = "value", +}; + +#define BUF_SZ 2048 + +void pmap() { + FILE *f; + size_t bytes; + char *buffer = malloc(BUF_SZ); + if (buffer == NULL) + err(EXIT_FAILURE, NULL); + + f = fopen("/proc/self/maps", "r"); + if (f == NULL) + err(EXIT_FAILURE, NULL); + + while ((bytes = fread(buffer, 1, BUF_SZ, f)) > 0) + fwrite(buffer, 1, bytes, stdout); + + if (ferror(f)) + perror(NULL); + + fclose(f); + free(buffer); +} + +int main (void) +{ + prctl(PR_SET_DUMPABLE, 0,0, 0); + char **volatile p = (char **)array; + pmap(); + __builtin_puts(p[N]); +} +EOF + +errors=0 + +# Print a blank line that we will rewrite later. +echo +for x in $(seq $ITER) ; do + p=$(($x * 100 / $ITER)) + echo -e '\e[1A\e[K Progress:' "$p% Errors: $errors" + if ! test $x; then + errors=$(($errors + 1)) + fi +done + +rm t.c + +if [[ "$errors" -eq 0 ]]; then + echo "The test succeeded!" +else + percent=$(($errors * 100 / $ITER)) + echo "Error: tests had a $percent% failure rate" >&2 + exit 1 +fi diff --git a/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/test.sh b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/test.sh new file mode 100755 index 0000000..7d1e734 --- /dev/null +++ b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/test.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Include Beaker environment +. /usr/share/beakerlib/beakerlib.sh || exit 1 + +. "$TMT_TREE/tests/lib.sh" || exit 1 + +rlJournalStart + rlPhaseStartSetup + buTestHeader + buEnterTmpDir + + buCopyReproducers "repro.sh tst-load-segment-gaps.py" + + buPrepareSRPM "glibc" + buConfigureSRPM "glibc" + rlRun "tree $TmpDir" + rlPhaseEnd + + rlPhaseStartTest + rlRun "./repro.sh" + rlPhaseEnd + + rlPhaseStartCleanup + buExitTmpDir + rlPhaseEnd +rlJournalPrintText +rlJournalEnd diff --git a/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/tst-load-segment-gaps.py b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/tst-load-segment-gaps.py new file mode 100644 index 0000000..85ae734 --- /dev/null +++ b/tests/bugs/Avoid-creating-gaps-between-adjacent-LOAD-segments/tst-load-segment-gaps.py @@ -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 +# . + +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) diff --git a/tests/lib.sh b/tests/lib.sh index 17bdf69..8fff10d 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -116,6 +116,17 @@ function buPrepareSRPM () { } +function buConfigureSRPM () { + if [ -n "$BU_AS_TEST_USER" ]; then + username="${1:-$BU_TEST_USER}" + + rlRun "su - $username -c 'rpmbuild -bp --define=\"_topdir $TmpDir\" $SPECFILE'" + else + rlRun " rpmbuild -bp --define=\"_topdir $TmpDir\" $SPECFILE" + fi +} + + function buBuildSRPM () { if [ -n "$BU_AS_TEST_USER" ]; then username="${1:-$BU_TEST_USER}"