<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="../assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Enodev.fr / Christophe's log (Posts about release)</title><link>https://www.enodev.fr/</link><description></description><atom:link href="https://www.enodev.fr/categories/release.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><lastBuildDate>Fri, 19 Apr 2024 14:34:09 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>XZ backdoor lessons: reproducing target-isns release tarballs</title><link>https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html</link><dc:creator>Christophe Vu-Brugier</dc:creator><description>&lt;p&gt;Andres Freund detected a backdoor in
&lt;a href="https://github.com/tukaani-project/xz"&gt;XZ&lt;/a&gt;,
a data compression library, and
&lt;a href="https://lwn.net/ml/oss-security/20240329155126.kjjfduxw2yrlxgzm@awork3.anarazel.de/"&gt;disclosed&lt;/a&gt;
it as
&lt;a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-3094"&gt;CVE-2024-3094&lt;/a&gt;
at the end of March 2024.&lt;/p&gt;
&lt;p&gt;Many good articles describe how the attackers implanted the backdoor:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LWN: &lt;a href="https://lwn.net/Articles/967866/"&gt;Free software's not-so-eXZellent adventure&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;LWN: &lt;a href="https://lwn.net/Articles/967192/"&gt;How the XZ backdoor works&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Russ Cox: &lt;a href="https://research.swtch.com/xz-timeline"&gt;Timeline of the XZ open source attack&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The attackers prepared the backdoor for more than two years and
completed their work when they became maintainers. They published a
release tarball with a malicious build script not present in the
source code. The attackers uploaded the release tarball to GitHub.&lt;/p&gt;
&lt;p&gt;I maintain &lt;a href="https://github.com/open-iscsi/target-isns"&gt;target-isns&lt;/a&gt;, a
niche free software project packaged in several Linux
distributions. There is not much activity in the project because it is
mostly done. The last release of target-isns – version
&lt;a href="https://github.com/open-iscsi/target-isns/releases/tag/v0.6.8"&gt;v0.6.8&lt;/a&gt;
– happened in May 2020. I built the release tarball on my
machine and uploaded it to GitHub.&lt;/p&gt;
&lt;p&gt;I believe that one of the lessons of the XZ backdoor episode is
&lt;a href="https://en.wikipedia.org/wiki/Trust,_but_verify"&gt;"Trust, but verify"&lt;/a&gt;.
Applied to target-isns releases I published, this could mean:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How can users verify that the release tarball of target-isns matches the source code?&lt;/li&gt;
&lt;li&gt;How can we improve the release process of target-isns to ease that verification?&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How to verify a release tarball built with git-archive&lt;/h2&gt;
&lt;p&gt;target-isns is written in C and built with
&lt;a href="https://cmake.org"&gt;CMake&lt;/a&gt;. A custom target, named &lt;em&gt;dist&lt;/em&gt;, invokes
&lt;a href="https://git-scm.com/docs/git-archive"&gt;git-archive&lt;/a&gt; to generate a
tarball of the source code. Ironically, the tarball is compressed with XZ.&lt;/p&gt;
&lt;p&gt;To verify that the release tarball matches the source code, we must
compare the checksum of the published release tarball with one built
locally from the source code.&lt;/p&gt;
&lt;h3&gt;How to compute the checksum of the published release tarball&lt;/h3&gt;
&lt;p&gt;Let's download the release tarball for target-isns v0.6.8 from GitHub
and compute its SHA256 checksum:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;-OL&lt;span class="w"&gt; &lt;/span&gt;--silent&lt;span class="w"&gt; &lt;/span&gt;https://github.com/open-iscsi/target-isns/releases/download/v0.6.8/target-isns-0.6.8.tar.xz
&lt;span class="gp"&gt;$ &lt;/span&gt;sha256sum&lt;span class="w"&gt; &lt;/span&gt;target-isns-0.6.8.tar.xz
&lt;span class="go"&gt;544de09a2073242b21f6859841a5016c79c4006a53435a79b5cfc6602a59db97  target-isns-0.6.8.tar.xz&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;How to reproduce the release tarball from the source code&lt;/h3&gt;
&lt;p&gt;We clone the repository with &lt;code&gt;git clone&lt;/code&gt; and checkout the tag we want
with &lt;code&gt;--branch v0.6.8&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;--branch&lt;span class="w"&gt; &lt;/span&gt;v0.6.8&lt;span class="w"&gt; &lt;/span&gt;https://github.com/open-iscsi/target-isns.git
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;target-isns
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The command &lt;code&gt;git show v0.6.8&lt;/code&gt; reports that the tagger (it's me) signed
the tag with &lt;a href="https://www.gnupg.org"&gt;GnuPG&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://www.enodev.fr/stories/about-cvubrugier.html"&gt;about&lt;/a&gt; page mentions the
fingerprint of my public GnuPG key. We import that key &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;gpg&lt;span class="w"&gt; &lt;/span&gt;--keyserver&lt;span class="w"&gt; &lt;/span&gt;keyserver.ubuntu.com&lt;span class="w"&gt; &lt;/span&gt;--search-key&lt;span class="w"&gt; &lt;/span&gt;81D01C4E399FFEDEDBD93D7F08390B7DF2FC1876
&lt;span class="go"&gt;gpg: data source: http://185.125.188.27:11371&lt;/span&gt;
&lt;span class="gp gp-VirtualEnv"&gt;(1)&lt;/span&gt;     &lt;span class="go"&gt;Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;          4096 bit RSA key 08390B7DF2FC1876, created: 2011-06-16&lt;/span&gt;
&lt;span class="go"&gt;Keys 1-1 of 1 for "81D01C4E399FFEDEDBD93D7F08390B7DF2FC1876".  Enter number(s), N)ext, or Q)uit &amp;gt; 1&lt;/span&gt;
&lt;span class="go"&gt;gpg: key 08390B7DF2FC1876: public key "Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt;" imported&lt;/span&gt;
&lt;span class="go"&gt;gpg: Total number processed: 1&lt;/span&gt;
&lt;span class="go"&gt;gpg:               imported: 1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After importing my public key, we verify the tag with &lt;code&gt;git tag --verify&lt;/code&gt; &lt;sup id="fnref2:1"&gt;&lt;a class="footnote-ref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;tag&lt;span class="w"&gt; &lt;/span&gt;--verify&lt;span class="w"&gt; &lt;/span&gt;v0.6.8
&lt;span class="go"&gt;object 52e4fd427b1aff902ef4e7bce9a9c2f6b358a5eb&lt;/span&gt;
&lt;span class="go"&gt;type commit&lt;/span&gt;
&lt;span class="go"&gt;tag v0.6.8&lt;/span&gt;
&lt;span class="go"&gt;tagger Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt; 1588947171 +0200&lt;/span&gt;

