Repository: bitcoin/bitcoin (language: cpp)

Issue:
fuzz: minisketch: Undefined-shift in std::__1::vector<Field<unsigned int, 32, 141u, RecLinTrans<unsigned int, 6, 6, 5
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current behaviour

Undefined-shift

### Expected behaviour

no Undefined-shift

### Steps to reproduce

* Compile fuzz targets with `./configure CC=clang CXX=clang++ --enable-fuzz --with-sanitizers=fuzzer,undefined`
* Create crash input: `echo 'Av////////////8gICD///8gIP8g/yAg/yA=' | base64 --decode > /tmp/crash.bin`
* Run Fuzz target: `FUZZ=minisketch ./src/test/fuzz/fuzz /tmp/crash.bin`

### Relevant log output

```
# FUZZ=minisketch ./src/test/fuzz/fuzz /tmp/crash.bin 
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 1866818617
INFO: Loaded 1 modules   (424177 inline 8-bit counters): 424177 [0x55d4679ea4d0, 0x55d467a51dc1), 
INFO: Loaded 1 PC tables (424177 PCs): 424177 [0x55d467a51dc8,0x55d4680cacd8), 
./src/test/fuzz/fuzz: Running 1 inputs 1 time(s) each.
Running: /tmp/crash.bin
minisketch/src/fields/../int_utils.h:179:69: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior minisketch/src/fields/../int_utils.h:179:69 in 
minisketch/src/fields/../int_utils.h:180:74: runtime error: shift exponent 32 is too large for 32-bit type 'unsigned int'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior minisketch/src/fields/../int_utils.h:180:74 in 
Executed /tmp/crash.bin in 1 ms
***
*** NOTE: fuzzing was not performed, you have only
***       executed the target code on a fixed set of inputs.
***
```

### How did you obtain Bitcoin Core

Compiled from source

### What version of Bitcoin Core are you using?

master

### Operating system and version

Linux

### Machine specifications

_No response_


Discussion / hints:
Ref https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=67799
See https://github.com/sipa/minisketch/pull/81 for a fix.

Fix patch:
diff --git a/configure.ac b/configure.ac
index 63a012b35df49..febb352cdbe55 100644
--- a/configure.ac
+++ b/configure.ac
@@ -968,22 +968,6 @@ AC_CHECK_DECLS([setsid])
 
 AC_CHECK_DECLS([pipe2])
 
-AC_MSG_CHECKING([for __builtin_clzl])
-AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]], [[
- (void) __builtin_clzl(0);
-  ]])],
- [ AC_MSG_RESULT([yes]); have_clzl=yes; AC_DEFINE([HAVE_BUILTIN_CLZL], [1], [Define this symbol if you have __builtin_clzl])],
- [ AC_MSG_RESULT([no]); have_clzl=no;]
-)
-
-AC_MSG_CHECKING([for __builtin_clzll])
-AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ ]], [[
-  (void) __builtin_clzll(0);
-  ]])],
- [ AC_MSG_RESULT([yes]); have_clzll=yes; AC_DEFINE([HAVE_BUILTIN_CLZLL], [1], [Define this symbol if you have __builtin_clzll])],
- [ AC_MSG_RESULT([no]); have_clzll=no;]
-)
-
 dnl Check for malloc_info (for memory statistics information in getmemoryinfo)
 AC_MSG_CHECKING([for getmemoryinfo])
 AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <malloc.h>]],
@@ -1736,7 +1720,6 @@ AM_CONDITIONAL([USE_UPNP], [test "$use_upnp" = "yes"])
 
 dnl for minisketch
 AM_CONDITIONAL([ENABLE_CLMUL], [test "$enable_clmul" = "yes"])
-AM_CONDITIONAL([HAVE_CLZ], [test "$have_clzl$have_clzll" = "yesyes"])
 
 AC_DEFINE([CLIENT_VERSION_MAJOR], [_CLIENT_VERSION_MAJOR], [Major version])
 AC_DEFINE([CLIENT_VERSION_MINOR], [_CLIENT_VERSION_MINOR], [Minor version])
