[patch] switch to xxhash for buildid recomputation
Frank Ch. Eigler
fche@redhat.com
Tue Oct 1 21:32:05 GMT 2024
Hi, Mark -
> I think we shouldn't zero-pad, just replace the first 128 bits. Don't
> disturb what we aren't changing. Especially since most build-ids are
> currently 160 bits (sha1), so then they would suddenly all end in 4
> zeros.
Makes sense.
> [...]
> > +# Checks for xxhash (mandatory, md5/sha1 buildid hashes are removed)
> > +AC_CHECK_HEADERS([xxhash.h], [], AC_MSG_ERROR([missing xxhash.h]))
>
> I think this configure check it not enough. You also want to make
> sure you have at least xxhash 0.8.0 by checking for XXH3 types or
> functions (xxhash before 0.8.0 only supported 32/64bit hashes.
Replaced with a pkgconfig check.
> [...]
> > +$CC $CFLAGS -o main main.c
>
> Note that gcc doesn't generate build-ids unless configured with
> --enable-linker-build-id (which every distro seem to enable, but often
> isn't enabled when using a none distro gcc). So you might want to
> explicitly add -Wl,--build-id here.
Fixed.
> [...]
> > + /* Accept any build_id_size > 0. Hashes will be truncated or padded
> > + to the incoming note size, as debugedit cannot change their
> > + size. */
> > + if (build_id_size <= 0)
> > {
> > error (1, 0, "Cannot handle %zu-byte build ID", build_id_size);
> > }
>
> Do we really want to allow really small build_ids? We really need at
> least 4 bytes, if they should be usable in the /usr/lib/.build-id
> scheme. And at least 16 bytes (128 bits) to generate universially
> unique build-ids.
They're silly small, but on the other hand, this is what the developer
wanted with the incoming binaries. debugedit per se can handle it and
can leave policy to elsewhere.
> > @@ -3439,22 +3443,15 @@ handle_build_id (DSO *dso, Elf_Data *build_id,
> > /* Clear the old bits so they do not affect the new hash. */
> > memset ((char *) build_id->d_buf + build_id_offset, 0, build_id_size);
>
> I would use the same length as used in the memcpy below
> MIN (build_id_size, sizeof(XXH128_canonical_t))
> so we don't disturb any bits we aren't replacing.
Done.
OK, v2:
gpg: Signature made Tue 01 Oct 2024 05:28:59 PM EDT
gpg: using RSA key 4DD136490411C0A42B28844F258B6EFA0F209D24
gpg: Good signature from "Frank Ch. Eigler <fche@elastic.org>" [ultimate]
Author: Frank Ch. Eigler <fche@redhat.com>
Date: Tue Sep 24 15:34:44 2024 -0400
debugedit: switch to xxhash for buildid recomputation
When debugedit recomputes build-id (due to -i), it previously used md5
or sha1 (depending on length of incoming buildid). This patch
replaces both those (including the code that does all the hashing)
with a 128-bit xxhash. The 128-bit hash is truncated or 0-padded to
whatever the incoming ELF note width was.
xxhash is much faster (8x over sha1) than either of those
crypto-flavoured hashes, and still produces fairly collision-free
values. This was confirmed informally with a bulk build-id
recomputation of a few million binaries in the debuginfod server
corpus, yielding zero unexpected collisions.
Signed-off-by: Frank Ch. Eigler <fche@redhat.com>
diff --git a/configure.ac b/configure.ac
index 999e31f39750..c4823722ea61 100644
--- a/configure.ac
+++ b/configure.ac
@@ -75,13 +75,11 @@ AC_SYS_LARGEFILE
PKG_PROG_PKG_CONFIG
PKG_CHECK_MODULES([LIBELF], [libelf])
PKG_CHECK_MODULES([LIBDW], [libdw])
+PKG_CHECK_MODULES([XXHASH], [libxxhash >= 0.8.2])
# Checks for header files.
AC_CHECK_HEADERS([fcntl.h inttypes.h limits.h malloc.h stddef.h stdint.h stdlib.h string.h unistd.h])
-# Checks for xxhash (mandatory, md5/sha1 buildid hashes are removed)
-AC_CHECK_HEADERS([xxhash.h], [], AC_MSG_ERROR([missing xxhash.h]))
-
# Checks for typedefs, structures, and compiler characteristics.
AC_CHECK_HEADER_STDBOOL
AC_C_INLINE
diff --git a/tests/debugedit.at b/tests/debugedit.at
index 4413704acc0b..68886b1f3d79 100644
--- a/tests/debugedit.at
+++ b/tests/debugedit.at
@@ -717,3 +717,43 @@ $CC $CFLAGS -gdwarf-5 -o main main.c
AT_CHECK([[debugedit -l sources.list main]])
AT_CHECK([[grep -q main.c sources.list]])
AT_CLEANUP
+
+
+# ===
+# build-id recomputation
+# ===
+AT_SETUP([debugedit build-id recompute])
+AT_KEYWORDS([debuginfo] [debugedit] [build-id])
+
+# compile a test program and extract its linker-assigned build-id
+echo "int main () { }" > main.c
+$CC $CFLAGS -Wl,--build-id -o main main.c
+AT_CHECK([[$READELF -n main | grep Build.ID: | awk '{print $3}']], [0], [stdout], [ignore])
+bid="`cat stdout`"
+AT_CHECK([[expr "$bid" : '[0-9a-f]*']], [0], [ignore])
+
+# run debugedit to recompute build-id
+AT_CHECK([[debugedit -i -s deadbeef main]], [0], [stdout])
+bid2a="`cat stdout`"
+AT_CHECK([[expr "$bid2a" : '[0-9a-f]*']], [0], [ignore])
+AT_CHECK([[test "$bid" != "$bid2a"]])
+
+# check that debugedit's stdout matches readelf -n note
+AT_CHECK([[$READELF -n main | grep Build.ID: | awk '{print $3}']], [0], [stdout], [ignore])
+bid2b="`cat stdout`"
+AT_CHECK([[expr "$bid2b" : '[0-9a-f]*']], [0], [ignore])
+AT_CHECK([[test "$bid2a" == "$bid2b"]])
+
+# run debugedit to recompute build-id, check for idempotence
+AT_CHECK([[debugedit -i -s deadbeef main]], [0], [stdout])
+bid2c="`cat stdout`"
+AT_CHECK([[expr "$bid2c" : '[0-9a-f]*']], [0], [ignore])
+AT_CHECK([[test "$bid2a" == "$bid2c"]])
+
+# check that debugedit -i with different -s seed results in different valid build-id
+AT_CHECK([[debugedit -i -s zoofoo main]], [0], [stdout])
+bid3="`cat stdout`"
+AT_CHECK([[expr "$bid3" : '[0-9a-f]*']], [0], [ignore])
+AT_CHECK([[test "$bid3" != "$bid2a"]])
+
+AT_CLEANUP
diff --git a/tools/debugedit.c b/tools/debugedit.c
index 83ee755a1523..d31e8e4d2207 100644
--- a/tools/debugedit.c
+++ b/tools/debugedit.c
@@ -3440,8 +3440,14 @@ handle_build_id (DSO *dso, Elf_Data *build_id,
|| (! dirty_elf && build_id_seed == NULL))
goto print;
- /* Clear the old bits so they do not affect the new hash. */
- memset ((char *) build_id->d_buf + build_id_offset, 0, build_id_size);
+ /* Clear the bits about to be recomputed, so they do not affect the
+ new hash. Extra bits left over from wider-than-128-bit hash are
+ preserved for extra entropy. This computation should be
+ idempotent, so repeated rehashes (with the same seed) should
+ result in the same hash. */
+ XXH128_canonical_t result_canon;
+ memset ((char *) build_id->d_buf + build_id_offset, 0,
+ MIN (build_id_size, sizeof(result_canon)));
XXH3_state_t* state = XXH3_createState();
if (!state)
@@ -3519,7 +3525,6 @@ handle_build_id (DSO *dso, Elf_Data *build_id,
XXH128_hash_t result = XXH3_128bits_digest (state);
XXH3_freeState (state);
/* Use canonical-endianness output. */
- XXH128_canonical_t result_canon;
XXH128_canonicalFromHash (&result_canon, result);
memcpy((unsigned char *)build_id->d_buf + build_id_offset, &result_canon,
MIN (build_id_size, sizeof(result_canon)));
More information about the Debugedit
mailing list