&lt;span class="go"&gt;target-isns v0.6.8&lt;/span&gt;
&lt;span class="go"&gt;gpg: Signature made Fri May  8 14:13:16 2020 UTC&lt;/span&gt;
&lt;span class="go"&gt;gpg:                using RSA key 81D01C4E399FFEDEDBD93D7F08390B7DF2FC1876&lt;/span&gt;
&lt;span class="go"&gt;gpg:                issuer "cvubrugier@example.org"&lt;/span&gt;
&lt;span class="go"&gt;gpg: Good signature from "Christophe Vu-Brugier &amp;lt;cvubrugier@example.org&amp;gt;" [unknown]&lt;/span&gt;
&lt;span class="go"&gt;gpg: WARNING: This key is not certified with a trusted signature!&lt;/span&gt;
&lt;span class="go"&gt;gpg:          There is no indication that the signature belongs to the owner.&lt;/span&gt;
&lt;span class="go"&gt;Primary key fingerprint: 81D0 1C4E 399F FEDE DBD9  3D7F 0839 0B7D F2FC 1876&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The tag signature is correct. We build a release tarball and compare
its checksum with the published release tarball.&lt;/p&gt;
&lt;div class="code"&gt;&lt;pre class="code literal-block"&gt;&lt;span class="gp"&gt;# &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;build
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;build/
&lt;span class="gp"&gt;$ &lt;/span&gt;cmake&lt;span class="w"&gt; &lt;/span&gt;..
&lt;span class="go"&gt;-- The C compiler identification is GNU 12.2.0&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compiler ABI info&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compiler ABI info - done&lt;/span&gt;
&lt;span class="go"&gt;-- Check for working C compiler: /usr/bin/cc - skipped&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compile features&lt;/span&gt;
&lt;span class="go"&gt;-- Detecting C compile features - done&lt;/span&gt;
&lt;span class="go"&gt;-- Configuring done&lt;/span&gt;
&lt;span class="go"&gt;-- Generating done&lt;/span&gt;
&lt;span class="go"&gt;-- Build files have been written to: /target-isns/build&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;dist
&lt;span class="go"&gt;Built target dist&lt;/span&gt;
&lt;span class="gp"&gt;$ &lt;/span&gt;sha256sum&lt;span class="w"&gt; &lt;/span&gt;target-isns-0.6.8.tar.xz
&lt;span class="go"&gt;544de09a2073242b21f6859841a5016c79c4006a53435a79b5cfc6602a59db97  target-isns-0.6.8.tar.xz&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The checksum (&lt;code&gt;544de09a&lt;/code&gt;) of the generated tarball matches the
checksum of the published tarball: we reproduce the release tarball.&lt;/p&gt;
&lt;p&gt;I reproduced the release tarball on Debian 12 (bookworm). Does that
work with other Linux distributions?&lt;/p&gt;
&lt;h2&gt;Reproducing the release tarball from other Linux distributions&lt;/h2&gt;
&lt;p&gt;I use &lt;a href="https://podman.io"&gt;Podman&lt;/a&gt; to build the release tarball in several
Linux containers.&lt;/p&gt;
&lt;table class="table table-striped"&gt;
&lt;thead&gt;&lt;tr&gt;
&lt;th&gt;Linux distribution&lt;/th&gt;
&lt;th&gt;Release tarball reproduced&lt;/th&gt;
&lt;th&gt;Git version&lt;/th&gt;
&lt;th&gt;XZ version&lt;/th&gt;
&lt;/tr&gt;&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Alpine 3.19.1&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.43.0&lt;/td&gt;
&lt;td&gt;5.4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="table-danger"&gt;
&lt;td&gt;Arch Linux&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;2.44.0&lt;/td&gt;
&lt;td&gt;5.6.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian 11 (bullseye)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.30.2&lt;/td&gt;
&lt;td&gt;5.2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debian 12 (bookworm)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.39.2&lt;/td&gt;
&lt;td&gt;5.4.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fedora 39&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.44.0&lt;/td&gt;
&lt;td&gt;5.4.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu 22.04 (jammy)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.34.1&lt;/td&gt;
&lt;td&gt;5.2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ubuntu 24.04 (noble, pre-release)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.43.0&lt;/td&gt;
&lt;td&gt;5.4.5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rocky Linux 8&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.39.3&lt;/td&gt;
&lt;td&gt;5.2.4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rocky Linux 9&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;2.39.3&lt;/td&gt;
&lt;td&gt;5.2.5&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The release tarball is reproducible everywhere except on Arch
Linux. The output of &lt;code&gt;git archive&lt;/code&gt; is the same but XZ compresses the
tarball differently. A binary search with &lt;code&gt;git bisect&lt;/code&gt; finds the
commit that introduce the change:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/tukaani-project/xz/commit/6daa4d0ea46a8441f21f609149f3633158bf4704"&gt;XZ: Use threaded mode by default (as if --threads=0 was used).&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;XZ (version 5.6.1) on Arch Linux compresses data with several threads
by default. With &lt;code&gt;--threads=1&lt;/code&gt;, we can force XZ to compress data with
just one thread. With that, we can reproduce the release tarball of
target-isns on Arch Linux.&lt;/p&gt;
&lt;p&gt;Usually, there are different ways for a compressor to represent the
original data. That's why a compressor output may differ when its algorithm
or parameters change. To reduce the risk of compressed tarballs
varying across platforms, target-isns releases could switch to a
compressor less likely to change such as
&lt;a href="https://www.gnu.org/software/gzip/"&gt;gzip&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Future work&lt;/h2&gt;
&lt;p&gt;While existing release tarballs of target-isns are reproducible, we
should improve the release process.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Document the release process.&lt;/dt&gt;
&lt;dd&gt;A document should describe how releases are made and how to
  reproduce them.&lt;/dd&gt;
