Bug 397083

Summary: Likely false positive "uninitialised value(s)" for __wmemchr_avx2 and __wmemcmp_avx2_movbe
Product: [Developer tools] valgrind Reporter: Lars Winterfeld <lars.winterfeld>
Component: memcheckAssignee: Julian Seward <jseward>
Status: RESOLVED FIXED    
Severity: normal CC: bruno, florent, mark, pjfloyd, sam, takimoto-j
Priority: NOR    
Version First Reported In: 3.18.1   
Target Milestone: ---   
Platform: Ubuntu   
OS: Linux   
Latest Commit: Version Fixed In:
Sentry Crash Report:
Attachments: source code to reproduce error
Valgrind output.
first stab

Description Lars Winterfeld 2018-08-02 08:50:41 UTC
Created attachment 114264 [details]
source code to reproduce error

A C++ code like
  wstring w = L"x";
  size_t p = w.find(w);

causes valgrind to output warnings:

Conditional jump or move depends on uninitialised value(s)
  at 0x55588F7: __wmemchr_avx2 (memchr-avx2.S:250)

Use of uninitialised value of size 8
  at 0x55584C2: __wmemcmp_avx2_movbe (memcmp-avx2-movbe.S:171)

I found the similar (but not identical) bug report #307828.

Some one here says it's the fault of valgrind, not the c++ standard library: https://sourceware.org/bugzilla/show_bug.cgi?id=22954
I'm not fit enough in assembler to judge whether boundary checks are performed correctly or if indeed too much memory is read (in order to do avx2 instruction with many bytes at once?).

Under Ubuntu, I have libstdc++6:amd64 version 7.2.0-8ubuntu3.2, "GNU Standard C++ Library v3".

Steps to reproduce:
  Find attached testw.cpp
  g++ notused/testw.cpp -o testw && valgrind ./testw

Notice how for some string lengths no error is reported.
Comment 1 Lars Winterfeld 2018-08-02 08:52:41 UTC
Created attachment 114265 [details]
Valgrind output.
Comment 2 Bruno Haible 2023-03-16 21:11:29 UTC
I'm still seeing this on Ubuntu 22.04, with valgrind 3.18.1.
===================== foo.c ========================
#include <stdlib.h>
#include <wchar.h>

int main ()
{
  wchar_t *s = (wchar_t *) malloc (8 * sizeof (wchar_t));
  s[0] = '-';
  s[1] = 'N';
  s[2] = 'A';
  s[3] = 'N';
  s[4] = ' ';
  s[5] = '3';
  s[6] = '3';
  s[7] = '\0';
  return wmemcmp (s + 1, L"NAN", 3) == 0;
}
===================================================
$ gcc -ggdb foo.c
$ valgrind ./a.out
==2109475== Memcheck, a memory error detector
==2109475== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2109475== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2109475== Command: ./a.out
==2109475== 
==2109475== Invalid read of size 32
==2109475==    at 0x4A1C55D: __wmemcmp_avx2_movbe (memcmp-avx2-movbe.S:413)
==2109475==    by 0x10920D: main (foo.c:15)
==2109475==  Address 0x4aa6044 is 4 bytes inside a block of size 32 alloc'd
==2109475==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==2109475==    by 0x10917E: main (foo.c:6)
==2109475== 
...
Comment 3 Paul Floyd 2023-04-14 05:57:05 UTC
Should be a simple fix. I’ll have a go tonight or over the weekend.
Comment 4 Paul Floyd 2023-04-14 06:24:27 UTC
A bit more to it than I'd hoped - we don't have a wmemcmp wrapper yet.
Comment 5 Paul Floyd 2023-04-14 19:45:05 UTC
Created attachment 158110 [details]
first stab

Not tested yet
Comment 6 Paul Floyd 2023-04-15 13:24:17 UTC
On gcc14 (Debian bookworm) doesn't generate errors with the testw example

The C example calling wmemcmp directly produces this

==660430== Invalid read of size 32
==660430==    at 0x49BD53D: __wmemcmp_avx2_movbe (memcmp-avx2-movbe.S:415)
==660430==    by 0x1091E9: main (wmemcmp.c:15)
==660430==  Address 0x4a47044 is 4 bytes inside a block of size 32 alloc'd
==660430==    at 0x48407B4: malloc (vg_replace_malloc.c:381)
==660430==    by 0x10915A: main (wmemcmp.c:6)

