Reproduzierbarer Paketbau in der Praxis

Hier noch ein kleiner Blogbeitrag über reproduzierbaren Paketbau in der derzeitigen Praxis von Debian GNU/Linux-Paketbetreuern. Das ist eine gute Gelegenheit, in diesem Blog auch einmal den Builder Pbuilder anzusprechen, mit dem der derzeitig noch experimentelle Toolchain des Reproducible Builds-Projektes1 schon in Betrieb genommen werden kann.

Als Beispiel habe ich das Quellpaket von Exiv2 ausprobiert2, das ist eine C++-Bibliothek nebst CLI-Werkzeug für den Umgang mit Image-Metadaten in verschiedenen Formaten (Exif, IPTC, XMP)3.

Das Tool Debbindiff4 kann verwendet werden um herauszufinden, wodurch sich etwa zwei direkt hintereinander gebaute Exiv2-Quellpakete5 voneinander unterscheiden:

$ debbindiff --text exiv2.1.debbindiff 1/exiv2_0.24-4.1_amd64.changes 2/exiv2_0.24-4.1_amd64.changes 

Dabei tauchen bei allen Binärpaketen Abweichungen auf. Eine Gruppe davon sind Non-Determinismen in der “package metadata”, das sind überwiegend “normale” Unterschiede im Zeitstempel von generierten Dateien, wie sie ohne angepassten Toolchain bei jedem Quellpaket so vorkommen6.

Darüber hinaus gibt es aber auch spezielle Unterschiede (exiv2/0.24-4.1), die sich in zwei weitere Gruppen einteilen lassen7:

<hr class="footer"/><address class="footer"><small>
Generated on Wed Apr 8 2015 10:55:33 for Exiv2 by &#160;<a href="http://www.doxygen.org/index.html">
<img class="footer" src="doxygen.png" alt="doxygen"/>
</a> 1.8.8
</small></address>

Der bisher rein experimentelle Toolchain des Reproducible Builds-Projektes9 lässt sich zum Beispiel mit dem Builder Pbuilder10 schon jetzt in Betrieb nehmen. Der Bau in einer Chroot-Umgebung empfiehlt sich generell, weil damit bequem gegenüber Sid gebaut werden kann (was gemeinhin nicht als “running system” eingesetzt wird), für das experimentelle Repositorium ist das aber auf jeden Fall dringend empfohlen. Chroot-Packetbau braucht aber eine gute Internet-Verbindung, an ge-debootstrapten Systemen und in Wegwerf-Umgebungen gezogenen Paketen kommt dabei ganz schön was zusammen.

Pbuilder

Pbuilder ist einfach in Betrieb zu nehmen, zunächst installiere man einfach das Debian-Paket, danach muss mit $ sudo pbuilder --create der Sid-Chroot gezogen werden, der danach in gezipter Form unter /var/cache/pbuilder/base.tgz zur Verfügung steht. Ob der Chroot läuft kann direkt ausprobiert werden, indem man zum Beispiel ein Update dafür anstößt mit $ sudo pbuilder --update. Mit der Grundkonfiguration müsste alles in diesem Beitrag Besprochene funktionieren.

Der experimentelle Toolchain des Reproducible Builds-Projektes findet sich im Paketarchiv http://reproducible.alioth.debian.org/debian/. Diese Adresse muss nun einfach zur den Apt-Quellen im Sid-Chroot hinzugefügt werden. Dafür muss man sich zuerst in den Chroot einloggen11:

$ sudo pbuilder --login --save-after-exec
W: /root/.pbuilderrc does not exist
I: Building the build Environment
I: extracting base tarball [/var/cache/pbuilder/base.tgz]
I: creating local configuration
I: copying local configuration
I: mounting /proc filesystem
I: mounting /run/shm filesystem
I: mounting /dev/pts filesystem
I: policy-rc.d already exists
I: Obtaining the cached apt archive contents
I: entering the shell
File extracted to: /var/cache/pbuilder/build//31137
root@varuna:/# 

Nun kann man die Paketquelle für Apt ergänzen:

root@varuna:/# echo 'deb http://reproducible.alioth.debian.org/debian/ ./' >> /etc/apt/sources.list
root@varuna:/# cat /etc/apt/sources.list
deb ftp://ftp.de.debian.org/debian sid main
#deb-src ftp://ftp.de.debian.org/debian sid main
deb http://reproducible.alioth.debian.org/debian/ ./

Danach muss auch noch der öffentliche Schlüssel dieses Repositoriums gezogen und dem Apt-Schlüsselbund hinzugefügt werden, dafür benötigt es auch noch Wget12:

root@varuna:/# apt-get install wget
{...}
root@varuna:/# wget -O- http://reproducible.alioth.debian.org/reproducible.gpg | apt-key add -
--2015-04-08 15:01:54--  http://reproducible.alioth.debian.org/reproducible.gpg
Connecting to 127.0.0.1:3142... connected.
Proxy request sent, awaiting response... 200 OK
Length: 2238 (2.2K) [application/octet-stream]
Saving to: 'STDOUT'

