#1100805 gcc-14 version 14.2.0-18 causes glibc to be miscompiled on armhf

Package:
gcc-14
Source:
gcc-14
Description:
GNU C compiler
Submitter:
Aurelien Jarno
Date:
2025-04-09 10:57:02 UTC
Severity:
normal
#1100805#5
Date:
2025-03-18 19:56:30 UTC
From:
To:
Hi,

When glibc is compiled with gcc-14 >= 14.2.0-18, the libinsane
autopkgtest fails on armhf [1]:

| 207s autopkgtest [07:54:23]: test workaround-one-page-flatbed: [-----------------------
| 207s Building
| 208s Reducing file descriptors limit
| 208s Running test with valgrind
| 208s ==6914== Memcheck, a memory error detector
| 208s ==6914== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
| 208s ==6914== Using Valgrind-3.24.0 and LibVEX; rerun with -h for copyright info
| 208s ==6914== Command: /tmp/autopkgtest-lxc.6v7pbw_c/downtmp/autopkgtest_tmp/tests_workaround_one_page_flatbed
| 208s ==6914==
| 208s
| 208s
| 208s      CUnit - A unit testing framework for C - Version 2.1-3
| 208s      http://cunit.sourceforge.net/
| 208s
| 208s
| 208s Suite: Workaround_one_page_flatbed
| 209s   Test: tests_one_page_flatbed() ...==6914== Source and destination overlap in memcpy(0x6debcf0, 0x6debcf1, 5)
| 209s ==6914==    at 0x4881648: memcpy (in /usr/libexec/valgrind/vgpreload_memcheck-arm-linux.so)
| 209s ==6914==
| 209s passed
| 209s
| 209s Run Summary:    Type  Total    Ran Passed Failed Inactive
| 209s               suites      1      1    n/a      0        0
| 209s                tests      1      1      1      0        0
| 209s              asserts     30     30     30      0      n/a
| 209s
| 209s Elapsed time =    0.111 seconds
| 209s ==6914==
| 209s ==6914== HEAP SUMMARY:
| 209s ==6914==     in use at exit: 0 bytes in 0 blocks
| 209s ==6914==   total heap usage: 292 allocs, 292 frees, 41,951 bytes allocated
| 209s ==6914==
| 209s ==6914== All heap blocks were freed -- no leaks are possible
| 209s ==6914==
| 209s ==6914== For lists of detected and suppressed errors, rerun with: -s
| 209s ==6914== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 1021 from 69)
| 209s autopkgtest [07:54:25]: test workaround-one-page-flatbed: -----------------------]

This is fully reproducible when glibc 2.41-6 is built with gcc-14
14.2.0-18 or 14.2.0-19. On the contrary the problem disappears when
rebuilding glibc 2.41-6 with gcc-14 14.2.0-17 currently in testing. In
turn this prevent glibc 2.41-6 to migrate to testing.

Please revert.

Regards
Aurelien

[1] https://ci.debian.net/packages/libi/libinsane/testing/armhf/58878994/#S57

#1100805#20
Date:
2025-03-19 06:32:54 UTC
From:
To:
this is not seen with a glibc-2.41-1 built with gcc-14 14.2.0-18.

No, I'm not suggesting to revert to that version, like you do for
gcc-14, but to investigate the issue.

#1100805#27
Date:
2025-03-19 10:42:17 UTC
From:
To:
14.2.0-18 doesn't show that behavior. -19 just addresses one Ada issue
and two C++ issues.

Looking at the failing autopkg test: It passes when not run under
valgrind (on amdahl).  Are there glibc changes in glibc, which could
cause valgrind to fail?

I also see that valgrind has not been rebuilt for the new glibc version.
Is this necessary?

#1100805#32
Date:
2025-03-19 20:45:09 UTC
From:
To:
This does not match my tests. I have tried a second time after you
mentioned you can't reproduce it with glibc 2.41-1 in the previous
message and I *do* reproduce the issue with glibc 2.41-1, 2.41-4,
2.41-6.

I confirm that the autopkg test only fails when run under valgrind. It
could mean that the glibc breakage affects valgrind and not the tested
package.