diff --git a/src/Makefile.minisketch.include b/src/Makefile.minisketch.include
index 1363bec34eac2..c6f894f0ca018 100644
--- a/src/Makefile.minisketch.include
+++ b/src/Makefile.minisketch.include
@@ -12,10 +12,6 @@ LIBMINISKETCH_CPPFLAGS += -DHAVE_CLMUL
 MINISKETCH_LIBS += $(LIBMINISKETCH_CLMUL)
 endif
 
-if HAVE_CLZ
-LIBMINISKETCH_CPPFLAGS += -DHAVE_CLZ
-endif
-
 EXTRA_LIBRARIES += $(MINISKETCH_LIBS)
 
 minisketch_libminisketch_clmul_a_SOURCES = $(MINISKETCH_FIELD_CLMUL_SOURCES_INT) $(MINISKETCH_FIELD_CLMUL_HEADERS_INT)
diff --git a/src/minisketch/.cirrus.yml b/src/minisketch/.cirrus.yml
index 4a5353f1373c9..5ceefee2cffa3 100644
--- a/src/minisketch/.cirrus.yml
+++ b/src/minisketch/.cirrus.yml
@@ -36,17 +36,6 @@ env_matrix_snippet: &ENV_MATRIX_VALGRIND
       TESTRUNS: 1
       BUILD:
 
-env_matrix_snippet: &ENV_MATRIX_SAN
-  - env:
-      ENABLE_FIELDS: 28
-  - env:
-      BUILD: distcheck
-  - env:
-      CXXFLAGS:  "-fsanitize=undefined -fno-omit-frame-pointer"
-      LDFLAGS: "-fsanitize=undefined -fno-omit-frame-pointer"
-      UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
-      BENCH: no
-
 env_matrix_snippet: &ENV_MATRIX_SAN_VALGRIND
   - env:
       ENABLE_FIELDS: "11,64,37"
@@ -72,9 +61,9 @@ task:
     << : *ENV_MATRIX_SAN_VALGRIND
   matrix:
     - env:
-        CC: gcc
+        CXX: g++
     - env:
-        CC: clang
+        CXX: clang++ -gdwarf-4
   << : *MERGE_BASE
   test_script:
     - ./ci/cirrus.sh
@@ -92,30 +81,45 @@ task:
     << : *ENV_MATRIX_VALGRIND
   matrix:
     - env:
-        CC: i686-linux-gnu-gcc
+        CXX: i686-linux-gnu-g++
     - env:
-        CC: clang --target=i686-pc-linux-gnu -isystem /usr/i686-linux-gnu/include
+        CXX: clang++ --target=i686-linux-gnu -gdwarf-4
+        CXXFLAGS: -g -O2 -isystem /usr/i686-linux-gnu/include -isystem /usr/i686-linux-gnu/include/c++/10/i686-linux-gnu
   test_script:
     - ./ci/cirrus.sh
   << : *CAT_LOGS
 
 task:
-  name: "x86_64: macOS Catalina"
+  name: "arm64: macOS Monterey"
   macos_instance:
-    image: catalina-base
+    image: ghcr.io/cirruslabs/macos-monterey-base:latest
   env:
-    # Cirrus gives us a fixed number of 12 virtual CPUs.
-    MAKEFLAGS: -j13
-  matrix:
-    << : *ENV_MATRIX_SAN
+    # Cirrus gives us a fixed number of 4 virtual CPUs.
+    MAKEFLAGS: -j5
   matrix:
     - env:
-        CC: gcc-9
+        CXX: g++-11
+      # Homebrew's gcc for arm64 has no libubsan.
+      matrix:
+        - env:
+            ENABLE_FIELDS: 28
+        - env:
+            BUILD: distcheck
     - env:
-        CC: clang
+        CXX: clang++
+      matrix:
+        - env:
+            ENABLE_FIELDS: 28
+        - env:
+            BUILD: distcheck
+        - env:
+            CXXFLAGS:  "-fsanitize=undefined -fno-omit-frame-pointer"
+            LDFLAGS: "-fsanitize=undefined -fno-omit-frame-pointer"
+            UBSAN_OPTIONS: "print_stacktrace=1:halt_on_error=1"
+            BENCH: no
   brew_script:
     - brew update
