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



Attachment: signature.asc
Description: This is a digitally signed message part

Reply via email to