There are absolutely no change in glibc. You can take glibc version
2.41-1, 2.41-4, or 2.41-6, build it with gcc-14 14.2.0-17, the
autopkgtest passes. Build it with 14.2.0-18, the autopkgtest fails.

I do not see why it would be necessary. Anyway I just tried (after
patching it to workaround the unrelated FTBFS failure) and that doesn't
change anything.

The bug is very difficult to debug due to valgrind being involved.
Despite many tries I haven't been able to reproduce it outside of
valgrind. The problem disappears with gcc-13 13.3.0-13, but is still
there with gcc-15 15-20250315-1.

So far my finding are quite limited. It seems the new GCC generates more
optimized code in general, and especially for the memmem() function, and
that changes the location of the memmove() function located just after
in the binary. Aligning it to 6 bits seems to fix the problem with -18
(it is aligned to 5 bits), but strangely it is only aligned to 4 bits
with -17... Also padding it with the same number of nops at the
beginning of the function doesn't help, so this somehow excludes
alignment issues of functions located after it. Overall it seems that
memmove() wrongly jumps to memcpy() for cases it shouldn't, but I don't
really see why and while alignment would matter.

I am puzzled by all of that I and don't really know how to debug that
further.

Regards
Aurelien

#1100805#37
Date:
2025-03-20 10:18:12 UTC
From:
To:
describe. My uninformed impression is that upstream support for armhf
isn't great. I did once open an issue asking to support the latest
(2018) Procedure Call Standard and received no replies, which makes me
wonder if there's much upstream interest at all when it comes to 32bit
arm: https://bugs.kde.org/show_bug.cgi?id=479699

On that topic, just as a sanity check I tried rebuilding libinsane
without stack-clash-protection and still could reproduce #1100805.

Here's my 2 cents: if there's no obvious compiler or C library change
that may suggest a regression was introduced, just stop running valgrind
in the libinsane autopkgtest on armhf and move on.

#1100805#42
Date:
2025-03-20 17:29:08 UTC
From:
To:
control: clone -1 -2
control: reassign -2 src:libinsane
control: retitle -2 libinsane: please disable valgrind in armhf autopkgtest
control: reassign -1 valgrind
control: retitle -1 valgrind: false memory overlap positive in memcpy on armhf
control: severity -1 important

Hi,
positive and a check done randomly depending on the code alignment.

On the GCC side, I have found that the issue is trigger by this commit:

commit 95c98c5368aedf2a482bf551cd2573c1961a6823
Author: Richard Biener <rguenther@suse.de>
Date:   Wed Jan 15 14:31:57 2025 +0100

     tree-optimization/115494 - PRE PHI translation and ranges

It significantly changes the generated code, and it seems more compact
overall. This is especially true for the memset function, which in
turns move the location of the memmove function in the binary. This is
the part I just do not understand, but the behaviour of this code seems
to depends on its location. Aligning it to 6 bits fixes the issue, but
padding the beginning of the function with the equivalent number of nops
doesn't.

Said otherwise, this does trigger the issue:

00071560 <memmove@@GLIBC_2.4>:
    71560:       e320f000        nop     {0}
    71564:       e320f000        nop     {0}
    71568:       e320f000        nop     {0}
    7156c:       e320f000        nop     {0}
    71570:       e320f000        nop     {0}
    71574:       e320f000        nop     {0}
    71578:       e320f000        nop     {0}
    7157c:       e320f000        nop     {0}
    71580:       e050c001        subs    ip, r0, r1
    71584:       8152000c        cmphi   r2, ip
    71588:       9afeb686        bls     1efa8 <_dl_audit_preinit@plt+0x20>
    7158c:       e92d4011        push    {r0, r4, lr}

This doesn't trigger the issue:

00071580 <memmove@@GLIBC_2.4>:
    71580:       e050c001        subs    ip, r0, r1
    71584:       8152000c        cmphi   r2, ip
    71588:       9afeb686        bls     1efa8 <_dl_audit_preinit@plt+0x20>
    7158c:       e92d4011        push    {r0, r4, lr}