-    - brew install automake libtool gcc@9
+    - brew install automake libtool gcc@11
   << : *MERGE_BASE
   test_script:
     - ./ci/cirrus.sh
@@ -128,13 +132,11 @@ task:
     cpu: 4
     memory: 2G
   env:
-    EXEC_CMD: qemu-s390x -L /usr/s390x-linux-gnu
+    EXEC_CMD: qemu-s390x
     HOST: s390x-linux-gnu
     BUILD:
   << : *MERGE_BASE
   test_script:
-    # https://sourceware.org/bugzilla/show_bug.cgi?id=27008
-    - rm /etc/ld.so.cache
     - ./ci/cirrus.sh
   << : *CAT_LOGS
 
@@ -146,6 +148,7 @@ task:
     memory: 2G
   env:
     EXEC_CMD: wine
+    EXEC_EXT: .exe
     HOST: x86_64-w64-mingw32
     BUILD:
   << : *MERGE_BASE
diff --git a/src/minisketch/ci/cirrus.sh b/src/minisketch/ci/cirrus.sh
index 02f737ca7f2eb..36250d165195d 100755
--- a/src/minisketch/ci/cirrus.sh
+++ b/src/minisketch/ci/cirrus.sh
@@ -7,7 +7,7 @@ export LC_ALL=C
 
 env >> test_env.log
 
-$CC -v || true
+$CXX -v || true
 valgrind --version || true
 
 ./autogen.sh
@@ -32,10 +32,10 @@ then
 fi
 
 if [ -n "$EXEC_CMD" ]; then
-    $EXEC_CMD ./test $TESTRUNS
-    $EXEC_CMD ./test-verify $TESTRUNS
+    $EXEC_CMD "./test$EXEC_EXT" $TESTRUNS
+    $EXEC_CMD "./test-verify$EXEC_EXT" $TESTRUNS
 fi
 
 if [ "$BENCH" = "yes" ]; then
-    $EXEC_CMD ./bench
+    $EXEC_CMD "./bench$EXEC_EXT"
 fi
diff --git a/src/minisketch/ci/linux-debian.Dockerfile b/src/minisketch/ci/linux-debian.Dockerfile
index 63e5412ee79cc..122af36e1f044 100644
--- a/src/minisketch/ci/linux-debian.Dockerfile
+++ b/src/minisketch/ci/linux-debian.Dockerfile
@@ -8,10 +8,10 @@ RUN apt-get update
 RUN apt-get install --no-install-recommends --no-upgrade -y \
         git ca-certificates \
         make automake libtool pkg-config dpkg-dev valgrind qemu-user \
-        gcc g++ clang libc6-dbg \
+        gcc g++ clang libclang-rt-dev libc6-dbg \
         gcc-i686-linux-gnu g++-i686-linux-gnu libc6-dev-i386-cross libc6-dbg:i386 \
-        g++-s390x-linux-gnu gcc-s390x-linux-gnu libc6-dev-s390x-cross libc6-dbg:s390x \
-        wine g++-mingw-w64-x86-64
+        g++-s390x-linux-gnu libstdc++6:s390x gcc-s390x-linux-gnu libc6-dev-s390x-cross libc6-dbg:s390x \
+        wine wine64 g++-mingw-w64-x86-64
 
 # Run a dummy command in wine to make it set up configuration
 RUN wine true || true
diff --git a/src/minisketch/configure.ac b/src/minisketch/configure.ac
index 83910448a2c2a..cd52d7f412a69 100644
--- a/src/minisketch/configure.ac
+++ b/src/minisketch/configure.ac
@@ -104,11 +104,6 @@ esac
 AX_CHECK_COMPILE_FLAG([-Wall],[WARN_CXXFLAGS="$WARN_CXXFLAGS -Wall"],,[[$CXXFLAG_WERROR]])
 AX_CHECK_COMPILE_FLAG([-fvisibility=hidden],[CXXFLAGS="$CXXFLAGS -fvisibility=hidden"],[],[$CXXFLAG_WERROR])
 