-   100%[==========================>]   2.19K  --.-KB/s   in 0s

2015-04-08 15:01:54 (277 MB/s) - written to stdout [2238/2238]
OK
root@varuna:/# apt-key list | grep EA59A31F 
pub   4096R/EA59A31F 2014-10-02 [expires: 2017-10-01]
root@varuna:/# exit

Danach ausloggen und nochmal ein Update anschmeissen, dabei werden direkt einige Pakete aus reproducible.alioth.debian.org über den offiziellen Zweig drüber gezogen. Danach steht die Chroot-Umgebung von Sid mit dem eingebundenen experimentellen Toolchain für reproduzierbaren Paketbau bereit.

TimestampsFromCPPMacros

Quellpakete werden bei Pbuilder mit pbuilder --build foo.dsc gebaut, wobei Root-Rechte benötigt werden. Das Ergebnis wird bei unveränderter Grundeinstellung immer in /var/cache/pbuilder/result gespeichert. Wenn man zweimal hintereinander bauen und vergleichen möchte, dann muss eventuell umkopiert werden, wozu sich dcmd anbietet (expandiert Dateilisten von .dsc- und .changes-Dateien).

Aber schon der erste Bau bricht nun mit Fehlermeldung ab, Buildlog:

libtool: compile: g++ -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -fvisibility=hidden
-fvisibility-inlines-hidden -Wall -Wcast-align -Wpointer-arith -Wformat-security -Wmissing-format-attribute
-Woverloaded-virtual -W -I. -Werror=date-time -D_FORTIFY_SOURCE=2 -DEXV_LOCALEDIR=\"/usr/share/locale\"
-I../xmpsdk/include -c -DEXV_BUILDING_LIB=1 version.cpp  -fPIC -DPIC -o .libs/version.o
version.cpp:260:26: error: macro "__DATE__" might prevent reproducible builds [-Werror=date-time]
     os << "date="     << __DATE__               << endl;
                          ^
version.cpp:261:26: error: macro "__TIME__" might prevent reproducible builds [-Werror=date-time]
     os << "time="     << __TIME__               << endl;
                          ^

Der Entwickler verwendet in src/version.cpp die Präprozessor-Makros __DATE__ und __TIME__, und dieser Non-Determinismus macht das Quellpaket grundsätzlich unreproduzierbar13. Im experimentellen Toolchain gehört -Werror=date-time zu den Buildflags für GCC, so dass der Bau hier sofort abbricht14:

root@varuna:/# dpkg-buildflags 
CFLAGS=-g -O2 -fstack-protector-strong -Wformat -Werror=format-security
CPPFLAGS=-Werror=date-time -D_FORTIFY_SOURCE=2
CXXFLAGS=-g -O2 -fstack-protector-strong -Wformat -Werror=format-security
FCFLAGS=-g -O2 -fstack-protector-strong
FFLAGS=-g -O2 -fstack-protector-strong
GCJFLAGS=-g -O2 -fstack-protector-strong
LDFLAGS=-Wl,-z,relro
OBJCFLAGS=-g -O2 -fstack-protector-strong -Wformat -Werror=format-security
OBJCXXFLAGS=-g -O2 -fstack-protector-strong -Wformat -Werror=format-security

Man macht also ernst mit der Reproduzierbarkeit, die Pakete bauen sonst gar nicht mehr. Exiv2 reproduzierbar zu machen läuft auf einen Patch hinaus, zunächst habe ich das Quellpaket aber erst einmal mit großzügigeren Buildflags durchlaufen lassen. Die Buildflags lassen sich ziemlich einfach manipulieren, über den Funktionalitätbereich reproducible kann man mittels der Umgebungsvariable DEB_BUILD_MAINT_OPTIONS in debian/rules die Option timleless disablen, um den kritischen Flag für den C-Präprozessor raus zu nehmen15:

export DEB_BUILD_MAINT_OPTIONS=reproducible=-timeless

Um debian/rules zu verändern muss man natürlich das Quellpaket auspacken. Pbuilder bietet das Tool pdebuild, um aus einem ausgepackten Quellverzeichnis heraus zu bauen, dabei kann auch mittels der Option --buildresult ../Pfad/ ein alternativer Pfad für das Bauergebnis eingestellt werden, und dazu noch mit --debbuildopts '-b' nur die Binärpakete gebaut werden.

Beim ersten Durchsehen der Bauergebnisse zeigt sich allerdings, dass die von Doxygen erzeugten HTML-Dateien in der Dokumentation immer noch denselben Zeitstempel im Footer tragen, obwohl das mit dem alternativen Doxygen-Paket im experimentellen Toolchain (Build-dep protokolliert in der neuen .buildlog-Datei: doxygen (= 1.8.8-5.1~reproducible2)) eigentlich behoben sein sollte16. Die Umgebungsvariable HTML_TIMESTAMP für Doxygen ist in der lokalen Konfigurationsdatei config/Doxyfile allerdings wieder auf YES gestellt. Das muss für ein reproduzierbares Paket auch noch gepatcht werden:

--- a/config/Doxyfile
+++ b/config/Doxyfile
@@ -974,7 +974,7 @@
 # page will contain the date and time when the page was generated. Setting
 # this to NO can help when comparing the output of multiple runs.
 
-HTML_TIMESTAMP         = YES
+HTML_TIMESTAMP         = NO
 
 # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
 # documentation will contain sections that can be hidden and shown after the

Mit dem behobenen Doxygen-Problem zeigt sich im Debbindiff von zwei nochmals direkt hintereinander gebauten Paketen nun wie erwartet folgendes Ergebnis: es sind nur noch die Unterschiede in den Binaries vorhanden17, und es ist klar, wie die zu Stande kommen, nämlich durch die verwendeten Präprozessor-Makros __DATE__ und __TIME__. Ein einfacher Patch nimmt die einfach raus:

--- a/src/version.cpp
+++ b/src/version.cpp
@@ -257,8 +257,6 @@
     os << "dll="      << dll                    << endl;
     os << "debug="    << debug                  << endl;
     os << "version="  << __VERSION__            << endl;
-    os << "date="     << __DATE__               << endl;
-    os << "time="     << __TIME__               << endl;
 
     if ( libs.begin() != libs.end() ) {
         os << "executable=" << *libs.begin() << endl;

Die fehlende Funktionalität kann durchaus verschmerzt werden: Mit diesen Maßnahmen konnten die Binaries tatsächlich Checksummen identisch reproduzierbar gemacht werden:

~/Schreibtisch/reproducible/sandbox$ md5sum 1/*.deb
a889f848739835927e5b474139000860  1/exiv2_0.24-4.1_amd64.deb
b59629baa2921b2aa68e215d7e96068b  1/libexiv2-13_0.24-4.1_amd64.deb
785f39ce75c030c35a188b4aca62bc40  1/libexiv2-dbg_0.24-4.1_amd64.deb
7d5f4be3af711854a7230a097661bf50  1/libexiv2-dev_0.24-4.1_amd64.deb
6b1653d1c677e180554d37b9d07674e4  1/libexiv2-doc_0.24-4.1_all.deb
~/Schreibtisch/reproducible/sandbox$ md5sum 2/*.deb
a889f848739835927e5b474139000860  2/exiv2_0.24-4.1_amd64.deb
b59629baa2921b2aa68e215d7e96068b  2/libexiv2-13_0.24-4.1_amd64.deb
785f39ce75c030c35a188b4aca62bc40  2/libexiv2-dbg_0.24-4.1_amd64.deb
7d5f4be3af711854a7230a097661bf50  2/libexiv2-dev_0.24-4.1_amd64.deb
6b1653d1c677e180554d37b9d07674e4  2/libexiv2-doc_0.24-4.1_all.deb

  1. https://wiki.debian.org/ReproducibleBuilds [return]
  2. https://packages.qa.debian.org/e/exiv2.html [return]
  3. http://www.exiv2.org/ [return]
  4. https://packages.qa.debian.org/d/debbindiff.html [return]
  5. Quellpaket kann bezogen werden mit $ apt-get source exiv2 [return]
  6. Man vergleiche zum Beispiel das Ergebnis vom zweimaligen Bau von Pdfcrack/0.14-2, bei dem es auf Abweichungen in dieser Kategorie beschränkt bleibt [return]
  7. http://www.danielstender.com/blog/wp-content/uploads/exiv2.1.debbindiff [return]
  8. http://www.stack.nl/~dimitri/doxygen/ [return]
  9. http://wiki.debian.org/ReproducibleBuilds/ExperimentalToolchain [return]
  10. https://www.netfort.gr.jp/~dancer/software/pbuilder-doc/pbuilder-doc.html [return]
  11. Der Chroot ist eine “Wegwerf”-Umgebung, die bei jedem Aufruf wieder neu zu Verfügung steht. Möchte man Veränderungen behalten benötigt es die Option --save-after-exit (Backup vom Chroot empfohlen). Pbuilder unterscheidet dabei nicht wie Sbuild bzw. Schroot zwischen Source und Chroot [return]
  12. Genau genommen wird dadurch aber der Sid-Chroot “verunreinigt”, aber das kann man in diesem Zusammenhang vernachlässigen [return]
  13. https://reproducible.debian.net/rb-pkg/unstable/amd64/exiv2.html [return]
  14. https://wiki.debian.org/ReproducibleBuilds/TimestampsFromCPPMacros [return]
  15. Siehe die (aktuelle) Manpage von dpkg-buildflags. Sehr detaillierte Infos zum Thema “Buildflags” auch auf der Debian Wiki-Seite zum Thema “Hardening”: https://wiki.debian.org/Hardening [return]
  16. https://wiki.debian.org/ReproducibleBuilds/ExperimentalToolchain#doxygen [return]
  17. http://www.danielstender.com/blog/wp-content/uploads/exiv2.2.debbindiff [return]