&lt;dt&gt;Setup a continuous integration pipeline to generate the release tarball from a tag.&lt;/dt&gt;
&lt;dd&gt;A continuous integration job should automatically generate the
  release tarball from a tag. The job should run in a container (for
  example a &lt;a href="https://www.docker.com/"&gt;Docker&lt;/a&gt; image). With that, users
  could reproduce the release tarballs by running the same container
  image on their machine.&lt;/dd&gt;
&lt;dt&gt;Sign the release tarball with GnuPG.&lt;/dt&gt;
&lt;dd&gt;The maintainer should download the tarball generated by the release
  pipeline, reproduce it, and test it. Then, they should publish the
  release tarball, the SHA256 checksum file, and sign these files with
  their private GnuPG key.&lt;/dd&gt;
&lt;/dl&gt;
&lt;div class="caption"&gt;
  &lt;img src="https://www.enodev.fr/images/araignee-crabe.jpg" class="img-fluid rounded-circle" alt="A crab spider (Misumena vatia) caught a bee."&gt;
  &lt;p&gt;A crab spider (&lt;em&gt;&lt;a href="https://en.wikipedia.org/wiki/Misumena_vatia"&gt;Misumena vatia&lt;/a&gt;&lt;/em&gt;) caught a bee.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="footnote"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I removed my email from the command output. &lt;a class="footnote-backref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fnref:1" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;a class="footnote-backref" href="https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html#fnref2:1" title="Jump back to footnote 1 in the text"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><category>Git</category><category>GitHub</category><category>GnuPG</category><category>release</category><category>security</category><category>XZ</category><guid>https://www.enodev.fr/posts/xz-backdoor-lessons-reproducing-target-isns-release-tarballs.html</guid><pubDate>Sat, 13 Apr 2024 09:00:00 GMT</pubDate></item></channel></rss>