No error with the patch
Comment 7 Bruno Haible 2023-04-19 01:28:13 UTC
(In reply to Paul Floyd from comment #5)
> Created attachment 158110 [details]

There's a small issue here: ISO C allows all possible wchar_t values to occur in the arrays passed to wmemcmp(), and requires that the comparison be done w.r.t. the order in the underlying integer type 'wchar_t'. For more on this topic, please see https://www.openwall.com/lists/musl/2023/04/18/5 and https://lists.gnu.org/archive/html/bug-gnulib/2023-04/msg00145.html .

Regarding your patch, this means that the line
         Int res = a0 - b0; \
should be replaced with something that does not assume that a0 and b0 are in the range 0..INT_MAX. For example,
         Int res = (a0 > b0) - (a0 < b0);
(This expression on the right-hand side has the noce property that it does not produce conditional jumps with GCC. See https://lists.gnu.org/archive/html/bug-gnulib/2020-07/msg00109.html .)

Additionally, watch out: wchar_t is signed (i.e. equivalent to int32_t) on most platforms. However it is unsigned (i.e. equivalent to uint32_t) on arm64 platforms (on glibc, musl libc, FreeBSD, but not on macOS).
Comment 8 Paul Floyd 2023-04-19 06:48:12 UTC
It was just a copy and quick adaptation of the memchr representation. There are other issues - the byte count isn't right either.

There's a lot less benefit to using a loop of UWord steps since UWord is only 2x the size of wchar_t (whilst for memchr it is 8x the size of char).

Looking at a couple of libc implementations, FreeBSD keeps it very simple, no real effort to optimize.

wmemcmp(const wchar_t *s1, const wchar_t *s2, size_t n)
{
        size_t i;

        for (i = 0; i < n; i++) {
                if (*s1 != *s2) {
                        /* wchar might be unsigned */
                        return *s1 > *s2 ? 1 : -1;
                }
                s1++;
                s2++;
        }
        return 0;
}

GNU libc does try harder - though that also has to work on Windows where wchar_t is 16bits where I expect there will be more benefit.

Code is a bit longer so here's the link. In short, an unrolled loop doing 4 whar_ts at a time followed by 3 ifs to handle the remainder.

https://elixir.bootlin.com/glibc/glibc-2.37/source/wcsmbs/wmemcmp.c#L25
Comment 9 Paul Floyd 2023-04-20 10:15:15 UTC
On RHEL 7.9 I just see

00000000000a8c40 T wmemchr

but for wmemcmp

00000000000b44b0 t __wmemcmp_sse2
000000000016b820 t __wmemcmp_sse4_1
000000000016f280 t __wmemcmp_ssse3
00000000000a8cc0 i wmemcmp

On RHEL 9.1 that becomes

00000000000f77d0 i __wmemchr
00000000000c0f80 t __wmemchr_avx2
00000000000c98e0 t __wmemchr_avx2_rtm
00000000000d2740 t __wmemchr_evex
00000000000d3240 t __wmemchr_evex_rtm
00000000000f77d0 t __wmemchr_ifunc
000000000003dfc0 t __wmemchr_sse2
00000000000f77d0 i wmemchr

and

00000000000f7860 i __wmemcmp
00000000000c1240 t __wmemcmp_avx2_movbe
00000000000c9bc0 t __wmemcmp_avx2_movbe_rtm
00000000000d2a40 t __wmemcmp_evex_movbe
00000000000f7860 t __wmemcmp_ifunc
00000000001075d0 t __wmemcmp_sse2
00000000000d4fe0 t __wmemcmp_ssse3
00000000000f7860 i wmemcmp

(ignoring a few GI versions)
Comment 10 Mark Wielaard 2023-04-20 18:41:38 UTC
I can replicate the issue with the C example, but not the C++ one on gcc-12.2.1-4.fc37.x86_64 glibc-2.36-9.fc37.x86_64
Comment 11 Mark Wielaard 2023-04-20 18:52:31 UTC
It looks like the original c++ issue (which called down to glibc wmemchr) has been fixed by bug #388862 in 3.14.0 (the reporter used 3.13.0). I think just intercepting the generic wmemchr (ifunc) is enough, you don't have to also intercept the internal variants.
Comment 12 Mark Wielaard 2023-04-20 18:54:33 UTC
(In reply to Mark Wielaard from comment #11)
> It looks like the original c++ issue (which called down to glibc wmemchr)
> has been fixed by bug #388862 in 3.14.0 (the reporter used 3.13.0). I think
> just intercepting the generic wmemchr (ifunc) is enough, you don't have to
> also intercept the internal variants.

And I think the same holds for wmemcmp. So you should just have to intercept the main wmemcmp function, not any of its variants (unless those are somehow internally called directly instead of going through the ifunc).
Comment 13 Paul Floyd 2023-04-20 20:41:58 UTC
I left wmemcmp alone and just added a simple wmemchr wrapper, Linux only.

commit a2af9adec4536c3ce95d4ea5ddf15d00d7c55c6c (HEAD -> master, origin/master, origin/HEAD, wmemcmp)
Author: Paul Floyd <pjfloyd@wanadoo.fr>
Date:   Thu Apr 20 22:11:31 2023 +0200

    Bug 397083 - Likely false positive "uninitialised value(s)" for __wmemchr_avx2 and __wmemcmp_avx2_movbe
Comment 14 Bruno Haible 2023-04-20 20:52:25 UTC
(In reply to Paul Floyd from comment #13)
> I left wmemcmp alone and just added a simple wmemchr wrapper, Linux only.

Actually, looking at your commit, you left wmemchr alone and added a simple wmemcmp wrapper.

Thanks! That should solve my issue.
Comment 15 Paul Floyd 2023-11-16 16:13:10 UTC
I think this got fixed back in April 2023
Comment 16 Paul Floyd 2024-04-11 18:24:01 UTC
*** Bug 452072 has been marked as a duplicate of this bug. ***