Besides the change of the memmove address, libc.so.6 binaries with
memmove padded or aligned are identical. Using GDB with valgrind and
adding a breakpoint on memmove, it appears that the program just doesn't
stop. This also appears with the small reproducer below. I attribute
this to a valgrind bug, although it's just a guess.

Now about the problem reported by valgrind, this is the call stack
  at 0x4881648: memcpy (vg_replace_strmem.c:1150)
  by 0x495823B: memmove (string_fortified.h:36)
  by 0x495823B: re_string_reconstruct (regex_internal.c:685)
  by 0x495C69B: re_search_internal (regexec.c:778)
  by 0x49606D1: regexec@@GLIBC_2.4 (regexec.c:216)
  by 0x48AAE17: item_filter (source_types.c:91)
  by 0x48A4C8F: lis_bw_item_get_children (basewrapper.c:392)
  by 0x48A4BF3: lis_bw_item_get_children (basewrapper.c:360)
  by 0x108D17: tests_one (in /tmp/autopkgtest/tests_workaround_one_page_flatbed)

In short memmove is called by internal GNU libc code, more precisely the
regex related functions that are called from the libinsane library. The
arm specific memmove assembly code first checks if memcpy can be called
to do the job:

ENTRY(memmove)
                 subs    ip, r0, r1
                 cmphi   r2, ip
#if !IS_IN (libc)
                 bls     memcpy
#else
                 bls     HIDDEN_JUMPTARGET(memcpy)
#endif

This happens in two cases, if forward copy can be used (i.e. dst <= src)
or if the memory addresses do not overlap. This basically relies on the
fact that all the arm memcpy implementations allow overlap in case of
forward copy. Note that the call to memcpy is done through the PLT, and
that different implementations are provided through the IFUNC mechanism.
On Debian armhf this default to the VFP implementation, but the NEON
implementation can be selected if the CPU supports it. This also means
that valgrind will intercept those calls to memcpy to check for overlap,
as if it was called directly by user code. In this specific case this is
perfectly fine, but valgrind believes the contrary. This is a small
reproducer:

#include <string.h>
#include <stdio.h>

int main(int, char **)
{
     char buffer[32] = " -- Hello world -- ";

     printf("%s\n", buffer);
     memmove(buffer, buffer + 4, strlen(buffer) - 4 + 1);
     printf("%s\n", buffer);
}

Other architectures are not affected because their memmove and memcpy
implementations do not call each other, and usually generate both
memmove and memcpy functions from the same source code, using
preprocessor directives to handle the differences. The arm memmove and
memcpy assembly codes in glibc would probably benefit from a rewrite, but
nowadays nobody is really interested by arm32 anymore.

Anyway this is therefore not a GCC nor a GLIBC issue, but rather a false
positive from valgrind. One option is to add a corresponding suppression
entry in the default suppression file. The other alternative is to
disable valgrind testing in libinsane on armhf, with the risk that it
pops up at a later point on another package.

Given the state valgrind, especially on 32-bit architectures following
the removal of OpenMPI, it's probably better to handle that at the
libinsane level, removing the valgrind from the autopkgtest on armhf. I
am therefore reassigning the bug there. Neverthless I am cloning also it
to valgrind to avoid having the issue poping up on another package at a
later point.

Regards
Aurelien

#1100805#57
Date:
2025-03-20 21:48:17 UTC
From:
To:
control: tag -1 + patch

Hi Thomas,

Sorry for the cloned bug with a lot of history. Just to make things
clear what has to be done on the libinsane side is just as simple as the
following patch:
--- libinsane-1.0.10/debian/tests/control +++ libinsane-1.0.10/debian/tests/control @@ -30,5 +30,5 @@ workaround-lamp, workaround-hide-source-auto, workaround-invalid-page-size, -Depends: @, build-essential, libcunit1-dev, valgrind [amd64 arm64 armhf i386 mips mips64 mips64el mipsel powerpc ppc64 ppc64el s390x] +Depends: @, build-essential, libcunit1-dev, valgrind [amd64 arm64 i386 mips mips64 mips64el mipsel powerpc ppc64 ppc64el s390x] Restrictions: allow-stderr I am available in case you need more details or are unable to prepare an upload. Regards Aurelien