Source: libidn2 Version: 2.3.8-2 Severity: serious Justification: Debian Policy § 7.8 (license compliance), and possibly an "ABI breakage ticking time bomb" Control: found -1 2.3.8-5 Control: affects -1 src:gnulib
Ahoy,
I have observed the following discrepancy on Trixie and it appears to affect
unstable in the same way. This bug report is sort of a story format to inform
aspiring bug hunters and leave little doubt about the nature of my concerns.
The libidn2.a static library contains object files from libunistring. These can
be extracted into a current working directory by doing
$ ar -t /usr/lib/x86_64-linux-gnu/libidn2.a | grep -E '^libunistring' | xargs
-E '' -- ar -x /usr/lib/x86_64-linux-gnu/libidn2.a
which yields
> libunistring_la-categ_none.o
> libunistring_la-c-ctype.o
> libunistring_la-c-strcasecmp.o
> libunistring_la-c-strncasecmp.o
> libunistring_la-decompose-internal.o
> libunistring_la-decomposition-table.o
> libunistring_la-localcharset.o
> libunistring_la-stdlib.o
> libunistring_la-striconveha.o
> libunistring_la-striconveh.o
> libunistring_la-unistd.o
libunistring's static library has object files with all of these same names
(except the *-stdlib.o one), which can be extracted in a second directory with
$ ar -t /usr/lib/x86_64-linux-gnu/libidn2.a | grep -E '^libunistring' | xargs
-E '' -L 1 -- ar -x /usr/lib/x86_64-linux-gnu/libunistring.a
From comparing them, I can see that some object files are bit-for-bit identical
but most aren't:
$ for object in libidn2-extract/*.o; do cmp "$object"
libunistring-extract/"${object##*/}" 2>/dev/null && printf '%s matches\n'
"${object##*/}"; done
> libunistring_la-categ_none.o matches
> libidn2-extract/libunistring_la-c-ctype.o
> libunistring-extract/libunistring_la-c-ctype.o differ: byte 41, line 1
> libidn2-extract/libunistring_la-c-strcasecmp.o
> libunistring-extract/libunistring_la-c-strcasecmp.o differ: byte 41, line 1
> libidn2-extract/libunistring_la-c-strncasecmp.o
> libunistring-extract/libunistring_la-c-strncasecmp.o differ: byte 41, line 1
> libidn2-extract/libunistring_la-decompose-internal.o
> libunistring-extract/libunistring_la-decompose-internal.o differ: byte 41,
> line 1
> libidn2-extract/libunistring_la-decomposition-table.o
> libunistring-extract/libunistring_la-decomposition-table.o differ: byte 41,
> line 1
> libunistring_la-localcharset.o matches
> libidn2-extract/libunistring_la-striconveha.o
> libunistring-extract/libunistring_la-striconveha.o differ: byte 41, line 1
> libidn2-extract/libunistring_la-striconveh.o
> libunistring-extract/libunistring_la-striconveh.o differ: byte 41, line 1
> libunistring_la-unistd.o matches
At first I conjectured that this is because the libidn2 version that ended up
in Trixie (2.3.8-2) was uploaded about one week before Trixie's libunistring
version (1.3-2) was [1] [2], and so the binaries would be very similar but not
identical. This would be a license violation, because the libidn2-dev binary
package would carry object files from libunistring-dev 1.3-1 which is no longer
in the Debian archive in source form [3]. This is the sort of thing Built-Using
is intended to prevent. Just to double-check my theory, I got the old binary
package from snapshot.debian.org [4]... but I couldn't believe this wasn't it!
$ curl -L -O
https://snapshot.debian.org/file/6362f906874dd02c4c724bc9433a1b16bccd2d80/libunistring-dev_1.3-1_amd64.deb
$ dpkg-deb -x ./libunistring-dev_1.3-1_amd64.deb ./libunistring-dev_1.3-1/
$ mkdir libunistring-extract_1.3-1/
$ cd libunistring-extract_1.3-1/
$ ar -t /usr/lib/x86_64-linux-gnu/libidn2.a | grep -E '^libunistring' | xargs
-E '' -L 1 -- ar -x
../libunistring-dev_1.3-1/usr/lib/x86_64-linux-gnu/libunistring.a
> ../libidn2-extract/libunistring_la-categ_none.o libunistring_la-categ_none.o
> differ: byte 75, line 1
> ../libidn2-extract/libunistring_la-c-ctype.o libunistring_la-c-ctype.o
> differ: byte 41, line 1
> ../libidn2-extract/libunistring_la-c-strcasecmp.o
> libunistring_la-c-strcasecmp.o differ: byte 41, line 1
> ../libidn2-extract/libunistring_la-c-strncasecmp.o
> libunistring_la-c-strncasecmp.o differ: byte 41, line 1
> ../libidn2-extract/libunistring_la-decompose-internal.o
> libunistring_la-decompose-internal.o differ: byte 41, line 1
> ../libidn2-extract/libunistring_la-decomposition-table.o
> libunistring_la-decomposition-table.o differ: byte 41, line 1
> ../libidn2-extract/libunistring_la-localcharset.o
> libunistring_la-localcharset.o differ: byte 131, line 1
> ../libidn2-extract/libunistring_la-striconveha.o
> libunistring_la-striconveha.o differ: byte 41, line 1
> ../libidn2-extract/libunistring_la-striconveh.o libunistring_la-striconveh.o
> differ: byte 41, line 1
> libunistring_la-unistd.o matches
"Where else could these object files be coming from?" I asked. The answer lies
in the build log [5]:
> Creating directory ./unistring/m4
> Creating directory ./unistring/uniconv
> Creating directory ./unistring/unictype
> Creating directory ./unistring/uninorm
> Creating directory ./unistring/unistr
> Replacing file build-aux/config.rpath (non-gnulib code backed up in
> build-aux/config.rpath~) !!
> Copying file unistring/_Noreturn.h
> Copying file unistring/arg-nonnull.h
> Copying file unistring/array-mergesort.h
> ...
and the refreshed copy of embedded libunistring later gets built and used, even
though it's not supposed to [6] (unimportant compiler hardening and warning
options omitted for brevity):
> make[4]: Entering directory
> '/build/reproducible-path/libidn2-2.3.8/build/unistring'
> /bin/bash ../libtool --tag=CC --mode=compile gcc -DHAVE_CONFIG_H -I.
> -I../../unistring -I.. -I../../gl -I../gl -g -O2
> -ffile-prefix-map=/build/reproducible-path/libidn2-2.3.8=. -c -o
> libunistring_la-c-ctype.lo
> [ likewise for c-strcasecmp.c, localcharset.c, and other source/object files
> we can recognize from before ]
> ...
> libtool: link: ar cr .libs/libunistring.a .libs/libunistring_la-c-ctype.o
> .libs/libunistring_la-c-strcasecmp.o .libs/libunistring_la-c-strncasecmp.o
> .libs/libunistring_la-localcharset.o .libs/libunistring_la-stdlib.o
> .libs/libunistring_la-striconveh.o .libs/libunistring_la-striconveha.o
> unictype/.libs/libunistring_la-categ_none.o
> uninorm/.libs/libunistring_la-decompose-internal.o
> uninorm/.libs/libunistring_la-decomposition-table.o
> .libs/libunistring_la-unistd.o
> libtool: link: ranlib .libs/libunistring.a
> libtool: link: ( cd ".libs" && rm -f "libunistring.la" && ln -s
> "../libunistring.la" "libunistring.la" )
> make[4]: Leaving directory
> '/build/reproducible-path/libidn2-2.3.8/build/unistring'
So we actually build a static libunistring library on-the-fly, in spite of our
intentions to use the system version, and *this* is what wiggles its way into
libidn2.a. That explains some things. In hindsight it can be seen in an easy
way that these objects are incorporated into the libidn2 shared library as well:
$ DEBUGINFOD_URLS=${DEBUGINFOD_URLS:-"https://debuginfod.debian.net
https://debuginfod.elfutils.org"} eu-srcfiles -e
/usr/lib/x86_64-linux-gnu/libidn2.so | grep -F unistr
> /build/unistring/stdlib.h
> /build/unistring/string.h
> /unistring/array-mergesort.h
> /unistring/c-ctype.h
> /unistring/c-strcase.h
> /unistring/c-strcasecmp.c
> /unistring/c-strcaseeq.h
> /unistring/c-strncasecmp.c
> /unistring/iconveh.h
> /unistring/localcharset.c
> /unistring/striconveh.c
> /unistring/striconveh.h
> /unistring/striconveha.c
> /unistring/unictype/categ_none.c
> /unistring/uninorm/decompose-internal.h
> /unistring/uninorm/decomposition-table1.h
> /unistring/uninorm/decomposition-table2.h
> /usr/include/unistr.h
> /usr/include/unistring/iconveh.h
> /usr/include/unistring/localcharset.h
The gcc invocations in the build logs don't show the -isystem option being
used, as is sometimes done in cases like this, but just -I. Therefore I'm
inclined to believe those last few headers really are obtained from the
system's true libunistring in preference to the headers in the build directory.
What a horrible mess.
Now, the build log has messages such as
> checking for libunistring... yes
> checking how to link with libunistring...
> /usr/lib/x86_64-linux-gnu/libunistring.so
> checking for libunistring version... 1.3.0
> checking for unistring/woe32dll.h... yes
> checking whether to use the included libunistring... no
It's not apparent why an included libunistring (from any origin) is being used.
This is probably a bug in Gnulib, autotools, or some other far-out place. Maybe
the responsible Gnulib modules can be disabled, but otherwise I think setting
Built-Using on gnulib is necessary for license compliance here as this
libunistring is still GNU LGPL just the same. Let me know if you'd like me to
elaborate on how it's a license conformance problem and why the GNU LGPL
confers that obligation here. I recently gave some advice about this in opening
bug #1143501 which you may like to peek at.
I'm going to get some sleep now. Hopefully this mail is at least mildly
comprehensible, and it doesn't put you to sleep just the same
P.S. gnulib-tool has features to export machine-readable information about the
licenses of used modules and give special care for the GNU (L)GPL, and the
debhelper module could be made smart enough to hook into dh_builtusing to set
that field when a relevant module gets pulled in or used. Otherwise this seems
like a "gotcha" that could bite other packages. That'd be a great long-term
solution.
[1] https://lists.debian.org/msgid-search/E1ttE7I-003r29-PP%40fasolo.debian.org
[2] https://lists.debian.org/msgid-search/E1twCKg-008V3v-HH%40fasolo.debian.org
[3] https://deb.debian.org/debian/pool/main/libu/libunistring/
[4]
https://snapshot.debian.org/file/6362f906874dd02c4c724bc9433a1b16bccd2d80/libunistring-dev_1.3-1_amd64.deb
[5]
https://buildd.debian.org/status/fetch.php?pkg=libidn2&arch=amd64&ver=2.3.8-2&stamp=1741995249&raw=1#line=1944,1952
[6]
https://buildd.debian.org/status/fetch.php?pkg=libidn2&arch=amd64&ver=2.3.8-2&stamp=1741995249&raw=1#line=4131,4167
-- System Information:
Debian Release: 13.6
APT prefers stable-updates
APT policy: (500, 'stable-updates'), (500, 'stable-security-debug'), (500,
'stable-security'), (500, 'stable-debug'), (500, 'proposed-updates-debug'),
(500, 'proposed-updates'), (500, 'stable')
Architecture: amd64 (x86_64)
Kernel: Linux 6.12.96+deb13-amd64 (SMP w/2 CPU threads; PREEMPT)
Locale: LANG=en_US.UTF-8, LC_CTYPE=en_US.UTF-8 (charmap=UTF-8), LANGUAGE not
set
Shell: /bin/sh linked to /usr/bin/dash
Init: systemd (via /run/systemd/system)
LSM: AppArmor: enabled
Versions of packages libidn2-dev depends on:
ii libidn2-0 2.3.8-2
libidn2-dev recommends no packages.
libidn2-dev suggests no packages.
-- no debconf information
signature.asc
Description: This is a digitally signed message part