-## Some compilers (gcc) ignore unknown -Wno-* options, but warn about all
-## unknown options if any other warning is produced. Test the -Wfoo case, and
-## set the -Wno-foo case if it works.
-AX_CHECK_COMPILE_FLAG([-Wshift-count-overflow],[NOWARN_CXXFLAGS="$NOWARN_CXXFLAGS -Wno-shift-count-overflow"],,[[$CXXFLAG_WERROR]])
-
 if test "x$use_ccache" != "xno"; then
   AC_MSG_CHECKING(if ccache should be used)
   if test x$CCACHE = x; then
@@ -119,7 +114,6 @@ if test "x$use_ccache" != "xno"; then
     fi
   else
     use_ccache=yes
-    CC="$ac_cv_path_CCACHE $CC"
     CXX="$ac_cv_path_CCACHE $CXX"
   fi
   AC_MSG_RESULT($use_ccache)
diff --git a/src/minisketch/src/int_utils.h b/src/minisketch/src/int_utils.h
index d21ba56f336c0..2b3d8cb402668 100644
--- a/src/minisketch/src/int_utils.h
+++ b/src/minisketch/src/int_utils.h
@@ -7,13 +7,16 @@
 #ifndef _MINISKETCH_INT_UTILS_H_
 #define _MINISKETCH_INT_UTILS_H_
 
+#include <stdint.h>
 #include <stdlib.h>
 
 #include <limits>
 #include <algorithm>
 #include <type_traits>
 
-#ifdef _MSC_VER
+#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L
+#  include <bit>
+#elif defined(_MSC_VER)
 #  include <intrin.h>
 #endif
 
@@ -54,11 +57,10 @@ class BitWriter {
     int offset = 0;
     unsigned char* out;
 
-public:
-    BitWriter(unsigned char* output) : out(output) {}
-
     template<int BITS, typename I>
-    inline void Write(I val) {
+    inline void WriteInner(I val) {
+        // We right shift by up to 8 bits below. Verify that's well defined for the type I.
+        static_assert(std::numeric_limits<I>::digits > 8, "BitWriter::WriteInner needs I > 8 bits");
         int bits = BITS;
         if (bits + offset >= 8) {
             state |= ((val & ((I(1) << (8 - offset)) - 1)) << offset);
@@ -77,6 +79,19 @@ class BitWriter {
         offset += bits;
     }
 
+
+public:
+    BitWriter(unsigned char* output) : out(output) {}
+
+    template<int BITS, typename I>
+    inline void Write(I val) {
+        // If I is smaller than an unsigned int, invoke WriteInner with argument converted to unsigned.
+        using compute_type = typename std::conditional<
+            (std::numeric_limits<I>::digits < std::numeric_limits<unsigned>::digits),
+            unsigned, I>::type;
+        return WriteInner<BITS, compute_type>(val);
+    }
+
     inline void Flush() {
         if (offset) {
             *(out++) = state;
@@ -129,7 +144,11 @@ constexpr inline I Mask() { return ((I((I(-1)) << (std::numeric_limits<I>::digit
 /** Compute the smallest power of two that is larger than val. */
 template<typename I>
 static inline int CountBits(I val, int max) {
-#ifdef _MSC_VER
+#if defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L
+    // c++20 impl
+    (void)max;
+    return std::bit_width(val);
+#elif defined(_MSC_VER)
     (void)max;
     unsigned long index;
     unsigned char ret;
@@ -175,6 +194,7 @@ class BitsInt {
     }
 
     static constexpr inline bool IsZero(I a) { return a == 0; }
+    static constexpr inline bool IsOne(I a) { return a == 1; }
     static constexpr inline I Mask(I val) { return val & MASK; }
     static constexpr inline I Shift(I val, int bits) { return ((val << bits) & MASK); }
     static constexpr inline I UnsafeShift(I val, int bits) { return (val << bits); }
@@ -233,7 +253,7 @@ template<typename I, int N, typename L, typename F> inline constexpr I GFMul(con
 template<typename I, typename F, int BITS, uint32_t MOD>
 inline I InvExtGCD(I x)
 {
-    if (F::IsZero(x)) return x;
+    if (F::IsZero(x) || F::IsOne(x)) return x;
     I t(0), newt(1);
     I r(MOD), newr = x;
     int rlen = BITS + 1, newrlen = F::Bits(newr, BITS);


Test patch: