<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>sanecz</title><link>https://sanecz.net/</link><description>Lisa, aka sanecz. SRE, baremetal, automation and debugging.</description><language>en</language><generator>Hugo</generator><lastBuildDate>Sun, 18 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://sanecz.net/index.xml" rel="self" type="application/rss+xml"/><item><title>Livepatching the Linux kernel : through the eyes of an SRE</title><link>https://sanecz.net/blog/post-4/</link><guid isPermaLink="true">https://sanecz.net/blog/post-4/</guid><pubDate>Sun, 18 Oct 2026 00:00:00 +0000</pubDate><dc:creator>sanecz</dc:creator><category>kernel</category><category>livepatch</category><category>kvm</category><category>security</category><description>Rebooting a whole hypervisor fleet for every KVM escape isn't always an option. Writing a livepatch by hand for Januscape, letting kpatch-build do it for Pwnescape, and why cumulative patches matter.</description><content:encoded><![CDATA[<h2 id="introduction"><a class="h-anchor" href="#introduction" data-marks="##" aria-hidden="true" tabindex="-1"></a>Introduction</h2>
<p>Kernel live patching, or KLP, is a feature merged in the kernel 4.0 to apply fixes to the running kernel without requiring a reboot.</p>
<p>This might come in handy when you need to patch a bug or a security issue and cannot reboot the machine due to critical applications running on it, or need to minimize the downtime. KLP allows in-place patching of various functions in the kernel under some conditions, produces no service interruption (if patched properly) and is really fast to apply, but might require (a lot of) extra work.</p>
<p>In this article, we will first write a livepatch by hand for Januscape, let kpatch-build do the job for Pwnescape and then explore cumulative and atomic replaces with a Zapscape and Pwnescape.</p>
<p><em>Disclaimer: livepatches shown in this article were written and tested on my own test machines built for specific kernel versions. They are here to illustrate how livepatching works, not to be loaded on your production servers. If you do it anyway, keep in mind it may crash your server as our environments and kernel builds are not identical.
Use at your own risk, I’m not responsible for your hypervisors (I already have mine to deal with).</em></p>
<h2 id="livepatch"><a class="h-anchor" href="#livepatch" data-marks="##" aria-hidden="true" tabindex="-1"></a>Livepatch</h2>
<h3 id="how-does-it-work-"><a class="h-anchor" href="#how-does-it-work-" data-marks="###" aria-hidden="true" tabindex="-1"></a>How does it work ?</h3>
<p>Livepatch relies on ftrace. When the kernel is built with function tracing enabled, the compiler adds a call to <code>__fentry__</code> at the beginning of every function (-pg flag/-mfentry on x86_64). At boot, ftrace replaces all of these calls with a nop instruction, so they cost almost nothing when tracing is not used.</p>
<p>All the functions that can be traced are listed in <code>/sys/kernel/tracing/available_filter_functions</code>.</p>
<p>When a function is livepatched, KLP registers an ftrace handler on it, and the nop at the beginning of the old function becomes a call to this handler again. The handler then redirects the execution to the new function. The old function is still there and callers still call it, but it never runs its own code anymore.</p>
<figure class="img">
  <img class="only-light" src="https://sanecz.net/assets/livepatch/livepatch_light.png" alt="Diagram: callers still call the old function, its ftrace hook sends execution to the livepatch handler, which redirects to the new function, so the old function&#39;s body never runs." loading="lazy">
  
</figure>

<h3 id="requirements"><a class="h-anchor" href="#requirements" data-marks="###" aria-hidden="true" tabindex="-1"></a>Requirements</h3>
<p>For KLP to work, you&rsquo;ll need a kernel built with at least the following configuration: <code>CONFIG_MODULES</code> to enable loadable module support, <code>CONFIG_LIVEPATCH</code>/<code>CONFIG_HAVE_LIVEPATCH</code>, <code>CONFIG_KALLSYMS</code>,   <code>CONFIG_KALLSYMS_ALL</code>,  <code>CONFIG_DYNAMIC_FTRACE_WITH_REGS</code> or <code>CONFIG_DYNAMIC_FTRACE_WITH_ARGS</code>,  with a kernel version &gt;= 4.0.</p>
<p><code>CONFIG_HAVE_RELIABLE_STACKTRACE</code> provides reliable stack traces used by KLP to known when a task can be switched to the new function. Without it, the patching transition might never complete.</p>
<p><code>CONFIG_TRIM_UNUSED_KSYMS</code> should be disabled.</p>
<p>if <code>CONFIG_X86_KERNEL_IBT</code> is enabled, on Intel 11th gen+ CPUs and unless the kernel is booted with <code>ibt=off</code> or <code>ibt=warn</code>, calling functions resolved through <code>kallsyms_lookup_name</code> may trigger a control-protection exception and <a href="https://access.redhat.com/solutions/7131503">crash the host</a>. Also kpatch-build <a href="https://github.com/dynup/kpatch/pull/1411">does not support this option</a></p>
<p>The exact requirements depend on the architecture and kernel version. This is only a checklist for a x86_64 configuration used in this article, not a universal recipe.</p>
<figure class="code" data-lang="bash" aria-label="bash code"><figcaption><span class="code-lang" data-lang="bash" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy bash code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">grep -E <span class="s1">&#39;CONFIG_(LIVEPATCH|HAVE_LIVEPATCH|MODULES|KALLSYMS|KALLSYMS_ALL|TRIM_UNUSED_KSYMS|DYNAMIC_FTRACE_WITH_(REGS|ARGS)|UNWINDER_ORC|HAVE_RELIABLE_STACKTRACE|X86_KERNEL_IBT)[= ]&#39;</span> /boot/config-<span class="k">$(</span>uname -r<span class="k">)</span></span></span></code></pre></div></figure><h3 id="limitations"><a class="h-anchor" href="#limitations" data-marks="###" aria-hidden="true" tabindex="-1"></a>Limitations</h3>
<p>Only traced functions can be patched. This avoids issues like trying to patch functions implemented for ftrace itself or low-level architecture code. Tracing can be disabled per file by removing the function profiling flag from the compilation:</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">arch/x86/kernel/Makefile:CFLAGS_REMOVE_kvmclock.o = -pg
</span></span><span class="line"><span class="cl">arch/x86/kernel/Makefile:CFLAGS_REMOVE_ftrace.o = -pg
</span></span><span class="line"><span class="cl">arch/x86/kernel/Makefile:CFLAGS_REMOVE_early_printk.o = -pg
</span></span><span class="line"><span class="cl">arch/x86/kernel/Makefile:CFLAGS_REMOVE_head64.o = -pg
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">arch/arm64/kernel/Makefile:CFLAGS_REMOVE_ftrace.o = $(CC_FLAGS_FTRACE)
</span></span><span class="line"><span class="cl">arch/arm64/kernel/Makefile:CFLAGS_REMOVE_insn.o = $(CC_FLAGS_FTRACE)
</span></span><span class="line"><span class="cl">arch/arm64/kernel/Makefile:CFLAGS_REMOVE_return_address.o = $(CC_FLAGS_FTRACE)</span></span></code></pre></div></figure><p>Functions using the notrace macro cannot be patched either as they won&rsquo;t be available to ftrace, same for the inline functions because the kernel redefines inline and adds the notrace macro:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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 inline inline __gnu_inline __inline_maybe_unused notrace</span></span></span></code></pre></div></figure><p>Patching functions inlined by the compiler requires more tricks: the function doesn&rsquo;t exist as a standalone function anymore and its code is copied into each caller, so patching it means patching all of its callers instead.</p>
<p>The livepatch replaces the original function with your new one, so you cannot call the original function from your patch. The call would go through its ftrace hook, then be redirected to the new function, creating an infinite recursion. You&rsquo;ll have to copy the original code and modify it.</p>
<p>It only replace code, not data. If a fix changes a structure (adding a new field, bigger array etc etc), it can&rsquo;t be patched as-is, the existing objects were allocated with the old structure layout, and the rest of the kernel keep using it. There is some workaround using shadow variables (<code>klp_shadow_alloc()</code>, <code>klp_shadow_get()</code>). It attaches extra data to an existing object stored on the side of the structure instead of inside of it. It does work but the livepatch is considerably harder to write.</p>
<p>Tracing tools are also affected. Since the original function never runs its own code anymore, kprobes placed inside its body (systemtap, perf probe, bpftrace <code>kprobe</code> probes with an offset) stop firing once it&rsquo;s patched. A probe on the function entry shares the same ftrace call site as livepatch, it may still fire before the redirection, but it only tells you the old function was called, not that its code ran. Probes have to target the new function instead, those living in the livepatch module.</p>
<p>kretprobes can conflict with livepatch, as both rely on an ftrace handler that modifies the return address. Only one of them can acquire the handler: &ldquo;the first user wins&rdquo; and the other one is rejected.</p>
<h2 id="writing-a-livepatch-by-hand"><a class="h-anchor" href="#writing-a-livepatch-by-hand" data-marks="##" aria-hidden="true" tabindex="-1"></a>Writing a livepatch by hand</h2>
<h3 id="anatomy-of-a-livepatch"><a class="h-anchor" href="#anatomy-of-a-livepatch" data-marks="###" aria-hidden="true" tabindex="-1"></a>Anatomy of a livepatch</h3>
<p>A livepatch is just a kernel module with three structures that define the behavior of the patch, and your replacement code. Those nested structures define what to patch, where to patch and the initialization. This base livepatch comes from <code>samples/livepatch/livepatch-sample.c</code>.</p>
<p>Mandatory includes:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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;linux/module.h&gt; /* THIS_MODULE, MODULE_LICENSE, MODULE_INFO, module_init, module_exit */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;linux/livepatch.h&gt; /* klp_func, klp_object, klp_patch, klp_enable_patch */</span></span></span></code></pre></div></figure><p>Your new function, where KLP redirects callers to in place of the original:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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;linux/seq_file.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">livepatch_cmdline_proc_show</span><span class="p">(</span><span class="k">struct</span> <span class="n">seq_file</span> <span class="o">*</span><span class="n">m</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">v</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">seq_printf</span><span class="p">(</span><span class="n">m</span><span class="p">,</span> <span class="s">&#34;%s</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="s">&#34;this has been live patched&#34;</span><span class="p">);</span>
</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></figure><p><code>struct klp_func</code>: tells KLP which function to replace with the new one. Since funcs[] is an array, it can contain multiple functions to be replaced.</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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="k">struct</span> <span class="n">klp_func</span> <span class="n">funcs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">.</span><span class="n">old_name</span> <span class="o">=</span> <span class="s">&#34;cmdline_proc_show&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="p">.</span><span class="n">new_func</span> <span class="o">=</span> <span class="n">livepatch_cmdline_proc_show</span><span class="p">,</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">}</span>   <span class="cm">/* NULL terminator — required */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div></figure><p><code>struct klp_object</code>: tells KLP where the functions live.</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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="k">struct</span> <span class="n">klp_object</span> <span class="n">objs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="p">.</span><span class="n">funcs</span> <span class="o">=</span> <span class="n">funcs</span><span class="p">,</span> <span class="p">},</span> <span class="cm">/*  .name = NULL means vmlinux */</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">}</span>   <span class="cm">/* NULL terminator */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div></figure><p><code>struct klp_patch</code>: top-level structure, ties your struct klp_object to this livepatch module.</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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="k">struct</span> <span class="n">klp_patch</span> <span class="n">patch</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">mod</span> <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">objs</span> <span class="o">=</span> <span class="n">objs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div></figure><p>And finally, the init/exit and <code>MODULE_INFO</code>, otherwise it wouldn&rsquo;t be a working kernel module. <code>livepatch_init()</code> will call <code>klp_enable_patch()</code> to start the patching transition.</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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">livepatch_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span> <span class="nf">klp_enable_patch</span><span class="p">(</span><span class="o">&amp;</span><span class="n">patch</span><span class="p">);</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">livepatch_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="nf">module_init</span><span class="p">(</span><span class="n">livepatch_init</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">module_exit</span><span class="p">(</span><span class="n">livepatch_exit</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">MODULE_LICENSE</span><span class="p">(</span><span class="s">&#34;GPL&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">MODULE_INFO</span><span class="p">(</span><span class="n">livepatch</span><span class="p">,</span> <span class="s">&#34;Y&#34;</span><span class="p">);</span></span></span></code></pre></div></figure><p><code>klp_enable_patch()</code> will return before the patching is finished. There is a transition period where KLP switches tasks from the old function to our newly patched function, only when the task is no longer inside the old function. The same process applies when removing the patch: we first need to unpatch, wait for the transition, then unload the module.</p>
<p>To build this module, we will use the following Makefile:</p>
<figure class="code" data-lang="makefile" aria-label="makefile code"><figcaption><span class="code-lang" data-lang="makefile" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy makefile code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">obj-m</span> <span class="o">:=</span> livepatch-cmdline.o
</span></span><span class="line"><span class="cl"><span class="c"># KDIR will point to the build tree of the running kernel, you might need to install linux-headers-$(uname -r)
</span></span></span><span class="line"><span class="cl"><span class="nv">KDIR</span> <span class="o">:=</span> /lib/modules/<span class="k">$(</span>shell uname -r<span class="k">)</span>/build
</span></span><span class="line"><span class="cl"><span class="nf">all</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>MAKE<span class="k">)</span> -C <span class="k">$(</span>KDIR<span class="k">)</span> <span class="nv">M</span><span class="o">=</span><span class="k">$(</span>PWD<span class="k">)</span> modules
</span></span><span class="line"><span class="cl"><span class="nf">clean</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">	<span class="k">$(</span>MAKE<span class="k">)</span> -C <span class="k">$(</span>KDIR<span class="k">)</span> <span class="nv">M</span><span class="o">=</span><span class="k">$(</span>PWD<span class="k">)</span> clean
</span></span></code></pre></div></figure><p>Then run make, which will produce livepatch-cmdline.ko, then load the module:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> make
</span></span><span class="line"><span class="cl"><span class="go">make -C /lib/modules/6.2.0-39-generic/build M=/home/lisa modules
</span></span></span><span class="line"><span class="cl"><span class="go">make[1]: Entering directory &#39;/usr/src/linux-headers-6.2.0-39-generic&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">  CC [M]  /home/lisa/livepatch-cmdline.o
</span></span></span><span class="line"><span class="cl"><span class="go">  MODPOST /home/lisa/Module.symvers
</span></span></span><span class="line"><span class="cl"><span class="go">  CC [M]  /home/lisa/livepatch-cmdline.mod.o
</span></span></span><span class="line"><span class="cl"><span class="go">  LD [M]  /home/lisa/livepatch-cmdline.ko
</span></span></span><span class="line"><span class="cl"><span class="go">  BTF [M] /home/lisa/livepatch-cmdline.ko
</span></span></span><span class="line"><span class="cl"><span class="go">Skipping BTF generation for /home/lisa/livepatch-cmdline.ko due to unavailability of vmlinux
</span></span></span><span class="line"><span class="cl"><span class="go">make[1]: Leaving directory &#39;/usr/src/linux-headers-6.2.0-39-generic&#39;
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> insmod livepatch-cmdline.ko
</span></span></code></pre></div></figure><p>With dmesg we can follow the status of the patching transition:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> dmesg
</span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">[79191.929967] livepatch: &#39;livepatch_cmdline&#39;: starting patching transition
</span></span></span><span class="line"><span class="cl"><span class="go">[79192.818083] livepatch: &#39;livepatch_cmdline&#39;: patching complete
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> cat /proc/cmdline
</span></span><span class="line"><span class="cl"><span class="go">this have been livepatched
</span></span></span></code></pre></div></figure><p><code>livepatch-cmdline.ko</code> becomes livepatch_cmdline because the kernel replaces the dashes in module names with underscores.</p>
<p>Loading the patch is not enough to be sure that the livepatch took effect. Looking at /sys/kernel/livepatch/ will give more data about the state of the livepatch:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> ls /sys/kernel/livepatch/livepatch_cmdline/
</span></span><span class="line"><span class="cl"><span class="go">enabled     force       transition  vmlinux/
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> cat /sys/kernel/livepatch/livepatch_cmdline/enabled
</span></span><span class="line"><span class="cl"><span class="go">1
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> cat /sys/kernel/livepatch/livepatch_cmdline/vmlinux/patched
</span></span><span class="line"><span class="cl"><span class="go">1
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> ls  /sys/kernel/livepatch/livepatch_cmdline/vmlinux/
</span></span><span class="line"><span class="cl"><span class="go">cmdline_proc_show,1  patched
</span></span></span></code></pre></div></figure><p><code>livepatch_cmdline/enabled</code> returning 1 means the livepatch is active, then vmlinux is our patched object and the function patched is cmdline_proc_show.</p>
<p>If <code>/sys/kernel/livepatch/livepatch_cmdline/transition</code> stays at 1, it means that the transition isn&rsquo;t done yet. Either a task is still sleeping inside the old function and will be switched once it returns, or it&rsquo;s a long-running task that never leaves the old function. The blocking tasks can be found with <code>/proc/&lt;pid&gt;/patch_state</code>, which shows 0 while a task is still unpatched. The kernel automatically sends them a fake signal to try to make them leave the old function, which might help some tasks but not those that never leave the old function. In that case, the transition might never complete.</p>
<p>Disabling the livepatch:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> <span class="nb">echo</span> <span class="m">0</span> &gt;/sys/kernel/livepatch/livepatch_cmdline/enabled
</span></span><span class="line"><span class="cl"><span class="gp">#</span> rmmod livepatch-cmdline
</span></span><span class="line"><span class="cl"><span class="gp">#</span> dmesg
</span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">[79397.945201] livepatch: &#39;livepatch_cmdline&#39;: starting unpatching transition
</span></span></span><span class="line"><span class="cl"><span class="go">[79399.862745] livepatch: &#39;livepatch_cmdline&#39;: unpatching complete
</span></span></span></code></pre></div></figure><p>The directory <code>/sys/kernel/livepatch/livepatch_cmdline</code> also contains a <code>force</code> file. Writing 1 to it marks all the remaining task as patched to end a stuck transition. If you force it, a task may still be running the old code, and the module can no longer be removed afterwards. This totally bypass the task-consistency checks and other alternatives should really be considered before using the <code>force</code> file.</p>
<h3 id="livepatching-januscape"><a class="h-anchor" href="#livepatching-januscape" data-marks="###" aria-hidden="true" tabindex="-1"></a>Livepatching Januscape</h3>
<p>Januscape is the second of three KVM escapes reported by <a href="https://x.com/v4bel">V4Bel</a>. According to V4bel, it is the first KVM/x86 vulnerability that can trigger either on Intel or AMD architectures. This exploit, correctly executed, might allow an attacker to execute code on the host and the available POC cause denial of service by triggering a kernel panic on the host.</p>
<p>To write the Januscape livepatch, we will need to implement two fixes from two separate commits: <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb">81ccda30b4e8</a> and <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=0cb2af2ea66ad8ff195c156ea690f11216285bdf">0cb2af2ea66ad</a>.</p>
<p>The first commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb">81ccda30b4e8</a> patches <code>kvm_mmu_get_child_sp</code>:</p>
<figure class="code" data-lang="diff" aria-label="diff code"><figcaption><span class="code-lang" data-lang="diff" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy diff code"></button></figcaption><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/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
</span></span></span><span class="line"><span class="cl"><span class="gh">index 9368a71336fe4..c13b80fe3125b 100644
</span></span></span><span class="line"><span class="cl"><span class="gd">--- a/arch/x86/kvm/mmu/mmu.c
</span></span></span><span class="line"><span class="cl"><span class="gi">+++ b/arch/x86/kvm/mmu/mmu.c
</span></span></span><span class="line"><span class="cl"><span class="gu">@@ -2459,13 +2459,15 @@ static struct kvm_mmu_page *kvm_mmu_get_child_sp(struct kvm_vcpu *vcpu,
</span></span></span><span class="line"><span class="cl"> 						 u64 *sptep, gfn_t gfn,
</span></span><span class="line"><span class="cl"> 						 bool direct, unsigned int access)
</span></span><span class="line"><span class="cl"> {
</span></span><span class="line"><span class="cl"><span class="gd">-	union kvm_mmu_page_role role;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	union kvm_mmu_page_role role = kvm_mmu_child_role(sptep, direct, access);
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gd">-	if (is_shadow_present_pte(*sptep) &amp;&amp; !is_large_pte(*sptep) &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gd">-	    spte_to_child_sp(*sptep) &amp;&amp; spte_to_child_sp(*sptep)-&gt;gfn == gfn)
</span></span></span><span class="line"><span class="cl"><span class="gi">+	if (is_shadow_present_pte(*sptep) &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	    !is_large_pte(*sptep) &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	    spte_to_child_sp(*sptep) &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	    spte_to_child_sp(*sptep)-&gt;gfn == gfn &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	    spte_to_child_sp(*sptep)-&gt;role.word == role.word)
</span></span></span><span class="line"><span class="cl"> 		return ERR_PTR(-EEXIST);
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gd">-	role = kvm_mmu_child_role(sptep, direct, access);
</span></span></span><span class="line"><span class="cl"> 	return kvm_mmu_get_shadow_page(vcpu, gfn, role);
</span></span><span class="line"><span class="cl"> }
</span></span></code></pre></div></figure><p>Before writing any code, we need to ensure that the function we want to replace actually exists and can be traced by ftrace:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> grep -E <span class="s1">&#39; kvm_mmu_get_child_sp&#39;</span> /proc/kallsyms
</span></span><span class="line"><span class="cl"><span class="go">ffffffffc10c1900 t kvm_mmu_get_child_sp	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> grep -E <span class="s1">&#39;^kvm_mmu_get_child_sp &#39;</span> /sys/kernel/tracing/available_filter_functions
</span></span><span class="line"><span class="cl"><span class="go">kvm_mmu_get_child_sp [kvm]
</span></span></span></code></pre></div></figure><p>Note that KLP doesn&rsquo;t care whether <code>kvm_mmu_get_child_sp</code> is exported: the livepatch core finds the function to replace by looking it up in kallsyms itself.
However, if the compiler inlined the function into some of its callers, those callers would have to be patched too. It can be checked by looking at the disassembly of the module.</p>
<p>We will start with a naive implementation of this function in our livepatch. The struct <code>klp_object</code> will now contain the kvm module to patch instead of <code>NULL</code>, as we are not patching vmlinux functions anymore.</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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 pr_fmt(fmt) KBUILD_MODNAME &#34;: &#34; fmt
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;linux/module.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;linux/kernel.h&gt; /* pr_info, pr_err */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;linux/livepatch.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;linux/kvm_host.h&gt; /* struct kvm_vcpu */</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="nf">livepatch_kvm_mmu_get_child_sp</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm_vcpu</span> <span class="o">*</span><span class="n">vcpu</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="kt">gfn_t</span> <span class="n">gfn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="kt">bool</span> <span class="n">direct</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">access</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">union</span> <span class="n">kvm_mmu_page_role</span> <span class="n">role</span> <span class="o">=</span> <span class="nf">kvm_mmu_child_role</span><span class="p">(</span><span class="n">sptep</span><span class="p">,</span> <span class="n">direct</span><span class="p">,</span> <span class="n">access</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">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="o">!</span><span class="nf">is_large_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">gfn</span> <span class="o">==</span> <span class="n">gfn</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">role</span><span class="p">.</span><span class="n">word</span> <span class="o">==</span> <span class="n">role</span><span class="p">.</span><span class="n">word</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nf">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EEXIST</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">kvm_mmu_get_shadow_page</span><span class="p">(</span><span class="n">vcpu</span><span class="p">,</span> <span class="n">gfn</span><span class="p">,</span> <span class="n">role</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="k">static</span> <span class="k">struct</span> <span class="n">klp_func</span> <span class="n">funcs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">.</span><span class="n">old_name</span> <span class="o">=</span> <span class="s">&#34;kvm_mmu_get_child_sp&#34;</span><span class="p">,</span> <span class="p">.</span><span class="n">new_func</span> <span class="o">=</span> <span class="n">livepatch_kvm_mmu_get_child_sp</span><span class="p">,</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <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="k">static</span> <span class="k">struct</span> <span class="n">klp_object</span> <span class="n">objs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">&#34;kvm&#34;</span><span class="p">,</span> <span class="p">.</span><span class="n">funcs</span> <span class="o">=</span> <span class="n">funcs</span><span class="p">,</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <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="k">static</span> <span class="k">struct</span> <span class="n">klp_patch</span> <span class="n">patch</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">mod</span> <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span> <span class="p">.</span><span class="n">objs</span> <span class="o">=</span> <span class="n">objs</span><span class="p">,</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="nf">livepatch_init</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">       <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">       <span class="n">ret</span> <span class="o">=</span> <span class="nf">klp_enable_patch</span><span class="p">(</span><span class="o">&amp;</span><span class="n">patch</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">ret</span><span class="p">)</span> <span class="p">{</span> <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;klp_enable_patch failed: %d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">       <span class="nf">pr_info</span><span class="p">(</span><span class="s">&#34;CVE-2026-53359 patch applied</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">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><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">livepatch_exit</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{}</span>
</span></span><span class="line"><span class="cl"><span class="nf">module_init</span><span class="p">(</span><span class="n">livepatch_init</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">module_exit</span><span class="p">(</span><span class="n">livepatch_exit</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">MODULE_DESCRIPTION</span><span class="p">(</span><span class="s">&#34;CVE-2026-53359 (Januscape) livepatch&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">MODULE_LICENSE</span><span class="p">(</span><span class="s">&#34;GPL&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="nf">MODULE_INFO</span><span class="p">(</span><span class="n">livepatch</span><span class="p">,</span> <span class="s">&#34;Y&#34;</span><span class="p">);</span></span></span></code></pre></div></figure>
</details><p>Unfortunately, if we try to build this module, the compiler will throw a bunch of errors as none of the functions like kvm_mmu_child_role are declared anywhere.</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">[...]</span>
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lisa</span><span class="o">/</span><span class="n">livepatch</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span> <span class="n">In</span> <span class="n">function</span> <span class="err">‘</span><span class="n">livepatch_kvm_mmu_get_child_sp</span><span class="err">’</span><span class="o">:</span>
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lisa</span><span class="o">/</span><span class="n">livepatch</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">11</span><span class="o">:</span><span class="mi">36</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span> <span class="n">implicit</span> <span class="n">declaration</span> <span class="n">of</span> <span class="n">function</span> <span class="err">‘</span><span class="n">kvm_mmu_child_role</span><span class="err">’</span> <span class="p">[</span><span class="o">-</span><span class="n">Werror</span><span class="o">=</span><span class="n">implicit</span><span class="o">-</span><span class="n">function</span><span class="o">-</span><span class="n">declaration</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">   <span class="mi">11</span> <span class="o">|</span>     <span class="k">union</span> <span class="n">kvm_mmu_page_role</span> <span class="n">role</span> <span class="o">=</span> <span class="nf">kvm_mmu_child_role</span><span class="p">(</span><span class="n">sptep</span><span class="p">,</span> <span class="n">direct</span><span class="p">,</span> <span class="n">access</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">      <span class="o">|</span>                                    <span class="o">^~~~~~~~~~~~~~~~~~</span>
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lisa</span><span class="o">/</span><span class="n">livepatch</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">11</span><span class="o">:</span><span class="mi">36</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span> <span class="n">invalid</span> <span class="n">initializer</span>
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lisa</span><span class="o">/</span><span class="n">livepatch</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">13</span><span class="o">:</span><span class="mi">9</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span> <span class="n">implicit</span> <span class="n">declaration</span> <span class="n">of</span> <span class="n">function</span> <span class="err">‘</span><span class="n">is_shadow_present_pte</span><span class="err">’</span> <span class="p">[</span><span class="o">-</span><span class="n">Werror</span><span class="o">=</span><span class="n">implicit</span><span class="o">-</span><span class="n">function</span><span class="o">-</span><span class="n">declaration</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">   <span class="mi">13</span> <span class="o">|</span>     <span class="k">if</span> <span class="p">(</span><span class="nf">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">      <span class="o">|</span>         <span class="o">^~~~~~~~~~~~~~~~~~~~~</span>
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lisa</span><span class="o">/</span><span class="n">livepatch</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">14</span><span class="o">:</span><span class="mi">10</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span> <span class="n">implicit</span> <span class="n">declaration</span> <span class="n">of</span> <span class="n">function</span> <span class="err">‘</span><span class="n">is_large_pte</span><span class="err">’</span> <span class="p">[</span><span class="o">-</span><span class="n">Werror</span><span class="o">=</span><span class="n">implicit</span><span class="o">-</span><span class="n">function</span><span class="o">-</span><span class="n">declaration</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">   <span class="mi">14</span> <span class="o">|</span>         <span class="o">!</span><span class="nf">is_large_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">      <span class="o">|</span>          <span class="o">^~~~~~~~~~~~</span>
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lisa</span><span class="o">/</span><span class="n">livepatch</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">15</span><span class="o">:</span><span class="mi">9</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span> <span class="n">implicit</span> <span class="n">declaration</span> <span class="n">of</span> <span class="n">function</span> <span class="err">‘</span><span class="n">spte_to_child_sp</span><span class="err">’</span> <span class="p">[</span><span class="o">-</span><span class="n">Werror</span><span class="o">=</span><span class="n">implicit</span><span class="o">-</span><span class="n">function</span><span class="o">-</span><span class="n">declaration</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">   <span class="mi">15</span> <span class="o">|</span>         <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">      <span class="o">|</span>         <span class="o">^~~~~~~~~~~~~~~~</span>
</span></span><span class="line"><span class="cl"><span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">lisa</span><span class="o">/</span><span class="n">livepatch</span><span class="p">.</span><span class="nl">c</span><span class="p">:</span><span class="mi">16</span><span class="o">:</span><span class="mi">33</span><span class="o">:</span> <span class="nl">error</span><span class="p">:</span> <span class="n">invalid</span> <span class="n">type</span> <span class="n">argument</span> <span class="n">of</span> <span class="err">‘</span><span class="o">-&gt;</span><span class="err">’</span> <span class="p">(</span><span class="n">have</span> <span class="err">‘</span><span class="kt">int</span><span class="err">’</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="mi">16</span> <span class="o">|</span>         <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">gfn</span> <span class="o">==</span> <span class="n">gfn</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl"><span class="p">[...]</span></span></span></code></pre></div></figure><p>The functions <code>is_shadow_present_pte</code>, <code>is_large_pte</code>, <code>spte_to_child_sp</code> and the <code>kvm_mmu_page_role</code> union are declared in <code>arch/x86/kvm/mmu/mmu_internal.h</code> and <code>arch/x86/kvm/mmu/spte.h</code>. To be able to use those headers, we also need to specify the include path. Note that those headers are subsystem internals and are not available via linux-headers.</p>
<p>Also, the source tree must match the running kernel <strong>exactly</strong>. Same version, same config. Otherwise some structures might have a different layout, the livepatch will still build, but read and write at the wrong fields once loaded.</p>
<p>We also need to declare a few symbols/macros that are used inside those headers (but declared in <code>mmu.h</code>).
Makefile:</p>
<figure class="code" data-lang="makefile" aria-label="makefile code"><figcaption><span class="code-lang" data-lang="makefile" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy makefile code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-makefile" data-lang="makefile"><span class="line"><span class="cl"><span class="nv">ccflags-y</span> <span class="o">:=</span> -I/usr/src/linux-source-6.2.0/arch/x86/kvm/mmu
</span></span></code></pre></div></figure><p>livepatch.c:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">extern</span> <span class="kt">bool</span> <span class="n">enable_mmio_caching</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;mmu_internal.h&#34;   /* arch/x86/kvm/mmu/mmu_internal.h */</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&#34;spte.h&#34;           /* arch/x86/kvm/mmu/spte.h */</span></span></span></code></pre></div></figure><p>The functions <code>kvm_mmu_get_shadow_page</code> and <code>kvm_mmu_child_role</code> are static and not declared in any header, so they are not exported. We need to find those symbols elsewhere, for example inside the kernel symbol table: <code>/proc/kallsyms</code>.</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> grep -E <span class="s1">&#39;kvm_mmu_child_role|kvm_mmu_get_shadow_page&#39;</span> /proc/kallsyms
</span></span><span class="line"><span class="cl"><span class="go">ffffffffc10b91d0 t kvm_mmu_child_role	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10c0950 t __kvm_mmu_get_shadow_page	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10b91c0 t __pfx_kvm_mmu_child_role	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10c0940 t __pfx___kvm_mmu_get_shadow_page	[kvm]
</span></span></span></code></pre></div></figure><p><code>kvm_mmu_child_role</code> has the symbol type &ldquo;t&rdquo;, which means it&rsquo;s in the text section but not exported, so we cannot call it directly: the module loader only resolves exported symbols. We also have <code>__kvm_mmu_get_shadow_page</code> instead of <code>kvm_mmu_get_shadow_page</code>, but we will get to this later.</p>
<p>If a symbol does not appear, it means it&rsquo;s not resolvable, and it has probably been inlined and/or optimized away.</p>
<p>KLP actually has a proper way to do this: livepatch relocations. Instead of looking up the symbols ourselves, the livepatch module contains special relocation sections listing the non-exported symbols it needs, and the livepatch core resolves them itself when the patch is enabled, and again each time the target module is loaded. Unfortunately, these sections have a specific format that is not really meant to be written by hand, so we&rsquo;ll go with the kallsyms workaround for now (spoiler: kpatch-build generates them for us).</p>
<p>Using <code>kallsyms_lookup_name</code>, we are able to resolve a symbol name to a memory address. Unfortunately, it has been unexported since kernel 5.7. The common workaround is to plant a kprobe on the symbol <code>kallsyms_lookup_name</code> (which is still present in the kernel) to get the address of this function.</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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;linux/kprobes.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="p">[...]</span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="p">(</span><span class="o">*</span><span class="kt">kallsyms_lookup_name_t</span><span class="p">)(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">// defines the kprobe we want to plant
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">kprobe</span> <span class="n">kp</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">symbol_name</span> <span class="o">=</span> <span class="s">&#34;kallsyms_lookup_name&#34;</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 class="k">static</span> <span class="kt">int</span> <span class="nf">livepatch_init</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">kallsyms_lookup_name_t</span> <span class="n">my_kallsyms</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// register the probe
</span></span></span><span class="line"><span class="cl">	<span class="n">ret</span> <span class="o">=</span> <span class="nf">register_kprobe</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kp</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">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span> <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;kprobe failed: %d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">ret</span><span class="p">);</span> <span class="k">return</span> <span class="n">ret</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// retrieve the address of kallsyms_lookup_name
</span></span></span><span class="line"><span class="cl">	<span class="n">my_kallsyms</span> <span class="o">=</span> <span class="p">(</span><span class="kt">kallsyms_lookup_name_t</span><span class="p">)</span><span class="n">kp</span><span class="p">.</span><span class="n">addr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="c1">// release the probe
</span></span></span><span class="line"><span class="cl">	<span class="nf">unregister_kprobe</span><span class="p">(</span><span class="o">&amp;</span><span class="n">kp</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></figure>
</details><p>As <code>kvm_mmu_child_role</code> is a symbol present in the kernel, we will declare a function pointer with exactly the same signature as the original function, and store the address of kvm_mmu_child_role retrieved by <code>my_kallsyms</code> inside <code>fn_child_role</code>. Now we can replace the call to <code>kvm_mmu_child_role</code> inside <code>livepatch_kvm_mmu_get_child_sp</code> by <code>fn_child_role</code>.</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">[...]</span>
</span></span><span class="line"><span class="cl"><span class="k">typedef</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="p">(</span><span class="o">*</span><span class="kt">kallsyms_lookup_name_t</span><span class="p">)(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">kprobe</span> <span class="n">kp</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">symbol_name</span> <span class="o">=</span> <span class="s">&#34;kallsyms_lookup_name&#34;</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">union</span> <span class="nf">kvm_mmu_page_role</span> <span class="p">(</span><span class="o">*</span><span class="n">fn_child_role</span><span class="p">)(</span><span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">direct</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">access</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="nf">livepatch_kvm_mmu_get_child_sp</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm_vcpu</span> <span class="o">*</span><span class="n">vcpu</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="kt">gfn_t</span> <span class="n">gfn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="kt">bool</span> <span class="n">direct</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">access</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">union</span> <span class="n">kvm_mmu_page_role</span> <span class="n">role</span> <span class="o">=</span> <span class="nf">fn_child_role</span><span class="p">(</span><span class="n">sptep</span><span class="p">,</span> <span class="n">direct</span><span class="p">,</span> <span class="n">access</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></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="k">static</span> <span class="kt">int</span> <span class="nf">livepatch_init</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="n">fn_child_role</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="nf">my_kallsyms</span><span class="p">(</span><span class="s">&#34;kvm_mmu_child_role&#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="o">!</span><span class="n">fn_child_role</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	    <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;can&#39;t resolve kvm_mmu_child_role</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">return</span> <span class="o">-</span><span class="n">ENOENT</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="p">}</span></span></span></code></pre></div></figure>
</details><p>The last function to find is <code>kvm_mmu_get_shadow_page</code>. We can&rsquo;t resolve the symbol as seen earlier, which means we will need to find a workaround.
Looking at the code of <code>kvm_mmu_get_shadow_page</code>, it is a wrapper around <code>__kvm_mmu_get_shadow_page</code>, which is available through kallsyms.</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="nf">kvm_mmu_get_shadow_page</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm_vcpu</span> <span class="o">*</span><span class="n">vcpu</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                                    <span class="kt">gfn_t</span> <span class="n">gfn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                                                    <span class="k">union</span> <span class="n">kvm_mmu_page_role</span> <span class="n">role</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">shadow_page_caches</span> <span class="n">caches</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="p">.</span><span class="n">page_header_cache</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">arch</span><span class="p">.</span><span class="n">mmu_page_header_cache</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">.</span><span class="n">shadow_page_cache</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">arch</span><span class="p">.</span><span class="n">mmu_shadow_page_cache</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                <span class="p">.</span><span class="n">shadowed_info_cache</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">arch</span><span class="p">.</span><span class="n">mmu_shadowed_info_cache</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="k">return</span> <span class="nf">__kvm_mmu_get_shadow_page</span><span class="p">(</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">kvm</span><span class="p">,</span> <span class="n">vcpu</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">caches</span><span class="p">,</span> <span class="n">gfn</span><span class="p">,</span> <span class="n">role</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></figure>
</details><p>This function is not too complicated, which allows us to copy the code of kvm_mmu_get_shadow_page into our livepatch function.
Like <code>kvm_mmu_child_role</code>, the symbol for <code>__kvm_mmu_get_shadow_page</code> is available via kallsyms, so we will retrieve it the same way.</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// first declare the struct
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">shadow_page_caches</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">kvm_mmu_memory_cache</span> <span class="o">*</span><span class="n">page_header_cache</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">kvm_mmu_memory_cache</span> <span class="o">*</span><span class="n">shadow_page_cache</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">kvm_mmu_memory_cache</span> <span class="o">*</span><span class="n">shadowed_info_cache</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="c1">// prototype the __kvm_mmu_get_shadow_page
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="n">fn_get_shadow_page</span><span class="p">)(</span><span class="k">struct</span> <span class="n">kvm</span> <span class="o">*</span><span class="n">kvm</span><span class="p">,</span> <span class="k">struct</span> <span class="n">kvm_vcpu</span> <span class="o">*</span><span class="n">vcpu</span><span class="p">,</span> <span class="k">struct</span> <span class="n">shadow_page_caches</span> <span class="o">*</span><span class="n">caches</span><span class="p">,</span>  <span class="kt">gfn_t</span> <span class="n">gfn</span><span class="p">,</span> <span class="k">union</span> <span class="n">kvm_mmu_page_role</span> <span class="n">role</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 class="k">static</span> <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="nf">livepatch_kvm_mmu_get_child_sp</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm_vcpu</span> <span class="o">*</span><span class="n">vcpu</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="kt">gfn_t</span> <span class="n">gfn</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">                          <span class="kt">bool</span> <span class="n">direct</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">access</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">union</span> <span class="n">kvm_mmu_page_role</span> <span class="n">role</span> <span class="o">=</span> <span class="nf">fn_child_role</span><span class="p">(</span><span class="n">sptep</span><span class="p">,</span> <span class="n">direct</span><span class="p">,</span> <span class="n">access</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//  arch/x86/kvm/mmu/mmu.c:2226-2230
</span></span></span><span class="line"><span class="cl">    <span class="k">struct</span> <span class="n">shadow_page_caches</span> <span class="n">caches</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">page_header_cache</span>   <span class="o">=</span> <span class="o">&amp;</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">arch</span><span class="p">.</span><span class="n">mmu_page_header_cache</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">shadow_page_cache</span>   <span class="o">=</span> <span class="o">&amp;</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">arch</span><span class="p">.</span><span class="n">mmu_shadow_page_cache</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">shadowed_info_cache</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">arch</span><span class="p">.</span><span class="n">mmu_shadowed_info_cache</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="k">if</span> <span class="p">(</span><span class="nf">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="o">!</span><span class="nf">is_large_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">gfn</span> <span class="o">==</span> <span class="n">gfn</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="nf">spte_to_child_sp</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">role</span><span class="p">.</span><span class="n">word</span> <span class="o">==</span> <span class="n">role</span><span class="p">.</span><span class="n">word</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nf">ERR_PTR</span><span class="p">(</span><span class="o">-</span><span class="n">EEXIST</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// __kvm_mmu_get_shadow_page instead of kvm_mmu_get_shadow_page
</span></span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">fn_get_shadow_page</span><span class="p">(</span><span class="n">vcpu</span><span class="o">-&gt;</span><span class="n">kvm</span><span class="p">,</span> <span class="n">vcpu</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">caches</span><span class="p">,</span> <span class="n">gfn</span><span class="p">,</span> <span class="n">role</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="k">static</span> <span class="kt">int</span> <span class="nf">livepatch_init</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="c1">// retrieve __kvm_mmu_get_shadow_page via my_kallsyms
</span></span></span><span class="line"><span class="cl">	<span class="n">fn_get_shadow_page</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="nf">my_kallsyms</span><span class="p">(</span><span class="s">&#34;__kvm_mmu_get_shadow_page&#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="o">!</span><span class="n">fn_get_shadow_page</span><span class="p">)</span> <span class="p">{</span> <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;can&#39;t resolve __kvm_mmu_get_shadow_page</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span> <span class="k">return</span> <span class="o">-</span><span class="n">ENOENT</span><span class="p">;</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 class="p">}</span></span></span></code></pre></div></figure>
</details><p>Patching kvm_mmu_get_child_sp alone is not enough. The complete fix for Januscape is composed of two patches, and the second fix resides in <code>__link_shadow_page</code>. We must also check that <code>__link_shadow_page</code> can be patched:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> grep -E <span class="s1">&#39; __link_shadow_page\s&#39;</span> /proc/kallsyms
</span></span><span class="line"><span class="cl"><span class="go">ffffffffc10bf960 t __link_shadow_page	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> grep -E <span class="s1">&#39;^__link_shadow_page &#39;</span> /sys/kernel/tracing/available_filter_functions
</span></span><span class="line"><span class="cl"><span class="go">__link_shadow_page [kvm]
</span></span></span></code></pre></div></figure><p>Then the second patch (<a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=0cb2af2ea66ad8ff195c156ea690f11216285bdf">0cb2af2ea66ad</a>) should be implemented:</p>
<figure class="code" data-lang="diff" aria-label="diff code"><figcaption><span class="code-lang" data-lang="diff" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy diff code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-diff" data-lang="diff"><span class="line"><span class="cl"><span class="gu">@@ -2544,13 +2534,16 @@ static void __link_shadow_page(struct kvm *kvm,
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> 	BUILD_BUG_ON(VMX_EPT_WRITABLE_MASK != PT_WRITABLE_MASK);
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gd">-	/*
</span></span></span><span class="line"><span class="cl"><span class="gd">-	 * If an SPTE is present already, it must be a leaf and therefore
</span></span></span><span class="line"><span class="cl"><span class="gd">-	 * a large one.  Drop it, and flush the TLB if needed, before
</span></span></span><span class="line"><span class="cl"><span class="gd">-	 * installing sp.
</span></span></span><span class="line"><span class="cl"><span class="gd">-	 */
</span></span></span><span class="line"><span class="cl"><span class="gd">-	if (is_shadow_present_pte(*sptep))
</span></span></span><span class="line"><span class="cl"><span class="gd">-		drop_large_spte(kvm, sptep, flush);
</span></span></span><span class="line"><span class="cl"><span class="gi">+	if (is_shadow_present_pte(*sptep)) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+		struct kvm_mmu_page *parent_sp;
</span></span></span><span class="line"><span class="cl"><span class="gi">+		LIST_HEAD(invalid_list);
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+		parent_sp = sptep_to_sp(sptep);
</span></span></span><span class="line"><span class="cl"><span class="gi">+		WARN_ON_ONCE(parent_sp-&gt;role.level == PG_LEVEL_4K);
</span></span></span><span class="line"><span class="cl"><span class="gi">+
</span></span></span><span class="line"><span class="cl"><span class="gi">+		mmu_page_zap_pte(kvm, parent_sp, sptep, &amp;invalid_list);
</span></span></span><span class="line"><span class="cl"><span class="gi">+		kvm_mmu_remote_flush_or_zap(kvm, &amp;invalid_list, true);
</span></span></span><span class="line"><span class="cl"><span class="gi">+	}
</span></span></span></code></pre></div></figure><p>So we need to add a second klp_func for <code>__link_shadow_page</code> and copy the whole function, since we cannot call the original:</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">[...]</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="k">struct</span> <span class="n">klp_func</span> <span class="n">funcs</span><span class="p">[]</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">.</span><span class="n">old_name</span> <span class="o">=</span> <span class="s">&#34;kvm_mmu_get_child_sp&#34;</span><span class="p">,</span> <span class="p">.</span><span class="n">new_func</span> <span class="o">=</span> <span class="n">livepatch_kvm_mmu_get_child_sp</span><span class="p">,</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span> <span class="p">.</span><span class="n">old_name</span> <span class="o">=</span> <span class="s">&#34;__link_shadow_page&#34;</span><span class="p">,</span> <span class="p">.</span><span class="n">new_func</span> <span class="o">=</span> <span class="n">livepatch__link_shadow_page</span><span class="p">,</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <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="p">[...]</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">livepatch__link_shadow_page</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm</span> <span class="o">*</span><span class="n">kvm</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			       <span class="k">struct</span> <span class="n">kvm_mmu_memory_cache</span> <span class="o">*</span><span class="n">cache</span><span class="p">,</span> <span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			       <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="n">sp</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">flush</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">u64</span> <span class="n">spte</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">BUILD_BUG_ON</span><span class="p">(</span><span class="n">VMX_EPT_WRITABLE_MASK</span> <span class="o">!=</span> <span class="n">PT_WRITABLE_MASK</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">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="n">parent_sp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="nf">LIST_HEAD</span><span class="p">(</span><span class="n">invalid_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">parent_sp</span> <span class="o">=</span> <span class="nf">sptep_to_sp</span><span class="p">(</span><span class="n">sptep</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">WARN_ON_ONCE</span><span class="p">(</span><span class="n">parent_sp</span><span class="o">-&gt;</span><span class="n">role</span><span class="p">.</span><span class="n">level</span> <span class="o">==</span> <span class="n">PG_LEVEL_4K</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="nf">mmu_page_zap_pte</span><span class="p">(</span><span class="n">kvm</span><span class="p">,</span> <span class="n">parent_sp</span><span class="p">,</span> <span class="n">sptep</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">invalid_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">kvm_mmu_remote_flush_or_zap</span><span class="p">(</span><span class="n">kvm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">invalid_list</span><span class="p">,</span> <span class="nb">true</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="n">spte</span> <span class="o">=</span> <span class="nf">make_nonleaf_spte</span><span class="p">(</span><span class="n">sp</span><span class="o">-&gt;</span><span class="n">spt</span><span class="p">,</span> <span class="nf">sp_ad_disabled</span><span class="p">(</span><span class="n">sp</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">mmu_spte_set</span><span class="p">(</span><span class="n">sptep</span><span class="p">,</span> <span class="n">spte</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">mmu_page_add_parent_pte</span><span class="p">(</span><span class="n">cache</span><span class="p">,</span> <span class="n">sp</span><span class="p">,</span> <span class="n">sptep</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">sp</span><span class="o">-&gt;</span><span class="n">unsync_children</span> <span class="o">||</span> <span class="n">sp</span><span class="o">-&gt;</span><span class="n">unsync</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nf">mark_unsync</span><span class="p">(</span><span class="n">sptep</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></figure>
</details><p>We now have multiple symbols that we need to resolve or find a workaround for: <code>BUILD_BUG_ON</code>, <code>is_shadow_present_pte</code>, <code>LIST_HEAD</code>, <code>sptep_to_sp</code>, <code>WARN_ON_ONCE</code>, <code>PG_LEVEL_4K</code>, <code>mmu_page_zap_pte</code>, <code>kvm_mmu_remote_flush_or_zap</code>, <code>make_nonleaf_spte</code>, <code>sp_ad_disabled</code>, <code>mmu_spte_set</code>, <code>mmu_page_add_parent_pte</code>, <code>mark_unsync</code>.</p>
<ul>
<li><code>BUILD_BUG_ON</code> will be dropped, it is a macro only used for compile-time assertions to check masks.</li>
<li><code>LIST_HEAD</code>/<code>WARN_ON_ONCE</code> are generic kernel macros and <code>PG_LEVEL_4K</code> is an enum constant.</li>
</ul>
<p>Searching for symbols in <code>/proc/kallsyms</code>:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> grep -E <span class="s1">&#39;is_shadow_present_pte|sptep_to_sp|mmu_page_zap_pte|kvm_mmu_remote_flush_or_zap|make_nonleaf_spte|sp_ad_disabled|mmu_spte_set|mmu_page_add_parent_pte|mark_unsync&#39;</span> /proc/kallsyms
</span></span><span class="line"><span class="cl"><span class="go">ffffffffc10b92b0 t mark_unsync	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10ba7e0 t mmu_page_zap_pte	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10d7f47 t mmu_page_zap_pte.cold	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10b92a0 t __pfx_mark_unsync	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10ba7d0 t __pfx_mmu_page_zap_pte	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10c98d0 t __pfx_make_nonleaf_spte	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10c98e0 t make_nonleaf_spte	[kvm]
</span></span></span></code></pre></div></figure><p>Search for the remaining functions in the kernel source tree:</p>
<figure class="code" data-lang="bash" aria-label="bash code"><figcaption><span class="code-lang" data-lang="bash" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy bash code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">grep --include<span class="o">=</span><span class="s1">&#39;*.h&#39;</span> -rE <span class="s1">&#39;is_shadow_present_pte|sptep_to_sp|mmu_page_zap_pte|kvm_mmu_remote_flush_or_zap|make_nonleaf_spte|sp_ad_disabled|mmu_spte_set|mmu_page_add_parent_pte|mark_unsync&#39;</span> /usr/src/linux-source-6.2.0/arch/x86
</span></span><span class="line"><span class="cl">/usr/src/linux-source-6.2.0/arch/x86/kvm/mmu/spte.h:static inline struct kvm_mmu_page *sptep_to_sp<span class="o">(</span>u64 *sptep<span class="o">)</span>
</span></span><span class="line"><span class="cl">/usr/src/linux-source-6.2.0/arch/x86/kvm/mmu/spte.h:static inline bool is_shadow_present_pte<span class="o">(</span>u64 pte<span class="o">)</span>
</span></span><span class="line"><span class="cl">/usr/src/linux-source-6.2.0/arch/x86/kvm/mmu/spte.h:static inline bool sp_ad_disabled<span class="o">(</span>struct kvm_mmu_page *sp<span class="o">)</span>
</span></span><span class="line"><span class="cl">/usr/src/linux-source-6.2.0/arch/x86/kvm/mmu/spte.h:u64 make_nonleaf_spte<span class="o">(</span>u64 *child_pt, bool ad_disabled<span class="o">)</span><span class="p">;</span></span></span></code></pre></div></figure><p><em>(Probably a real IDE instead of me running a bare emacs and grep would have been more efficient for reading code.)</em></p>
<p>We will retrieve <code>mark_unsync</code>, <code>mmu_page_zap_pte</code> and <code>make_nonleaf_spte</code> via kallsyms.</p>
<ul>
<li><code>sptep_to_sp</code>, <code>is_shadow_present_pte</code> and <code>sp_ad_disabled</code> are static inline functions inside spte.h (included earlier for the first patch).</li>
<li><code>mmu_page_zap_pte.cold</code> is unlikely branch code split out by the compiler. As we do resolve the <code>mmu_page_zap_pte</code> symbol, we just ignore this part.</li>
</ul>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">[...]</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">fn_mmu_page_zap_pte</span><span class="p">)(</span><span class="k">struct</span> <span class="n">kvm</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="p">,</span> <span class="n">u64</span> <span class="o">*</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="nf">u64</span> <span class="p">(</span><span class="o">*</span><span class="n">fn_make_nonleaf_spte</span><span class="p">)(</span><span class="n">u64</span> <span class="o">*</span><span class="n">child_pt</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">ad_disabled</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">fn_mark_unsync</span><span class="p">)(</span><span class="n">u64</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></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">livepatch__link_shadow_page</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm</span> <span class="o">*</span><span class="n">kvm</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			       <span class="k">struct</span> <span class="n">kvm_mmu_memory_cache</span> <span class="o">*</span><span class="n">cache</span><span class="p">,</span> <span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			       <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="n">sp</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">flush</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">u64</span> <span class="n">spte</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">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</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></span><span class="line"><span class="cl">		<span class="nf">fn_mmu_page_zap_pte</span><span class="p">(</span><span class="n">kvm</span><span class="p">,</span> <span class="n">parent_sp</span><span class="p">,</span> <span class="n">sptep</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">invalid_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">kvm_mmu_remote_flush_or_zap</span><span class="p">(</span><span class="n">kvm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">invalid_list</span><span class="p">,</span> <span class="nb">true</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="n">spte</span> <span class="o">=</span> <span class="nf">fn_make_nonleaf_spte</span><span class="p">(</span><span class="n">sp</span><span class="o">-&gt;</span><span class="n">spt</span><span class="p">,</span> <span class="nf">sp_ad_disabled</span><span class="p">(</span><span class="n">sp</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="k">if</span> <span class="p">(</span><span class="n">sp</span><span class="o">-&gt;</span><span class="n">unsync_children</span> <span class="o">||</span> <span class="n">sp</span><span class="o">-&gt;</span><span class="n">unsync</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nf">fn_mark_unsync</span><span class="p">(</span><span class="n">sptep</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="k">static</span> <span class="kt">int</span> <span class="nf">livepatch_init</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="n">fn_mmu_page_zap_pte</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="nf">my_kallsyms</span><span class="p">(</span><span class="s">&#34;mmu_page_zap_pte&#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="o">!</span><span class="n">fn_mmu_page_zap_pte</span><span class="p">)</span> <span class="p">{</span> <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;can&#39;t resolve mmu_page_zap_pte</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span> <span class="k">return</span> <span class="o">-</span><span class="n">ENOENT</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">fn_make_nonleaf_spte</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="nf">my_kallsyms</span><span class="p">(</span><span class="s">&#34;make_nonleaf_spte&#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="o">!</span><span class="n">fn_make_nonleaf_spte</span><span class="p">)</span> <span class="p">{</span> <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;can&#39;t resolve make_nonleaf_spte</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span> <span class="k">return</span> <span class="o">-</span><span class="n">ENOENT</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">fn_mark_unsync</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="nf">my_kallsyms</span><span class="p">(</span><span class="s">&#34;mark_unsync&#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="o">!</span><span class="n">fn_mark_unsync</span><span class="p">)</span> <span class="p">{</span> <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;can&#39;t resolve mark_unsync</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span> <span class="k">return</span> <span class="o">-</span><span class="n">ENOENT</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="p">}</span></span></span></code></pre></div></figure>
</details><p><code>kvm_mmu_remote_flush_or_zap</code>, <code>mmu_spte_set</code> and <code>mmu_page_add_parent_pte</code> were inlined away and have no symbol. These functions have to be rebuilt from the source code.</p>
<p>The original <code>mmu_spte_set</code> is a wrapper around <code>__set_spte()</code>:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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">mmu_spte_set</span><span class="p">(</span><span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="n">u64</span> <span class="n">new_spte</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">WARN_ON</span><span class="p">(</span><span class="nf">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="nf">__set_spte</span><span class="p">(</span><span class="n">sptep</span><span class="p">,</span> <span class="n">new_spte</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="cp">#ifdef CONFIG_X86_64
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">__set_spte</span><span class="p">(</span><span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="n">u64</span> <span class="n">spte</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_ONCE</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="n">spte</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></figure><p>This allows us to skip this function and directly implement the <code>WARN_ON</code> + <code>WRITE_ONCE</code>.</p>
<p><code>mmu_page_add_parent_pte</code> is also a wrapper around <code>pte_list_add</code>, which is available via kallsyms and retrieved the same way as earlier.</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="c" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">[...]</span>
</span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="kt">void</span> <span class="nf">livepatch__link_shadow_page</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm</span> <span class="o">*</span><span class="n">kvm</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			       <span class="k">struct</span> <span class="n">kvm_mmu_memory_cache</span> <span class="o">*</span><span class="n">cache</span><span class="p">,</span> <span class="n">u64</span> <span class="o">*</span><span class="n">sptep</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			       <span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="n">sp</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">flush</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">u64</span> <span class="n">spte</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">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="k">struct</span> <span class="n">kvm_mmu_page</span> <span class="o">*</span><span class="n">parent_sp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="nf">LIST_HEAD</span><span class="p">(</span><span class="n">invalid_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="n">parent_sp</span> <span class="o">=</span> <span class="nf">sptep_to_sp</span><span class="p">(</span><span class="n">sptep</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">WARN_ON_ONCE</span><span class="p">(</span><span class="n">parent_sp</span><span class="o">-&gt;</span><span class="n">role</span><span class="p">.</span><span class="n">level</span> <span class="o">==</span> <span class="n">PG_LEVEL_4K</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">		<span class="nf">fn_mmu_page_zap_pte</span><span class="p">(</span><span class="n">kvm</span><span class="p">,</span> <span class="n">parent_sp</span><span class="p">,</span> <span class="n">sptep</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">invalid_list</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">kvm_mmu_remote_flush_or_zap</span><span class="p">(</span><span class="n">kvm</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">invalid_list</span><span class="p">,</span> <span class="nb">true</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="n">spte</span> <span class="o">=</span> <span class="nf">fn_make_nonleaf_spte</span><span class="p">(</span><span class="n">sp</span><span class="o">-&gt;</span><span class="n">spt</span><span class="p">,</span> <span class="nf">sp_ad_disabled</span><span class="p">(</span><span class="n">sp</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">WARN_ON</span><span class="p">(</span><span class="nf">is_shadow_present_pte</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="nf">WRITE_ONCE</span><span class="p">(</span><span class="o">*</span><span class="n">sptep</span><span class="p">,</span> <span class="n">spte</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">fn_pte_list_add</span><span class="p">(</span><span class="n">cache</span><span class="p">,</span> <span class="n">sptep</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sp</span><span class="o">-&gt;</span><span class="n">parent_ptes</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">sp</span><span class="o">-&gt;</span><span class="n">unsync_children</span> <span class="o">||</span> <span class="n">sp</span><span class="o">-&gt;</span><span class="n">unsync</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="nf">fn_mark_unsync</span><span class="p">(</span><span class="n">sptep</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="k">static</span> <span class="kt">int</span> <span class="nf">livepatch_init</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="n">fn_pte_list_add</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="nf">my_kallsyms</span><span class="p">(</span><span class="s">&#34;pte_list_add&#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="o">!</span><span class="n">fn_pte_list_add</span><span class="p">)</span> <span class="p">{</span> <span class="nf">pr_err</span><span class="p">(</span><span class="s">&#34;can&#39;t resolve pte_list_add</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">);</span> <span class="k">return</span> <span class="o">-</span><span class="n">ENOENT</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="p">}</span></span></span></code></pre></div></figure>
</details><p>The last function to rebuild is <code>kvm_mmu_remote_flush_or_zap</code>, defined as:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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">bool</span> <span class="nf">kvm_mmu_remote_flush_or_zap</span><span class="p">(</span><span class="k">struct</span> <span class="n">kvm</span> <span class="o">*</span><span class="n">kvm</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">					<span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">invalid_list</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">					<span class="kt">bool</span> <span class="n">remote_flush</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="o">!</span><span class="n">remote_flush</span> <span class="o">&amp;&amp;</span> <span class="nf">list_empty</span><span class="p">(</span><span class="n">invalid_list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="nb">false</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="o">!</span><span class="nf">list_empty</span><span class="p">(</span><span class="n">invalid_list</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">		<span class="nf">kvm_mmu_commit_zap_page</span><span class="p">(</span><span class="n">kvm</span><span class="p">,</span> <span class="n">invalid_list</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">kvm_flush_remote_tlbs</span><span class="p">(</span><span class="n">kvm</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></figure><p>Both symbols resolve, but <code>kvm_mmu_commit_zap_page.part.0</code> might be fragile to import: the function was split and partially inlined by the compiler. This might change (function not split, more parts, etc.) between compiler versions, optimizations and surrounding code:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> grep -E <span class="s1">&#39;kvm_mmu_commit_zap_page|kvm_flush_remote_tlbs&#39;</span> /proc/kallsyms
</span></span><span class="line"><span class="cl"><span class="go">ffffffffc10b6630 t kvm_mmu_commit_zap_page.part.0	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10b6620 t __pfx_kvm_mmu_commit_zap_page.part.0	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10bfcc0 t __pfx_kvm_flush_remote_tlbs_with_address	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc1059040 t kvm_flush_remote_tlbs	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc10bfcd0 t kvm_flush_remote_tlbs_with_address	[kvm]
</span></span></span><span class="line"><span class="cl"><span class="go">ffffffffc1059030 t __pfx_kvm_flush_remote_tlbs	[kvm]
</span></span></span></code></pre></div></figure><p>Here, the compiler moved the empty list check of <code>kvm_mmu_commit_zap_page</code> into its callers, so calling the .part.0 directly is only safe because we check that invalid_list is not empty before.  It is not even guaranteed to take the same arguments as the original function. In this case we also need to check its disassembly before calling it with the original prototype.</p>
<p>Finally, putting all the changes together, including the rebuild of <code>kvm_mmu_remote_flush_or_zap</code> results in a livepatch including functions from subsystem internal headers, symbols from kallsyms and some small rewrites. Full code can be found on <a href="https://github.com/sanecz/januscape-livepatch/blob/main/januscape-livepatch.c">Github</a>.</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> insmod ./livepatch.ko
</span></span><span class="line"><span class="cl"><span class="gp">#</span> dmesg -t <span class="p">|</span> tail -n4
</span></span><span class="line"><span class="cl"><span class="go">livepatch: enabling patch &#39;livepatch&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch: &#39;livepatch&#39;: starting patching transition
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch: CVE-2026-53359 patch applied
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch: &#39;livepatch&#39;: patching complete
</span></span></span></code></pre></div></figure><p>Here the livepatch is complete, but some issues remains. The function pointers are resolved once, when the livepatch is loaded. If we unload and reload the kvm module, those pointers will still point to the old module&rsquo;s addresses and the next call will crash the host. This could be worked around with <code>register_module_notifier</code>, by resolving the symbols again when kvm is loaded. Also if kvm is not loaded yet, the <code>livepatch_init()</code> would fails with <code>-ENOENT</code>.</p>
<p><code>kpatch-build</code> avoid this issue by letting the livepatch core resolve those symbols itself every time the module is loaded.</p>
<h2 id="generating-a-livepatch-with-kpatch-build"><a class="h-anchor" href="#generating-a-livepatch-with-kpatch-build" data-marks="##" aria-hidden="true" tabindex="-1"></a>Generating a livepatch with kpatch-build</h2>
<h3 id="building-a-patch-for-pwnescape"><a class="h-anchor" href="#building-a-patch-for-pwnescape" data-marks="###" aria-hidden="true" tabindex="-1"></a>Building a patch for Pwnescape</h3>
<div class="side">
  <div class="side-text">
    <p>Pwnescape is the latest known CVE at this time of writing, that allows a guest-host escape, found by <a href="https://pwn.ai">pwn.ai</a>. This one is a little different than the Januscape/Zapscape/ITscape trilogy, instead of exploiting KVM, it is using a deterministic TOCTOU vulnerability inside the kernel networking stack leading to a KVM escape. Full write-up can be found <a href="https://pwn.ai/blog/pwnescape">here</a>.</p>

  </div>
  <figure class="side-img">
    <img src="https://pbs.twimg.com/media/HU2zfbKW4AA4sfp?format=jpg" alt="Collage of status pages from eight hosting providers, all announcing emergency maintenance and host reboots for CVE-2026-99999 (Pwnescape)." loading="lazy">
    <figcaption>(Dear security guys, SRE speaking there: stop releasing big CVE on Thursday night/Friday OR during public holiday, pretty please, we do not enjoy this), <a href="https://x.com/PaulosYibelo/status/2111541156572013043">img source</a></figcaption>
  </figure>
</div>

<p>Unlike a livepatch written by hand, kpatch-build rebuilds the kernel with and without the fix and compares the result, so it needs the full source code of the running kernel, its config (with <code>CONFIG_DEBUG_INFO</code>) and the vmlinux with debug symbols.</p>
<p>You have to retrieve the debug symbols and the source code for your kernel:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> apt-get install linux-image-unsigned-<span class="k">$(</span>uname -r<span class="k">)</span>-dbgsym
</span></span><span class="line"><span class="cl"><span class="gp">#</span> apt-get <span class="nb">source</span> linux-image-unsigned-<span class="k">$(</span>uname -r<span class="k">)</span>
</span></span></code></pre></div></figure><p>Then install and build kpatch-build:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> git clone https://github.com/dynup/kpatch.git
</span></span><span class="line"><span class="cl"><span class="gp">#</span> <span class="nb">cd</span> kpatch <span class="o">&amp;&amp;</span> make <span class="o">&amp;&amp;</span> make install
</span></span></code></pre></div></figure><p>One naive approche to create the livepatch is to retreive the patch from the commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=6785011f8b16abf40b1e9d8b2e332232b3e68822">6785011f8b16</a> and start kpatch-build:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch-build -s ~/linux-src -c /boot/config-<span class="k">$(</span>uname -r<span class="k">)</span>   -v /usr/lib/debug/boot/vmlinux-<span class="k">$(</span>uname -r<span class="k">)</span> -n livepatch-pwnescape pwnescape.patch
</span></span><span class="line"><span class="cl"><span class="go">Using source directory at /home/lisa/linux-src
</span></span></span><span class="line"><span class="cl"><span class="go">Testing patch file(s)
</span></span></span><span class="line"><span class="cl"><span class="go">Reading special section data
</span></span></span><span class="line"><span class="cl"><span class="go">Building original source
</span></span></span><span class="line"><span class="cl"><span class="go">Building patched source
</span></span></span><span class="line"><span class="cl"><span class="go">Extracting new and modified ELF sections
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">LD [M] net/openvswitch/vport-gre.ko
</span></span></span><span class="line"><span class="cl"><span class="go">LD [M] net/vmw_vsock/vsock.ko
</span></span></span><span class="line"><span class="cl"><span class="go">LD [M] net/netfilter/nfnetlink_queue.ko
</span></span></span><span class="line"><span class="cl"><span class="go">LD [M] net/netfilter/xt_CHECKSUM.ko
</span></span></span><span class="line"><span class="cl"><span class="go">LD [M] net/bridge/netfilter/nf_conntrack_bridge.ko
</span></span></span><span class="line"><span class="cl"><span class="go">net/xfrm/xfrm_output.o: changed function: xfrm_output
</span></span></span><span class="line"><span class="cl"><span class="go">net/ipv4/ip_output.o: changed function: ip_do_fragment
</span></span></span><span class="line"><span class="cl"><span class="go">ERROR: drivers/net/ethernet/chelsio/cxgb4/t4_hw.o: reference to static local variable t4_decode.7 in CSWTCH.896 was removed
</span></span></span><span class="line"><span class="cl"><span class="go">create-diff-object: unreconcilable difference
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span></code></pre></div></figure><p>But kpatch failed on this run. Functions were correctly found but then failed on a Chelsio network driver that has nothing to do with our patch. The upstream code adds inside the <code>include/net/ip.h</code> the function <code>ip_check_csum_start()</code>, so every file including this header was rebuilt and compared.</p>
<p>The upstream patch is made to be merged into the kernel not used as a livepatch. You should understand how the patch works and what it is patching then rewrite appropiartly the patch.</p>
<p>Here we have just inlined the checks from <code>ip_check_csum_start()</code> in each patched function so <code>ip.h</code> stays untouched.</p>
<details class="code-fold">
  <summary>Click here to display full code<span class="code-fold-lang" data-lang="diff" aria-hidden="true"></span></summary>
  <figure class="code" data-lang="diff" aria-label="diff code"><figcaption><span class="code-lang" data-lang="diff" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy diff code"></button></figcaption><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/net/bridge/netfilter/nf_conntrack_bridge.c b/net/bridge/netfilter/nf_conntrack_bridge.c
</span></span></span><span class="line"><span class="cl"><span class="gd">--- a/net/bridge/netfilter/nf_conntrack_bridge.c
</span></span></span><span class="line"><span class="cl"><span class="gi">+++ b/net/bridge/netfilter/nf_conntrack_bridge.c
</span></span></span><span class="line"><span class="cl"><span class="gu">@@ -40,9 +40,14 @@ static int nf_br_ip_fragment(struct net *net, struct sock *sk,
</span></span></span><span class="line"><span class="cl"> 	int err = 0;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> 	/* for offloaded checksums cleanup checksum before fragmentation */
</span></span><span class="line"><span class="cl"><span class="gd">-	if (skb-&gt;ip_summed == CHECKSUM_PARTIAL &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gd">-	    (err = skb_checksum_help(skb)))
</span></span></span><span class="line"><span class="cl"><span class="gd">-		goto blackhole;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	if (skb-&gt;ip_summed == CHECKSUM_PARTIAL) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (unlikely(skb-&gt;csum_start &lt; skb-&gt;network_header + ip_hdrlen(skb)))
</span></span></span><span class="line"><span class="cl"><span class="gi">+			err = -EINVAL;
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (!err)
</span></span></span><span class="line"><span class="cl"><span class="gi">+			err = skb_checksum_help(skb);
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (err)
</span></span></span><span class="line"><span class="cl"><span class="gi">+			goto blackhole;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	}
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> 	iph = ip_hdr(skb);
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh">diff --git a/net/ipv4/ip_output.c b/net/ipv4/ip_output.c
</span></span></span><span class="line"><span class="cl"><span class="gd">--- a/net/ipv4/ip_output.c
</span></span></span><span class="line"><span class="cl"><span class="gi">+++ b/net/ipv4/ip_output.c
</span></span></span><span class="line"><span class="cl"><span class="gu">@@ -779,9 +779,14 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
</span></span></span><span class="line"><span class="cl"> 	int err = 0;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> 	/* for offloaded checksums cleanup checksum before fragmentation */
</span></span><span class="line"><span class="cl"><span class="gd">-	if (skb-&gt;ip_summed == CHECKSUM_PARTIAL &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gd">-	    (err = skb_checksum_help(skb)))
</span></span></span><span class="line"><span class="cl"><span class="gd">-		goto fail;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	if (skb-&gt;ip_summed == CHECKSUM_PARTIAL) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (unlikely(skb-&gt;csum_start &lt; skb-&gt;network_header + ip_hdrlen(skb)))
</span></span></span><span class="line"><span class="cl"><span class="gi">+			err = -EINVAL;
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (!err)
</span></span></span><span class="line"><span class="cl"><span class="gi">+			err = skb_checksum_help(skb);
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (err)
</span></span></span><span class="line"><span class="cl"><span class="gi">+			goto fail;
</span></span></span><span class="line"><span class="cl"><span class="gi">+	}
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> 	/*
</span></span><span class="line"><span class="cl"> 	 *	Point into the IP datagram header.
</span></span><span class="line"><span class="cl"><span class="gh">diff --git a/net/netfilter/nfnetlink_queue.c b/net/netfilter/nfnetlink_queue.c
</span></span></span><span class="line"><span class="cl"><span class="gd">--- a/net/netfilter/nfnetlink_queue.c
</span></span></span><span class="line"><span class="cl"><span class="gi">+++ b/net/netfilter/nfnetlink_queue.c
</span></span></span><span class="line"><span class="cl"><span class="gu">@@ -653,6 +653,13 @@ nla_put_failure:
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> static int nf_queue_checksum_help(struct sk_buff *entskb)
</span></span><span class="line"><span class="cl"> {
</span></span><span class="line"><span class="cl"><span class="gi">+	if (entskb-&gt;protocol == htons(ETH_P_IP)) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (!pskb_network_may_pull(entskb, sizeof(struct iphdr)) ||
</span></span></span><span class="line"><span class="cl"><span class="gi">+		    unlikely(entskb-&gt;csum_start &lt;
</span></span></span><span class="line"><span class="cl"><span class="gi">+			     entskb-&gt;network_header + ip_hdr(entskb)-&gt;ihl * 4))
</span></span></span><span class="line"><span class="cl"><span class="gi">+			return -EINVAL;
</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></span></span><span class="line"><span class="cl"> 	if (skb_csum_is_sctp(entskb))
</span></span><span class="line"><span class="cl"> 		return skb_crc32c_csum_help(entskb);
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gh">diff --git a/net/netfilter/xt_CHECKSUM.c b/net/netfilter/xt_CHECKSUM.c
</span></span></span><span class="line"><span class="cl"><span class="gd">--- a/net/netfilter/xt_CHECKSUM.c
</span></span></span><span class="line"><span class="cl"><span class="gi">+++ b/net/netfilter/xt_CHECKSUM.c
</span></span></span><span class="line"><span class="cl"><span class="gu">@@ -25,8 +25,12 @@ MODULE_ALIAS(&#34;ip6t_CHECKSUM&#34;);
</span></span></span><span class="line"><span class="cl"> static unsigned int
</span></span><span class="line"><span class="cl"> checksum_tg(struct sk_buff *skb, const struct xt_action_param *par)
</span></span><span class="line"><span class="cl"> {
</span></span><span class="line"><span class="cl"><span class="gd">-	if (skb-&gt;ip_summed == CHECKSUM_PARTIAL &amp;&amp; !skb_is_gso(skb))
</span></span></span><span class="line"><span class="cl"><span class="gi">+	if (skb-&gt;ip_summed == CHECKSUM_PARTIAL &amp;&amp; !skb_is_gso(skb)) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (xt_family(par) == NFPROTO_IPV4 &amp;&amp;
</span></span></span><span class="line"><span class="cl"><span class="gi">+		    unlikely(skb-&gt;csum_start &lt; skb-&gt;network_header + ip_hdr(skb)-&gt;ihl * 4))
</span></span></span><span class="line"><span class="cl"><span class="gi">+			return NF_DROP;
</span></span></span><span class="line"><span class="cl"> 		skb_checksum_help(skb);
</span></span><span class="line"><span class="cl"><span class="gi">+	}
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> 	return XT_CONTINUE;
</span></span><span class="line"><span class="cl"> }
</span></span><span class="line"><span class="cl"><span class="gh">diff --git a/net/xfrm/xfrm_output.c b/net/xfrm/xfrm_output.c
</span></span></span><span class="line"><span class="cl"><span class="gd">--- a/net/xfrm/xfrm_output.c
</span></span></span><span class="line"><span class="cl"><span class="gi">+++ b/net/xfrm/xfrm_output.c
</span></span></span><span class="line"><span class="cl"><span class="gu">@@ -810,16 +810,23 @@ int xfrm_output(struct sock *sk, struct sk_buff *skb)
</span></span></span><span class="line"><span class="cl"> 	}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> 	if (skb-&gt;ip_summed == CHECKSUM_PARTIAL) {
</span></span><span class="line"><span class="cl"><span class="gd">-		err = skb_checksum_help(skb);
</span></span></span><span class="line"><span class="cl"><span class="gd">-		if (err) {
</span></span></span><span class="line"><span class="cl"><span class="gd">-			XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
</span></span></span><span class="line"><span class="cl"><span class="gd">-			kfree_skb(skb);
</span></span></span><span class="line"><span class="cl"><span class="gd">-			return err;
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (skb-&gt;protocol == htons(ETH_P_IP)) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+			if (unlikely(skb-&gt;csum_start &lt; skb-&gt;network_header + ip_hdrlen(skb))) {
</span></span></span><span class="line"><span class="cl"><span class="gi">+				err = -EINVAL;
</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></span></span><span class="line"><span class="cl"> 		}
</span></span><span class="line"><span class="cl"><span class="gi">+		err = skb_checksum_help(skb);
</span></span></span><span class="line"><span class="cl"><span class="gi">+		if (err)
</span></span></span><span class="line"><span class="cl"><span class="gi">+			goto error;
</span></span></span><span class="line"><span class="cl"> 	}
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> out:
</span></span><span class="line"><span class="cl"> 	return xfrm_output2(net, sk, skb);
</span></span><span class="line"><span class="cl"><span class="gi">+error:
</span></span></span><span class="line"><span class="cl"><span class="gi">+	XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTERROR);
</span></span></span><span class="line"><span class="cl"><span class="gi">+	kfree_skb(skb);
</span></span></span><span class="line"><span class="cl"><span class="gi">+	return err;
</span></span></span><span class="line"><span class="cl"> }
</span></span><span class="line"><span class="cl"> EXPORT_SYMBOL_GPL(xfrm_output);
</span></span></code></pre></div></figure>
</details><p>Re-run kpatch-build. As there is fewer functions touched, the whole build with kpatch-build is much faster, it has to deal only with the files we actually patched.</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch-build -s ~/linux-src -c /boot/config-<span class="k">$(</span>uname -r<span class="k">)</span> -v /usr/lib/debug/boot/vmlinux-<span class="k">$(</span>uname -r<span class="k">)</span> -n livepatch-pwnescape pwnescape-fixed.patch
</span></span><span class="line"><span class="cl"><span class="go">Using source directory at /home/lisa/linux-src
</span></span></span><span class="line"><span class="cl"><span class="go">Testing patch file(s)
</span></span></span><span class="line"><span class="cl"><span class="go">Reading special section data
</span></span></span><span class="line"><span class="cl"><span class="go">Building original source
</span></span></span><span class="line"><span class="cl"><span class="go">Building patched source
</span></span></span><span class="line"><span class="cl"><span class="go">Extracting new and modified ELF sections
</span></span></span><span class="line"><span class="cl"><span class="go">net/xfrm/xfrm_output.o: changed function: xfrm_output
</span></span></span><span class="line"><span class="cl"><span class="go">net/bridge/netfilter/nf_conntrack_bridge.o: changed function: nf_ct_bridge_post
</span></span></span><span class="line"><span class="cl"><span class="go">net/ipv4/ip_output.o: changed function: ip_do_fragment
</span></span></span><span class="line"><span class="cl"><span class="go">net/netfilter/nfnetlink_queue.o: changed function: __nfqnl_enqueue_packet
</span></span></span><span class="line"><span class="cl"><span class="go">net/netfilter/xt_CHECKSUM.o: changed function: checksum_tg
</span></span></span><span class="line"><span class="cl"><span class="go">Patched objects: net/bridge/netfilter/nf_conntrack_bridge.ko net/netfilter/nfnetlink_queue.ko net/netfilter/xt_CHECKSUM.ko vmlinux
</span></span></span><span class="line"><span class="cl"><span class="go">Building patch module: livepatch-pwnescape.ko
</span></span></span><span class="line"><span class="cl"><span class="go">SUCCESS
</span></span></span></code></pre></div></figure><p>Original code was fixing functions <code>nf_br_ip_fragment</code> and <code>nf_queue_checksum_help</code> but they don&rsquo;t appears in the build output. The compiler have inlined them into their callers, so kpatch patched <code>nf_ct_bridge_post</code> and <code>__nfqnl_enqueue_packet</code> instead. This is exactly what we had to check manually for the januscape manual patch.</p>
<p><code>livepatch-pwnescape.ko</code> can now be insmod.</p>
<p>Alternativetly, <code>kpatch load</code> can also be used.</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch load livepatch-pwnescape.ko
</span></span><span class="line"><span class="cl"><span class="go">loading patch module: livepatch-pwnescape.ko
</span></span></span><span class="line"><span class="cl"><span class="go">waiting (up to 15 seconds) for patch transition to complete...
</span></span></span><span class="line"><span class="cl"><span class="go">transition complete (2 seconds)
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> dmesg <span class="p">|</span> tail -n3
</span></span><span class="line"><span class="cl"><span class="go">[247032.855125] livepatch: enabling patch &#39;livepatch_pwnescape&#39;
</span></span></span><span class="line"><span class="cl"><span class="go">[247032.869226] livepatch: &#39;livepatch_pwnescape&#39;: starting patching transition
</span></span></span><span class="line"><span class="cl"><span class="go">[247034.753344] livepatch: &#39;livepatch_pwnescape&#39;: patching complete
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_pwnescape [enabled]
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Installed patch modules:
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> ls -R /sys/kernel/livepatch/livepatch_pwnescape/
</span></span><span class="line"><span class="cl"><span class="go">/sys/kernel/livepatch/livepatch_pwnescape/:
</span></span></span><span class="line"><span class="cl"><span class="go">enabled  force  nf_conntrack_bridge  nfnetlink_queue  replace  transition  vmlinux xt_CHECKSUM
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">/sys/kernel/livepatch/livepatch_pwnescape/nf_conntrack_bridge:
</span></span></span><span class="line"><span class="cl"><span class="go">nf_ct_bridge_post,1  patched
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">/sys/kernel/livepatch/livepatch_pwnescape/nf_conntrack_bridge/nf_ct_bridge_post,1:
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">/sys/kernel/livepatch/livepatch_pwnescape/nfnetlink_queue:
</span></span></span><span class="line"><span class="cl"><span class="go">__nfqnl_enqueue_packet,1  patched
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span></code></pre></div></figure><p>A livepatch lives only in memory, if the server reboots on the same kernel, the fix is gone and the vulnerability is back. <code>kpatch install livepatch-pwnescape.ko</code> will store the module so kpatch loads it again at boot. Since the livepatch is built for one specific kernel, it will be only loaded on that kernel. Once you reboot on a different kernel, there is no patch to load. Also don’t forget to enable the kpatch service (<code>systemctl enable kpatch</code>).</p>
<p>No official POC was released yet for Pwnescape, only the security patch. Using a <a href="https://github.com/PeronGH/CVE-2026-99999/tree/main">POC by PeronGH</a>, the OOB reads reproduce on an unpatched kernel. With the livepatch loaded, the skb is rejected before fragmentation and no fragment is captured at all.</p>
<h2 id="cumulative-patches-and-atomic-replace"><a class="h-anchor" href="#cumulative-patches-and-atomic-replace" data-marks="##" aria-hidden="true" tabindex="-1"></a>Cumulative patches and atomic replace</h2>
<div class="side">
  <div class="side-text">
    <p>KVM vulnerabilities went wild recently and kept coming. Zapscape (also by V4Bel) which appeared one month after Januscape (which was one month after ITscape), was also a guest-to-host escape, allowing an attacker to execute commands with the root privileges on the host.</p>
<p>Let&rsquo;s say after Januscape, <a href="https://blog.ovhcloud.com/fr/posts/cve-2026-53359-kvm-patching-lessons-learned/">you rebooted your whole fleet on a kernel including the fix</a>. A few weeks later, Zapscape comes out. Without livepatching, either the production still runs an unpatched kernel or another emergency reboot campaign has to be made. Another month later, Pwnescape comes out. There is another livepatch or another livemigration + reboot campaign.</p>
<p>Once you livepatched Zapscape, you could load a second livepatch on top of the first one. But a cleaner way can also be to build a single cumulative livepatch containing both Zapscape and Pwnescape, which replaces the first Zapscape livepatch.</p>

  </div>
  <figure class="side-img">
    <img src="https://sanecz.net/assets/livepatch/please_stop.png" alt="Meme: &#34;I am once again asking... to stop releasing KVM guest to host escape please&#34;." loading="lazy">
  </figure>
</div>

<p>Cumulative patches are about the content. A single livepatch that contains every fix needed on the running kernel.
Atomic replaces are the mechanism used to load it: the new livepatch replaces all the previous one in a single transition, there is no moment where the server runs without any fix. Once the transition is complete, the previous livepatches are disabled and all functions they patched that are not in the new livepatch goes back to the original code.</p>
<p>On a hand-written livepatch, atomic replace is a single field in <code>struct klp_patch</code>. When this field is unset, the default value is false. The livepatch would be stacked on top of the previous ones. Setting it to true enables atomic replace:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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="k">struct</span> <span class="n">klp_patch</span> <span class="n">patch</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">mod</span> <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">objs</span> <span class="o">=</span> <span class="n">objs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">.</span><span class="n">replace</span> <span class="o">=</span> <span class="nb">true</span><span class="p">,</span> <span class="cm">/* replaces all the previously loaded livepatches */</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div></figure><p>kpatch-build builds atomic replace livepatches by default.</p>
<p>This is the current state of the test server:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_zapscape [enabled]
</span></span></span></code></pre></div></figure><p>The zapscape patch was built the same way with kpatch-build, from the upstream commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=2abd5287f08319fa35764566b15c6e22cb1068db">2abd5287f083</a>.</p>
<p>Now, the goal is to add the Pwnescape patch. Januscape is already fixed on my test kernel, so it&rsquo;s not needed but Zapscape is. If we load the previously built Pwnescape patch, Zapscape vulnerability  come back without any warning:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch load livepatch-pwnescape.ko
</span></span><span class="line"><span class="cl"><span class="go">loading patch module: livepatch-pwnescape.ko
</span></span></span><span class="line"><span class="cl"><span class="go">waiting (up to 15 seconds) for patch transition to complete...
</span></span></span><span class="line"><span class="cl"><span class="go">transition complete (3 seconds)
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_pwnescape [enabled]
</span></span></span></code></pre></div></figure><p>livepatch_zapscape is silently replaced and from now zapscape is not fixed anymore. And it gets worse if we need to roll back:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch unload livepatch_pwnescape
</span></span><span class="line"><span class="cl"><span class="go">disabling patch module: livepatch_pwnescape
</span></span></span><span class="line"><span class="cl"><span class="go">waiting (up to 15 seconds) for patch transition to complete...
</span></span></span><span class="line"><span class="cl"><span class="go">transition complete (2 seconds)
</span></span></span><span class="line"><span class="cl"><span class="go">unloading patch module: livepatch_pwnescape
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="go">Installed patch modules:
</span></span></span></code></pre></div></figure><p>The zapscape livepatch does not come back automatically. A replaced livepatch stays disabled even if the module is still loaded. The server now runs with no fix at all, zapscape livepatch has to be loaded again manually:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> lsmod  <span class="p">|</span> grep patch
</span></span><span class="line"><span class="cl"><span class="go">livepatch_zapscape     36864  0
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch load livepatch-zapscape.ko
</span></span><span class="line"><span class="cl"><span class="go">loading patch module: livepatch-zapscape.ko
</span></span></span><span class="line"><span class="cl"><span class="go">waiting (up to 15 seconds) for patch transition to complete...
</span></span></span><span class="line"><span class="cl"><span class="go">transition complete (2 seconds)
</span></span></span></code></pre></div></figure><p>This is why a livepatch built with atomic replace should be cumulative.</p>
<p>kpatch-build accepts multiple patch files, so a single livepatch can be built to contains both of the fixes:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch-build -s ~/linux-src -c /boot/config-<span class="k">$(</span>uname -r<span class="k">)</span> -v /usr/lib/debug/boot/vmlinux-<span class="k">$(</span>uname -r<span class="k">)</span> -n livepatch-cumulative zapscape.patch pwnescape-fixed.patch
</span></span><span class="line"><span class="cl"><span class="go">Using source directory at /home/lisa/linux-src
</span></span></span><span class="line"><span class="cl"><span class="go">Testing patch file(s)
</span></span></span><span class="line"><span class="cl"><span class="go">Reading special section data
</span></span></span><span class="line"><span class="cl"><span class="go">Building original source
</span></span></span><span class="line"><span class="cl"><span class="go">Building patched source
</span></span></span><span class="line"><span class="cl"><span class="go">Extracting new and modified ELF sections
</span></span></span><span class="line"><span class="cl"><span class="go">net/xfrm/xfrm_output.o: changed function: xfrm_output
</span></span></span><span class="line"><span class="cl"><span class="go">arch/x86/kvm/mmu/mmu.o: changed function: paging32_page_fault
</span></span></span><span class="line"><span class="cl"><span class="go">arch/x86/kvm/mmu/mmu.o: changed function: paging64_page_fault
</span></span></span><span class="line"><span class="cl"><span class="go">arch/x86/kvm/mmu/mmu.o: changed function: ept_page_fault
</span></span></span><span class="line"><span class="cl"><span class="go">arch/x86/kvm/mmu/mmu.o: changed function: direct_page_fault
</span></span></span><span class="line"><span class="cl"><span class="go">net/ipv4/ip_output.o: changed function: ip_do_fragment
</span></span></span><span class="line"><span class="cl"><span class="go">net/bridge/netfilter/nf_conntrack_bridge.o: changed function: nf_ct_bridge_post
</span></span></span><span class="line"><span class="cl"><span class="go">net/netfilter/nfnetlink_queue.o: changed function: __nfqnl_enqueue_packet
</span></span></span><span class="line"><span class="cl"><span class="go">net/netfilter/xt_CHECKSUM.o: changed function: checksum_tg
</span></span></span><span class="line"><span class="cl"><span class="go">Patched objects: arch/x86/kvm/kvm.ko net/bridge/netfilter/nf_conntrack_bridge.ko net/netfilter/nfnetlink_queue.ko net/netfilter/xt_CHECKSUM.ko vmlinux
</span></span></span><span class="line"><span class="cl"><span class="go">Building patch module: livepatch-cumulative.ko
</span></span></span><span class="line"><span class="cl"><span class="go">SUCCESS
</span></span></span></code></pre></div></figure><p>Then loading it on a server already patched:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_zapscape [enabled]
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch load livepatch-cumulative.ko
</span></span><span class="line"><span class="cl"><span class="go">loading patch module: livepatch-cumulative.ko
</span></span></span><span class="line"><span class="cl"><span class="go">waiting (up to 15 seconds) for patch transition to complete...
</span></span></span><span class="line"><span class="cl"><span class="go">transition complete (2 seconds)
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_cumulative [enabled]
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> cat /sys/kernel/livepatch/livepatch_cumulative/replace
</span></span><span class="line"><span class="cl"><span class="go">1
</span></span></span></code></pre></div></figure><p>livepatch_zapscape is out of the list, it was also automatically replaced and disabled. Its module is still loaded though, but can be now removed</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> lsmod <span class="p">|</span> grep patch
</span></span><span class="line"><span class="cl"><span class="go">livepatch_cumulative		61440  1
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_zapscape		36864  0
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> rmmod livepatch_zapscape
</span></span></code></pre></div></figure><p>New livepatch covers both fixes, on vmlinux, kvm and all other modules involved:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> ls  /sys/kernel/livepatch/livepatch_cumulative/vmlinux/
</span></span><span class="line"><span class="cl"><span class="go">ip_do_fragment,1  patched  xfrm_output,1
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> ls /sys/kernel/livepatch/livepatch_cumulative/kvm
</span></span><span class="line"><span class="cl"><span class="go">direct_page_fault,1  ept_page_fault,1  paging32_page_fault,1  paging64_page_fault,1  patched
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> ls  /sys/kernel/livepatch/livepatch_cumulative/nfnetlink_queue/
</span></span><span class="line"><span class="cl"><span class="go">__nfqnl_enqueue_packet,1  patched
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> cat /sys/kernel/livepatch/livepatch_cumulative/kvm/patched
</span></span><span class="line"><span class="cl"><span class="go">1
</span></span></span></code></pre></div></figure><p>Patches can also be loaded side by side, using <code>--non-replace</code>:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> kpatch-build --non-replace -s ~/linux-src -c /boot/config-<span class="k">$(</span>uname -r<span class="k">)</span> -v /usr/lib/debug/boot/vmlinux-<span class="k">$(</span>uname -r<span class="k">)</span> -n livepatch-pwnescape pwnescape-fixed.patch
</span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_zapscape [enabled]
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch load livepatch-pwnescape.ko
</span></span><span class="line"><span class="cl"><span class="go">loading patch module: livepatch-pwnescape.ko
</span></span></span><span class="line"><span class="cl"><span class="go">waiting (up to 15 seconds) for patch transition to complete...
</span></span></span><span class="line"><span class="cl"><span class="go">transition complete (2 seconds)
</span></span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> kpatch list
</span></span><span class="line"><span class="cl"><span class="go">Loaded patch modules:
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_pwnescape [enabled]
</span></span></span><span class="line"><span class="cl"><span class="go">livepatch_zapscape [enabled]
</span></span></span></code></pre></div></figure><p>Both patches are now enabled side by side. It’s simple here because they don’t touch the same functions. Each CVE adds another module to track and they have to be disabled in reverse order to be removed.</p>
<h2 id="so-should-you-livepatch"><a class="h-anchor" href="#so-should-you-livepatch" data-marks="##" aria-hidden="true" tabindex="-1"></a>So, should you livepatch?</h2>
<p>In this blog, we talked about Januscape, Zapscape and PwnEscape, which fortunately were cases of fixes being livepatchable. Those functions were hookable and transitioned cleanly: short-lived, they return immediately. It was also isolated to small and specific portions of the kernel code.</p>
<p>If the problem was more complex, affecting multiple functions, data structures etc, it can&rsquo;t be done. There are no miracles that can replace a good ol&rsquo; pssh <code>reboot &lt; /dev/null</code>.</p>
<p>Honestly, the manual livepatch remains fragile: resolving a split symbol through <code>.part.0</code>, whole function copies and hand-made helpers that were inlined away. This ties the livepatch to this exact kernel version or with some luck to some newer versions if the functions copied do not change. Any change in the compiler, the symbol names, the surrounding code (e.g. kvm mmu management) or a function that suddenly becomes inlined, and this module won&rsquo;t load or the kernel may panic. If it doesn&rsquo;t fail loudly, it may corrupt quietly in the exact fault path we were trying to protect.</p>
<p>Home-made livepatches might help you buy time until you are able to reboot your servers to a newer kernel version. But it still requires good knowledge of Linux and C. Paid alternatives that deliver livepatch exists and might suit your needs for critical production servers.</p>
<p>The hardest part of livepatching is not only writing the patch itself, but making sure the replacement is correct, thoroughly tested, compatible with the running kernel, safe to transition to and, most of all, maintainable on a fleet of servers.</p>
<p>So, would you livepatch ?</p>
<p>Thanks to my coworkers for putting up with the monkey I am in production, especially when I&rsquo;m tinkering with kpatch and kexec 💜</p>
<p><em>Maybe in a future article, we will talk about klp-build, which became the in-tree sucessor of kpatch-build <a href="https://www.phoronix.com/news/Linux-6.19-objtool-klp-build">since kernel version 6.19</a>.</em></p>
]]></content:encoded></item><item><title>Demystified: the IPMI System Event Log</title><link>https://sanecz.net/blog/post-3/</link><guid isPermaLink="true">https://sanecz.net/blog/post-3/</guid><pubDate>Tue, 24 Dec 2024 22:40:26 +0000</pubDate><dc:creator>sanecz</dc:creator><category>ipmi</category><category>baremetal</category><category>monitoring</category><description>IPMI is a useful tool for monitoring server hardware, and one of the features we'll cover here is the System Event Log (SEL), which helps track system health...</description><content:encoded><![CDATA[<h2 id="introduction"><a class="h-anchor" href="#introduction" data-marks="##" aria-hidden="true" tabindex="-1"></a>Introduction</h2>
<p>IPMI is a useful tool for monitoring server hardware, and one of the features we&rsquo;ll cover here is the System Event Log (SEL), which helps track system health. The SEL records events such as hardware failures (e.g., ECC errors, power supply failures), boot events, and sensor state changes.</p>
<p>In this article, we&rsquo;ll look at the differences between the <strong>ipmitool sel list</strong> and <strong>ipmitool sel elist</strong> commands, and how each presents SEL data in different formats. We&rsquo;ll also explore raw IPMI commands for accessing log entries directly and explain how to decode the raw output with some examples.</p>
<h2 id="ipmitool-sel-list-vs-ipmitool-sel-elist"><a class="h-anchor" href="#ipmitool-sel-list-vs-ipmitool-sel-elist" data-marks="##" aria-hidden="true" tabindex="-1"></a>ipmitool sel list vs ipmitool sel elist</h2>
<p>The <code>ipmitool sel list</code> command is used to list SEL entries. It provides an overview and summary of each entry. The output is concise and includes several pieces of information such as:</p>
<ul>
<li><strong>Record ID</strong>: Identifier for each log entry</li>
<li><strong>Timestamp</strong>: Time when the event was logged</li>
<li><strong>Sensor Type / Sensor number</strong>: Sensor that generated the event</li>
<li><strong>Sensor state</strong>: Value interpreted based on the current event type</li>
<li><strong>Event Direction</strong>: Assertion/Deassertion event</li>
</ul>
<p>e.g:</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">575 | 12/03/2024 | 11:27:10 | Module / Board #0x43 | Upper Critical going high | Asserted</span></span></code></pre></div></figure><p>Here, the information is pretty succint, some module on the board reached a critical value. But what is happening ?</p>
<figure class="img">
  <img src="https://sanecz.net/assets/ipmi/unknown_assert.png" alt="Meme: Woody and Buzz from Toy Story in front of a SEL log full of &#34;Unknown ... Asserted&#34; entries, captioned &#34;Unknown, unknown everywhere&#34;." loading="lazy" style="max-width: 45%">
  <figcaption>Sometimes&hellip;</figcaption>
</figure>

<p>On the other hand, <strong>ipmitool sel elist</strong> includes a little more details about the events but it may not apply for every entries. This might a little more information about OEM events. Even it&rsquo;s not possible to decode everything, it can give a a human readable hint about the sensor that triggered the event.</p>
<p>This includes:</p>
<ul>
<li><strong>Decoded sensor number</strong>: Full name of the sensor that generated the event from SDR (Sensor Data Record)</li>
<li><strong>Context</strong>: depends of the event, may contains the reading of the sensor that triggered the event, the threshold, the severity etc..</li>
</ul>
<p>e.g</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">575 | 12/03/2024 | 11:27:10 | Module / Board PWR_GB1_TOT_HSC | Upper Critical going high | Asserted | Reading 5865 &gt; Threshold 5865 Watts</span></span></code></pre></div></figure><p>Now it is clear that the power sensor reached the critical threshold value but also the current reading of the sensor and the threshold value are 5865 Watts.</p>
<h2 id="ipmitool-raw-and-the-sel"><a class="h-anchor" href="#ipmitool-raw-and-the-sel" data-marks="##" aria-hidden="true" tabindex="-1"></a>ipmitool raw and the SEL</h2>
<p>The ipmitool raw command lets you send raw IPMI commands directly to the BMC and see the responses. We’ll use this to retrieve logs stored in the BMC’s non-volatile memory, but first, we need to understand how these commands are structured.</p>
<h2 id="querying-the-sel-with-raw-commands"><a class="h-anchor" href="#querying-the-sel-with-raw-commands" data-marks="##" aria-hidden="true" tabindex="-1"></a>Querying the SEL with raw commands</h2>
<p>When sending a command to the BMC, you need two key pieces: the <strong>NetFn</strong> and the <strong>CMD</strong>.</p>
<p>The <strong>NetFn</strong> (Network Function) is a command category that groups related operations, so the BMC knows what kind of task to perform. This tells the BMC which category of commands you’re using. There is a NetFn for sensors, power settings, storage and many more ! We’ll use the storage NetFn (code 0x0A) because it’s responsible for managing system logs, sensor data, and hardware inventory.</p>
<p>Each NetFn has specific sub functions called commands (<strong>CMD</strong>). For the netfn storage, these include:</p>
<ul>
<li><strong>Write FRU Data (0x12)</strong>: Writes hardware information to the FRU memory.</li>
<li><strong>Set SEL Time (0x49)</strong>: Updates the timestamp for the logs.</li>
<li><strong>Get SEL Entry (0x43)</strong>: Reads a specific log entry.</li>
</ul>
<p>To read a log entry, we combine the Storage NetFn (0x0A) with the Get SEL Entry command (0x43). This tells the BMC to fetch data from the system event log stored in its memory.</p>
<p>Here’s what the raw command looks like:</p>
<figure class="code" data-lang="shell" aria-label="shell code"><figcaption><span class="code-lang" data-lang="shell" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy shell code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ipmitool raw 0x0a 0x43 &lt;args&gt;</span></span></code></pre></div></figure><p>The code for the availables netfn and commands are in the Intelligent Platform Management Interface Specification <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>:</p>
<ul>
<li>Network Function Codes: Table 5 (page 67)</li>
<li>Sub-functions: Appendix H (page 615)</li>
</ul>
<p>Using the IPMI specification table for the GET SEL Entry, we can retrieve any log entry by specifying its record ID. For example, let’s say we want to fetch the entire record for the entry with record ID 71h.</p>
<div class="side">
  <div class="side-text">
    <p>Here’s how we construct the command:</p>
<ul>
<li><strong>0x0A</strong>: Storage NetFn.</li>
<li><strong>0x43</strong>: Get SEL Entry command.</li>
</ul>
<p>With the arguments:</p>
<ul>
<li><strong>0x00 0x00</strong>: Reservation ID, 0x00 0x00 = no reservation is required.</li>
<li><strong>0x00 0x71</strong>: Record ID (71h) that we want to retrieve.</li>
<li><strong>0x00</strong>: Offset field, read from the beginning of the record.</li>
<li><strong>0xff</strong>: Retrieve the entire record.</li>
</ul>

  </div>
  <figure class="side-img">
    <img src="https://sanecz.net/assets/ipmi/get_sel_entry.png" alt="Get SEL Entry command table from the IPMI specification: request fields (reservation ID, record ID, offset, bytes to read) and response fields (next record ID, record data)." loading="lazy">
    <figcaption>Intelligent Platform Management Interface Specification page 424</figcaption>
  </figure>
</div>

<p>Which gives:</p>
<figure class="code" data-lang="shell" aria-label="shell code"><figcaption><span class="code-lang" data-lang="shell" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy shell code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ipmitool raw 0x0a 0x43 0x00 0x00 0x71 0x00 0x00 0xff</span></span></code></pre></div></figure><h2 id="decoding-the-raw-output"><a class="h-anchor" href="#decoding-the-raw-output" data-marks="##" aria-hidden="true" tabindex="-1"></a>Decoding the raw output</h2>
<p>After running the command, we get the following raw output:</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"> 72 00 71 00 02 7d 48 31 67 20 00 04 09 ff 6f 00 00 00</span></span></code></pre></div></figure><div class="side">
  <div class="side-text">
    <p>Using the response data from the table get SEL entry, we can extract some fields.</p>
<p>Note: the completion code is not returned by ipmitool.</p>
<ul>
<li>[0:1] is <strong>72 00</strong>, the next record ID, that is <strong>72h</strong></li>
<li>[2:N] is <strong>71 00 02 7d 48 31 67 20 00 04 09 ff 6f 00 00 00</strong>, as this is the record data, we need another documentation to decode it.</li>
</ul>

  </div>
  <figure class="side-img">
    <img src="https://sanecz.net/assets/ipmi/get_sel_entry.png" alt="Get SEL Entry command table from the IPMI specification: request fields (reservation ID, record ID, offset, bytes to read) and response fields (next record ID, record data)." loading="lazy">
    <figcaption>Intelligent Platform Management Interface Specification page 424</figcaption>
  </figure>
</div>

<p>Then we could use the System Event Log (SEL) Troubleshooting Guide from Intel <sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> to decode furthermore.</p>
<figure class="img">
  <img class="only-light" src="https://sanecz.net/assets/ipmi/ipmi_output_light.png" alt="Table from Intel&#39;s SEL Troubleshooting Guide mapping each byte of a SEL record to its field: record ID, record type, timestamp, generator ID, sensor type and number, event type and event data." loading="lazy">
  
  <figcaption>System Event Log (SEL) Troubleshooting Guide page 16</figcaption>
</figure>

<p>The event data can only be decoded after decoding the record type, sensor type, and event type. Both the record type and event type determine which decoding table to use. Additionally, they can indicate if the record is an OEM event, in which case you’ll need the manufacturer’s documentation to decode it.</p>
<p>For example, in our case, the record type is <strong>02h</strong>, which indicates a system event (not OEM), so it should be easier to decode. The event type is <strong>6Fh</strong>, and the event data is <strong>00 00 00</strong>.</p>
<figure class="img">
  <img class="only-light" src="https://sanecz.net/assets/ipmi/data_event_light.png" alt="Table from Intel&#39;s SEL Troubleshooting Guide for decoding the event data of a system event record with event type 6Fh." loading="lazy" style="max-width: 65%">
  
  <figcaption>System Event Log (SEL) Troubleshooting Guide page 40</figcaption>
</figure>

<h2 id="a-case-of-oem-sel-record"><a class="h-anchor" href="#a-case-of-oem-sel-record" data-marks="##" aria-hidden="true" tabindex="-1"></a>A case of OEM SEL record</h2>
<p>Let’s start with this SEL entry, which doesn’t seem too helpful at first glance:</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">49b | 12/04/2024 | 12:00:13 | OEM record dc | 001647 | 00747a031678</span></span></code></pre></div></figure><p>Query the BMC for the full details using the command:</p>
<figure class="code" data-lang="shell" aria-label="shell code"><figcaption><span class="code-lang" data-lang="shell" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy shell code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">ipmitool raw 0x0a 0x43 0x00 0x00 0x9b 0x04 0x00 0xff</span></span></code></pre></div></figure><p>We get the following output:</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">9c 04 9b 04 dc 4d 44 50 67 47 16 00 00 74 7a 03 16 78</span></span></code></pre></div></figure><p>Now let’s break it down step by step:</p>
<ul>
<li><strong>9c 04</strong>: LSB First. Next SEL record ID: <strong>49Ch</strong>.</li>
<li><strong>9b 04</strong>: Bytes[1,2] LSB First. Current record ID: <strong>49Bh</strong>.</li>
<li><strong>dc</strong>: Byte[3] Record type: OEM Timestamped.</li>
</ul>
<p>Then, we decode the timestamp:</p>
<ul>
<li><strong>4d 44 50 67</strong>: Byte[4-7]: LSB First, which is 6750444Dh or 1733313613 in decimal, converted to date: 04/12/2024 12:00:13.</li>
</ul>
<p>As this is an <strong>DCh</strong> OEM record, we&rsquo;ll use the table 3 (SEL Troubleshooting page 18) to decode the next part:</p>
<ul>
<li><strong>47 16 00</strong>: Bytes[8-10]: LSB First. This is the Manufacturer ID. When decoded, 0x1647 corresponds to NVIDIA (ID 5703). The enterprise can be found on the IANA Enterprise Numbers registry from the manufacturer ID.</li>
</ul>
<p>Since this is an OEM record, full decoding of bytes 11 to 16 requires the manufacturer’s documentation. Or, you know, just kindly ask them directly — here it seems that some RAM decided to give up and crash the server! ;)</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf">https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf</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.intel.com/content/dam/support/us/en/documents/server-products/SEL_TroubleshootingGuide.pdf">https://www.intel.com/content/dam/support/us/en/documents/server-products/SEL_TroubleshootingGuide.pdf</a>&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>IPv6 connectivity issue : a journey into radvd and socket options</title><link>https://sanecz.net/blog/post-2/</link><guid isPermaLink="true">https://sanecz.net/blog/post-2/</guid><pubDate>Tue, 02 Apr 2024 09:00:00 +0000</pubDate><dc:creator>sanecz</dc:creator><category>network</category><category>ipv6</category><category>radvd</category><category>kernel</category><category>debugging</category><description>User virtual machines could randomly lose their IPv6 connectivity to the Internet. Disabling the IPv6 option in the launcher thus allowed reliable access to the VM to be restored...</description><content:encoded><![CDATA[<h2 id="context"><a class="h-anchor" href="#context" data-marks="##" aria-hidden="true" tabindex="-1"></a>Context</h2>
<p>Shadow is a cloud computing service specialized in Gaming. To provide isolated machines to users, they rely heavily on virtual machines.</p>
<p>Each virtual machine has dual-stack connectivity with both IPv4 and IPv6. Using IPv4 the network configuration is pushed via DHCP providing both addressing and default route informations. Using IPv6, we need to use the Router Advertisement mechanism.</p>
<p>Recently, we changed our networking stack on the hypervisor, whereas the virtual interfaces for each VMs were created beforehand and persistent we moved to transient virtual interfaces creating them and removing each times a virtual machine is started and stopped.</p>
<p>After a few weeks we noticed virtual machines could randomly lose their IPv6 connectivity to the Internet.
While disabling the IPv6 connectivity allowed reliable access to the VM to be restored, we dug to find out what could be the cause of this defect.</p>
<h2 id="analysis-of-the-ipv6-connectivity-in-the-vm"><a class="h-anchor" href="#analysis-of-the-ipv6-connectivity-in-the-vm" data-marks="##" aria-hidden="true" tabindex="-1"></a>Analysis of the IPv6 connectivity in the VM</h2>
<p>After a short analysis we noticed that the Windows routing table no longer had an IPv6 route but instead, only a link-local route.</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&gt; route print
</span></span><span class="line"><span class="cl">[...]
</span></span><span class="line"><span class="cl">IPv6 Route Table
</span></span><span class="line"><span class="cl">Active Routes:
</span></span><span class="line"><span class="cl">If	Metric	Network Destination		Gateway
</span></span><span class="line"><span class="cl">1	331	::1/128				On-link
</span></span><span class="line"><span class="cl">4	271	fd12:0:0:a01::a/128		On-link
</span></span><span class="line"><span class="cl">4	271	fe80::/64			On-link
</span></span><span class="line"><span class="cl">4	271	fe80::d756:522a:7e8b:339c/128	On-link
</span></span><span class="line"><span class="cl">1	331	ff00::/8			On-link
</span></span><span class="line"><span class="cl">4	271	ff00::/8			On-link
</span></span><span class="line"><span class="cl">Persistent Routes: None</span></span></code></pre></div></figure><p>A correct configuration would be:</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">&gt; route print
</span></span><span class="line"><span class="cl">[...]
</span></span><span class="line"><span class="cl">IPv6 Route Table
</span></span><span class="line"><span class="cl">Active Routes:
</span></span><span class="line"><span class="cl">If	Metric	Network Destination		Gateway
</span></span><span class="line"><span class="cl">13 	271 	::/0				fe80::fc42:c6ff:fe7e:6388
</span></span><span class="line"><span class="cl">1 	331 	::1/128				On-link
</span></span><span class="line"><span class="cl">13 	271 	fd12:0:0:a08::a/128		On-linka
</span></span><span class="line"><span class="cl">13 	271	fe80::/64			On-link
</span></span><span class="line"><span class="cl">13	271 	fe80::6b86:ba8a:cc33:f514/128	On-link
</span></span><span class="line"><span class="cl">1	331	ff00::/8			On-link
</span></span><span class="line"><span class="cl">13 	271	ff00::/8			On-link
</span></span><span class="line"><span class="cl">Persistent Routes: None</span></span></code></pre></div></figure><p>The VM does not know where to send its IPv6 packets despite having IPv6 connectivity.</p>
<p>Once the VM initialized its network interface, it sends a <code>RS</code> (Router Sollicitation) request which aims to discover available IPv6 routers. This is <code>radvd</code>&rsquo;s time to shine, as it&rsquo;s in charge of answering this <code>RS</code> by a <code>RA</code> (Router Advertisement). The VM will then know that this router will handle its traffic (thus adding the <code>::/0</code> route via <code>fe80::fc42:c6ff:fe7e</code>).
These mechanisms are part of the Neighbor Discovery Protocol.</p>
<p>This router advertisement is never sent when <code>radvd</code> encounters a precise bug&hellip; And <code>tcpdump</code> made pretty clear that this router advertisement went missing.</p>
<h2 id="understanding-the-missing-router-advertisment"><a class="h-anchor" href="#understanding-the-missing-router-advertisment" data-marks="##" aria-hidden="true" tabindex="-1"></a>Understanding the missing Router Advertisment</h2>
<p>To receive Router Solicitation and send Router Advertisement, radvd configures its socket to join the multicast group of the virtual machine network interface. This is done by using the <code>setsockopt</code> syscall.
<code>setsockopt</code> is a syscall allowing to specify options on a socket.</p>
<p>For example, one can use this syscall to force the reuse of an address (with <code>SO_REUSE_ADDR</code>), change the size of the buffer allocated for receiving data on this socket (with <code>SO_RCVBUF</code>). In our case, it is used to join a multicast group (with <code>IPV6_ADD_MEMBERSHIP</code>).</p>
<p>Using <code>strace</code> we were able to trace these calls, their options and their return value.
Below, a curated output of the <code>strace</code> attached to the radvd <sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> process.</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> strace -p &lt;radvd PID&gt;
</span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">setsockopt(3, SOL_IPV6, IPV6_ADD_MEMBERSHIP, {inet_pton(AF_INET6, &#34;ff02::2&#34;, &amp;ipv6mr_multiaddr), ipv6mr_interface=if_nametoindex(&#34;tap2&#34;)}, 20) = -1 ENOMEM (Cannot allocate memory)
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">setsockopt(3, SOL_IPV6, IPV6_ADD_MEMBERSHIP, {inet_pton(AF_INET6, &#34;ff02::2&#34;, &amp;ipv6mr_multiaddr), ipv6mr_interface=if_nametoindex(&#34;tap4&#34;)}, 20) = -1 ENOMEM (Cannot allocate memory)
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">setsockopt(3, SOL_IPV6, IPV6_ADD_MEMBERSHIP, {inet_pton(AF_INET6, &#34;ff02::2&#34;, &amp;ipv6mr_multiaddr), ipv6mr_interface=if_nametoindex(&#34;tap3&#34;)}, 20) = -1 ENOMEM (Cannot allocate memory)
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">setsockopt(3, SOL_IPV6, IPV6_ADD_MEMBERSHIP, {inet_pton(AF_INET6, &#34;ff02::2&#34;, &amp;ipv6mr_multiaddr), ipv6mr_interface=if_nametoindex(&#34;tap1&#34;)}, 20) = -1 ENOMEM (Cannot allocate memory)
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span></code></pre></div></figure><p>Its argument in this context are :</p>
<ul>
<li><code>3</code>: the target socket file descriptor, that will always be the same</li>
<li><code>SOL_IPV6</code>: IPv6 protocol</li>
<li><code>IPV6_ADD_MEMBERSHIP</code>: States that we want to join a multicast group</li>
<li><code>{inet_pton(AF_INET6, &quot;ff02::2&quot;, &amp;ipv6mr_multiaddr), ipv6mr_interface=if_nametoindex(&quot;tap1&quot;)}</code>: structure that contains options about the multicast group we aim to join
<ul>
<li><code>net_pton(AF_INET6, &quot;ff02::2&quot;, &amp;ipv6mr_multiaddr)</code>: converts the <code>ff02::2</code> IP address from a string to a binary sequence</li>
<li><code>ipv6mr_interface=if_nametoindex(&quot;tap1&quot;)</code>: target interface index, here tap1</li>
</ul>
</li>
<li><code>20</code>: the structure&rsquo;s length</li>
</ul>
<p>We basically try to join an IPv6 multicast group using <code>tap1</code>. This however results in an <code>ENOMEM</code> error. That means we run out of memory, but why? The server&rsquo;s memory is absolutely not a concern, there&rsquo;s even a lot of it that&rsquo;s free!</p>
<p>So, it will be necessary to analyze in depth to determine exactly where this <code>ENOMEM</code> is being returned.</p>
<h2 id="digging-a-little-deeper"><a class="h-anchor" href="#digging-a-little-deeper" data-marks="##" aria-hidden="true" tabindex="-1"></a>Digging a little deeper</h2>
<p>We used the <code>ftrace</code> tool, which can track the execution of various kernel-side functions that compose the <code>setsockopt</code> syscall. <code>ftrace</code> or <code>function tracer</code> is an internal tracer in the Linux kernel that allows analysis, debugging, and examination of kernel activities via the <code>tracefs</code> filesystem.</p>
<figure class="code" data-lang="shell" aria-label="shell code"><figcaption><span class="code-lang" data-lang="shell" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy shell code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">$ <span class="nb">cd</span> /sys/kernel/tracing
</span></span><span class="line"><span class="cl"><span class="c1"># Request ftrace to trace only the radv process</span>
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> &lt;radvd PID&gt; &gt; set_ftrace_pid
</span></span><span class="line"><span class="cl"><span class="c1"># Trace only function calls</span>
</span></span><span class="line"><span class="cl">$ <span class="nb">echo</span> function_graph &gt; current_tracer</span></span></code></pre></div></figure><p>To reproduce the issue, we create a network interface in tap mode and launch a VM attempting to boot over network, which will generate a router advertisement from radvd (and thus trigger this setsockopt() call)</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> ip tuntap add mode tap tap1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ip link <span class="nb">set</span> tap1 up
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ip addr add fd12:0:0:a08::<span class="nv">$1</span>/64 dev tap1
</span></span><span class="line"><span class="cl"><span class="gp">$</span> <span class="nv">QEMU</span><span class="o">=</span><span class="s2">&#34;qemu-system-x86_64 -boot n -net nic -net tap,ifname=tap</span><span class="nv">$1</span><span class="s2">,script=no,downscript=no -nographic&#34;</span>
</span></span></code></pre></div></figure><p>We now stop <code>ftrace</code>:</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> <span class="nb">echo</span> nop &gt; /sys/kernel/tracing/current_tracer
</span></span></code></pre></div></figure><p><em>Trace has been shrunk for the sake of clarity</em></p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> cat /sys/kernel/tracing/trace
</span></span><span class="line"><span class="cl"><span class="go"> 60) ! 304.905 us  |  } /* syscall_trace_enter.constprop.0 */
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |  __x64_sys_setsockopt() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |    __sys_setsockopt() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |      sockfd_lookup_light() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |        __fdget() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.376 us    |          __fget_light();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.995 us    |        }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   1.719 us    |      }
</span></span></span><span class="line"><span class="cl"><span class="go">[..]
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |      sock_common_setsockopt() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |        rawv6_setsockopt() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |          ipv6_setsockopt() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |            do_ipv6_setsockopt() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |              rtnl_lock() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                mutex_lock() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.308 us    |                  __cond_resched();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.866 us    |                }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   1.440 us    |              }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |              sockopt_lock_sock() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.285 us    |                __cond_resched();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                _raw_spin_lock_bh() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.321 us    |                  preempt_count_add();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.998 us    |                }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                _raw_spin_unlock_bh() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                  __local_bh_enable_ip() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.278 us    |                    preempt_count_sub();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.860 us    |                  }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   1.400 us    |                }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   4.289 us    |              }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |              ipv6_sock_mc_join() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                __ipv6_sock_mc_join() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                  rtnl_is_locked() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.286 us    |                    mutex_is_locked();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.864 us    |                  }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.375 us    |                  sock_kmalloc();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   6.348 us    |                }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   7.243 us    |              }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |              sockopt_release_sock() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                release_sock() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                  _raw_spin_lock_bh() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.318 us    |                    preempt_count_add();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   1.033 us    |                  }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                  _raw_spin_unlock_bh() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)               |                    __local_bh_enable_ip() {
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.307 us    |                      preempt_count_sub();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.878 us    |                    }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   1.407 us    |                  }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   3.493 us    |                }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   4.086 us    |              }
</span></span></span><span class="line"><span class="cl"><span class="go">[..]
</span></span></span><span class="line"><span class="cl"><span class="go"> 60) + 22.281 us   |            }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60) + 23.195 us   |          }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60) + 23.953 us   |        }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60) + 24.821 us   |      }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60)   0.302 us    |      kfree();
</span></span></span><span class="line"><span class="cl"><span class="go"> 60) + 32.012 us   |    }
</span></span></span><span class="line"><span class="cl"><span class="go"> 60) + 32.633 us   |  }
</span></span></span></code></pre></div></figure><p>Making use of the <code>setsockopt</code> syscall involves calling:</p>
<ul>
<li><code>sock_common_setsockopt</code></li>
<li><code>rawv6_setsockopt</code></li>
<li><code>do_ipv6_setsockopt</code></li>
<li><code>sockopt_lock_sock</code></li>
<li><code>ipv6_sock_mc_join</code></li>
<li><code>__ipv6_sock_mc_join</code></li>
<li><code>sock_kmalloc</code></li>
<li><code>sockopt_release_sock</code></li>
</ul>
<p>The <code>ipv6_sock_mc_join</code> (&ldquo;ipv6 socket multicast join&rdquo;) function catched our attention here since its role is to add the interface to the multicast group. It also calls <code>sock_kmalloc</code>.</p>
<p>(<code>ipv6_sock_mc_join</code> is a mere wrapper of <code>__ipv6_sock_mc_join</code> that just adds an argument).</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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">__ipv6_sock_mc_join</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">int</span> <span class="n">ifindex</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">			       <span class="k">const</span> <span class="k">struct</span> <span class="n">in6_addr</span> <span class="o">*</span><span class="n">addr</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">mode</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">net_device</span> <span class="o">*</span><span class="n">dev</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">ipv6_mc_socklist</span> <span class="o">*</span><span class="n">mc_lst</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">ipv6_pinfo</span> <span class="o">*</span><span class="n">np</span> <span class="o">=</span> <span class="nf">inet6_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">net</span> <span class="o">*</span><span class="n">net</span> <span class="o">=</span> <span class="nf">sock_net</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span> <span class="n">err</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_RTNL</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="o">!</span><span class="nf">ipv6_addr_is_multicast</span><span class="p">(</span><span class="n">addr</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">for_each_pmc_socklock</span><span class="p">(</span><span class="n">np</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">mc_lst</span><span class="p">)</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">ifindex</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">mc_lst</span><span class="o">-&gt;</span><span class="n">ifindex</span> <span class="o">==</span> <span class="n">ifindex</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">		    <span class="nf">ipv6_addr_equal</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mc_lst</span><span class="o">-&gt;</span><span class="n">addr</span><span class="p">,</span> <span class="n">addr</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="o">-</span><span class="n">EADDRINUSE</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></figure><p><code>struct ipv6_mc_socklist</code> structure represents a multicast group member, as seen earlier in the trace:
<code>{inet_pton(AF_INET6, &quot;ff02::2&quot;, &amp;ipv6mr_multiaddr), ipv6mr_interface=if_nametoindex(&quot;tap1&quot;)}</code></p>
<ul>
<li><code>addr</code>: multicast group address</li>
<li><code>ifindex</code>: index of the interface we want to join the multicast group with</li>
<li><code>sfmode</code>: set to MCAST_EXCLUDE (this is the result of the use of the ipv6_sock_mc_join wrapper, that&rsquo;s the <code>mode</code> argument of __ipv6_sock_mc_join)</li>
<li><code>rcu</code>: Read-Copy-Update, a mechanism ensuring the reading of valid data in the context of data read much more frequently than written, concurrently (see: <a href="https://www.kernel.org/doc/html/next/RCU/whatisRCU.html">https://www.kernel.org/doc/html/next/RCU/whatisRCU.html</a>)</li>
</ul>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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">ipv6_mc_socklist</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">in6_addr</span>		<span class="n">addr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="kt">int</span>			<span class="n">ifindex</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">sfmode</span><span class="p">;</span>		<span class="cm">/* MCAST_{INCLUDE,EXCLUDE} */</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">ipv6_mc_socklist</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">ip6_sf_socklist</span>	<span class="n">__rcu</span> <span class="o">*</span><span class="n">sflist</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">	<span class="k">struct</span> <span class="n">rcu_head</span>		<span class="n">rcu</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span></span></span></code></pre></div></figure><p>This struct is 56 bytes long on a 6.1 kernel.</p>
<figure class="code" data-lang="shell" aria-label="shell code"><figcaption><span class="code-lang" data-lang="shell" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy shell code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-shell" data-lang="shell"><span class="line"><span class="cl">gdb /usr/lib/debug/lib/modules/6.1.0-18-amd64/vmlinux
</span></span><span class="line"><span class="cl">GNU gdb <span class="o">(</span>Debian 13.1-3<span class="o">)</span> 13.1
</span></span><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span> print sizeof<span class="o">(</span>struct ipv6_mc_socklist<span class="o">)</span>
</span></span><span class="line"><span class="cl"><span class="nv">$1</span> <span class="o">=</span> <span class="m">56</span>
</span></span><span class="line"><span class="cl"><span class="o">(</span>gdb<span class="o">)</span></span></span></code></pre></div></figure><p>After a few checks the <code>sock_kmalloc</code> function is then called, with the following arguments :</p>
<ul>
<li><code>sk</code>: current socket structure</li>
<li><code>sizeof(struct ipv6_mc_socklist)</code>:  added structure length</li>
<li><code>GFP_KERNEL</code>: Get Free Page flag, controls memory allocator behavior (see: <a href="https://www.kernel.org/doc/html/next/core-api/memory-allocation.html">https://www.kernel.org/doc/html/next/core-api/memory-allocation.html</a>)</li>
</ul>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="p">[</span><span class="n">__ipv6_sock_mc_join</span> <span class="n">continued</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">	<span class="n">mc_lst</span> <span class="o">=</span> <span class="nf">sock_kmalloc</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">ipv6_mc_socklist</span><span class="p">),</span> <span class="n">GFP_KERNEL</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="o">!</span><span class="n">mc_lst</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">		<span class="k">return</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span> <span class="o">&lt;-</span> <span class="n">here</span> <span class="n">we</span> <span class="n">go</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[...]</span></span></span></code></pre></div></figure><p><code>sock_kmalloc</code> seems to yield NULL since what we get from <code>ipv6_sock_mc_join</code> is an <code>ENOMEM</code> and no other function in the code has been executed (see ftrace previous output)</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cm">/*
</span></span></span><span class="line"><span class="cl"><span class="cm"> * Allocate a memory block from the socket&#39;s option memory buffer.
</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span>
</span></span><span class="line"><span class="cl"><span class="kt">void</span> <span class="o">*</span><span class="nf">sock_kmalloc</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">int</span> <span class="n">size</span><span class="p">,</span> <span class="kt">gfp_t</span> <span class="n">priority</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">optmem_max</span> <span class="o">=</span> <span class="nf">READ_ONCE</span><span class="p">(</span><span class="nf">sock_net</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">core</span><span class="p">.</span><span class="n">sysctl_optmem_max</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="kt">unsigned</span> <span class="kt">int</span><span class="p">)</span><span class="n">size</span> <span class="o">&lt;=</span> <span class="n">optmem_max</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">	    <span class="nf">atomic_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_omem_alloc</span><span class="p">)</span> <span class="o">+</span> <span class="n">size</span> <span class="o">&lt;</span> <span class="n">optmem_max</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="kt">void</span> <span class="o">*</span><span class="n">mem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="cm">/* First do the add, to avoid the race if kmalloc
</span></span></span><span class="line"><span class="cl"><span class="cm">		 * might sleep.
</span></span></span><span class="line"><span class="cl"><span class="cm">		 */</span>
</span></span><span class="line"><span class="cl">		<span class="nf">atomic_add</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_omem_alloc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="n">mem</span> <span class="o">=</span> <span class="nf">kmalloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">priority</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">mem</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">			<span class="k">return</span> <span class="n">mem</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">		<span class="nf">atomic_sub</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_omem_alloc</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">return</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></figure><p><code>sk_omem_alloc</code> (socket option/other memory alloc) is an <code>atomic_t</code> variable type (that&rsquo;s an int32_t) that holds the grand total of every memory allocation that were made on this socket for the option buffer (see <code>atomic_add(size, &amp;sk-&gt;sk_omem_alloc);</code>)</p>
<p><code>optmem_max</code> is the <code>net.core.optmem_max</code> sysctl value. That&rsquo;s the maximal memory size that can be allocated for a socket&rsquo;s option buffer. This is set to 20480 on our hypervisors.</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> sysctl net.core.optmem_max
</span></span><span class="line"><span class="cl"><span class="go">net.core.optmem_max = 20480
</span></span></span></code></pre></div></figure><p>Basically there is a maximum size for the option buffer of a socket, which is set by the <code>net.core.optmem_max</code> sysctl setting. The <code>struct ipv6_mc_socklist</code> structure that stores multicast group member interfaces data is 56 bytes long. In our setup, it takes 365 group members to entirely fill the option buffer of that socket, taking 204440 bytes. The 366th member will result in an <code>ENOMEM</code> as 20486 bytes would be required to store this new member alongside the existing ones.</p>
<p>In theory, this buffer should not be entierly filled, however radvd does not remove any interface from the multicast group when an interface is removed (tap VM interface are dynamically created and removed on-the-fly when a VM boots). The more connections there are to this server, the less space is available in the &ldquo;option&rdquo; buffer. This can only be fixed via a service restart (we can also increase the value of <code>net.core.optmem_max</code> but the issue will come back if the value is not high enough, and this is a very temporary fix)</p>
<h2 id="the-fix"><a class="h-anchor" href="#the-fix" data-marks="##" aria-hidden="true" tabindex="-1"></a>The fix</h2>
<p>We saw earlier that we can add members to an IPv6 multicast group by using <code>IPV6_ADD_MEMBERSHIP</code>. Shouldn&rsquo;t we remove them altogether? There&rsquo;s a <code>setsockopt</code> option named <code>IPV6_DROP_MEMBERSHIP</code> whose goal is to remove an interface from a multicast group.
That&rsquo;s the sole principle of the <code>cleanup_iface</code> function within radvd&rsquo;s code:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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">cleanup_iface</span><span class="p">(</span><span class="kt">int</span> <span class="n">sock</span><span class="p">,</span> <span class="k">struct</span> <span class="n">Interface</span> <span class="o">*</span><span class="n">iface</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="cm">/* leave the allrouters multicast group */</span>
</span></span><span class="line"><span class="cl">	<span class="nf">cleanup_allrouters_membership</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span> <span class="n">iface</span><span class="p">);</span>
</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></figure><p>Where <code>cleanup_allrouters_membership</code> is:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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">cleanup_allrouters_membership</span><span class="p">(</span><span class="kt">int</span> <span class="n">sock</span><span class="p">,</span> <span class="k">struct</span> <span class="n">Interface</span> <span class="o">*</span><span class="n">iface</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">ipv6_mreq</span> <span class="n">mreq</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="nf">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">mreq</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">mreq</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">	<span class="n">mreq</span><span class="p">.</span><span class="n">ipv6mr_interface</span> <span class="o">=</span> <span class="n">iface</span><span class="o">-&gt;</span><span class="n">props</span><span class="p">.</span><span class="n">if_index</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">	<span class="cm">/* ipv6-allrouters: ff02::2 */</span>
</span></span><span class="line"><span class="cl">	<span class="n">mreq</span><span class="p">.</span><span class="n">ipv6mr_multiaddr</span><span class="p">.</span><span class="n">s6_addr32</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nf">htonl</span><span class="p">(</span><span class="mh">0xFF020000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="n">mreq</span><span class="p">.</span><span class="n">ipv6mr_multiaddr</span><span class="p">.</span><span class="n">s6_addr32</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="nf">htonl</span><span class="p">(</span><span class="mh">0x2</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="nf">setsockopt</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span> <span class="n">SOL_IPV6</span><span class="p">,</span> <span class="n">IPV6_DROP_MEMBERSHIP</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">mreq</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">mreq</span><span class="p">));</span>
</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></figure><p><code>cleanup_iface</code> only gets called at the end of the program&rsquo;s execution.
Fix would be to call <code>cleanup_iface</code> for each interface deletion event:</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">	<span class="k">if</span> <span class="p">(</span><span class="n">nh</span><span class="o">-&gt;</span><span class="n">nlmsg_type</span> <span class="o">==</span> <span class="n">RTM_DELLINK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">		<span class="nf">dlog</span><span class="p">(</span><span class="n">LOG_INFO</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="s">&#34;netlink: %s removed, cleaning up&#34;</span><span class="p">,</span> <span class="n">iface</span><span class="o">-&gt;</span><span class="n">props</span><span class="p">.</span><span class="n">name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">		<span class="nf">cleanup_iface</span><span class="p">(</span><span class="n">icmp_sock</span><span class="p">,</span> <span class="n">iface</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">	<span class="p">}</span></span></span></code></pre></div></figure><p>A <a href="https://github.com/radvd-project/radvd/issues/228">patch has been submitted to radvd</a>.</p>
<p>Thanks to <a href="https://twitter.com/xdbob">@xdbob</a> for the patch, <a href="https://eahlys.me/">Claire</a> for the network debugging and <a href="https://twitter.com/cedricmaunoury">@cedricmaunoury</a> for the help during the bug search!</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>We did not have any logs due to radvd logging by default to stdout and then daemonized itself, closing those fd&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded></item><item><title>Retrieving the temperature of an NVIDIA GPU from a GTX 1080 on Linux, without nouveau or nvidia_drm</title><link>https://sanecz.net/blog/post-1/</link><guid isPermaLink="true">https://sanecz.net/blog/post-1/</guid><pubDate>Fri, 03 Mar 2023 09:00:00 -0700</pubDate><dc:creator>sanecz</dc:creator><category>gpu</category><category>nvidia</category><category>qemu</category><category>vfio</category><category>monitoring</category><description>I have several NVIDIA GPUs (~25,000) in passthrough on VMs, but I need to monitor their temperature from the hypervisor (qemu). There's no nvidia_smi available, no nouveau or nvidia_drm driver loaded, only vfio-pci and qemu here...</description><content:encoded><![CDATA[<p>I have several NVIDIA GPUs (~25,000) in passthrough on VMs, but I need to monitor their temperature from the hypervisor (qemu). There&rsquo;s no nvidia_smi available, no nouveau or nvidia_drm driver loaded, only vfio-pci and qemu here.</p>
<p>So, I dug into it to retrieve their temperatures from the hypervisor!</p>
<p>All tests will be conducted on a GTX 1080 (GP104). This technique works on the Pascal and Ampere families. The same logic could possibly be applied to other families; however, I&rsquo;m unable to test it.</p>
<h2 id="how-do-we-start-"><a class="h-anchor" href="#how-do-we-start-" data-marks="##" aria-hidden="true" tabindex="-1"></a>How do we start ?</h2>
<p>The envytools website provides a starting point for research and offers some leads, including the ranges of <code>MMIO (memory mapped i/o)</code> registers as well as the description of <code>PCI BARs (base register addresses)</code>. Particularly, this includes the <code>PTHERM</code> register.
The other lead is the kernel itself, hoping that the nouveau driver can retrieve the GPU temperature (spoiler: yes). It&rsquo;s just a matter of digging into the code to better understand what might be happening. In any case, a more specific idea is emerging: reading from the GPU memory, somewhere.</p>
<h2 id="lets-get-some-reading"><a class="h-anchor" href="#lets-get-some-reading" data-marks="##" aria-hidden="true" tabindex="-1"></a>Let&rsquo;s get some reading</h2>
<p>Snippet of code to retrieve the GPU temperature:
source: <strong>linux/drivers/gpu/drm/nouveau/nvkm/subdev/therm/gp100.c</strong></p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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></span><span class="line"><span class="cl"><span class="nf">gp100_temp_get</span><span class="p">(</span><span class="k">struct</span> <span class="n">nvkm_therm</span> <span class="o">*</span><span class="n">therm</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">nvkm_device</span> <span class="o">*</span><span class="n">device</span> <span class="o">=</span> <span class="n">therm</span><span class="o">-&gt;</span><span class="n">subdev</span><span class="p">.</span><span class="n">device</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">struct</span> <span class="n">nvkm_subdev</span> <span class="o">*</span><span class="n">subdev</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">therm</span><span class="o">-&gt;</span><span class="n">subdev</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">u32</span> <span class="n">tsensor</span> <span class="o">=</span> <span class="nf">nvkm_rd32</span><span class="p">(</span><span class="n">device</span><span class="p">,</span> <span class="mh">0x020460</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">u32</span> <span class="n">inttemp</span> <span class="o">=</span> <span class="p">(</span><span class="n">tsensor</span> <span class="o">&amp;</span> <span class="mh">0x0001fff8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="cm">/* device SHADOWed */</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">tsensor</span> <span class="o">&amp;</span> <span class="mh">0x40000000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="nf">nvkm_trace</span><span class="p">(</span><span class="n">subdev</span><span class="p">,</span> <span class="s">&#34;reading temperature from SHADOWed sensor</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="cm">/* device valid */</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">tsensor</span> <span class="o">&amp;</span> <span class="mh">0x20000000</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="p">(</span><span class="n">inttemp</span> <span class="o">&gt;&gt;</span> <span class="mi">8</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="k">return</span> <span class="o">-</span><span class="n">ENODEV</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></figure><p>source: <strong>linux/drivers/gpu/drm/nouveau/include/nvkm/core/device.h</strong></p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="c1">// Structure reduced for readability
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">nvkm_device</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">device</span> <span class="o">*</span><span class="n">dev</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">name</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">void</span> <span class="n">__iomem</span> <span class="o">*</span><span class="n">pri</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">list_head</span> <span class="n">subdev</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="p">[...]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#define nvkm_rd32(d,a) ioread32_native((d)-&gt;pri + (a))</span></span></span></code></pre></div></figure><p>The sensor value is retrieved using the nvkm_rd32 macro, which reads a 32-bit register at the offset <code>device-&gt;pri + 0x020460</code>.
Masks are then applied to the sensor value to obtain only the temperature. All of this needs to be kept in mind.</p>
<p>source: <strong>linux/drivers/gpu/drm/nouveau/nkvm/engine/device/base.c</strong></p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl">    <span class="p">[...]</span>
</span></span><span class="line"><span class="cl">    <span class="n">mmio_base</span> <span class="o">=</span> <span class="n">device</span><span class="o">-&gt;</span><span class="n">func</span><span class="o">-&gt;</span><span class="nf">resource_addr</span><span class="p">(</span><span class="n">device</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">mmio_size</span> <span class="o">=</span> <span class="n">device</span><span class="o">-&gt;</span><span class="n">func</span><span class="o">-&gt;</span><span class="nf">resource_size</span><span class="p">(</span><span class="n">device</span><span class="p">,</span> <span class="mi">0</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">device</span><span class="o">-&gt;</span><span class="n">pri</span> <span class="o">=</span> <span class="nf">ioremap</span><span class="p">(</span><span class="n">mmio_base</span><span class="p">,</span> <span class="n">mmio_size</span><span class="p">);</span></span></span></code></pre></div></figure><p><code>ioremap</code> will map the MMIO to a virtual memory space and return a pointer to the beginning of this space corresponding to the start of the GPU&rsquo;s physical memory at the requested address. Hence the use of <code>ioread32</code> since it is not recommended to directly dereference these addresses.</p>
<p>What do <code>mmio_base</code> and <code>mmio_size</code> correspond to? According to the envy documentation, the memory space of the MMIO registers is located on BAR 0 and is 16M in size. It is a 32-bit memory addressing area that is <code>non-prefetchable</code>.</p>
<p>So, <code>mmio_base</code> corresponds to the starting address of BAR 0, and <code>mmio_size</code> is 16M. This can also be verified using <code>lspci</code>.</p>
<figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> lspci -d 10de: -s .0 -vv
</span></span><span class="line"><span class="cl"><span class="go">02:00.0 VGA compatible controller: NVIDIA Corporation GP104 [GeForce GTX 1080] (rev a1) (prog-if 00 [VGA controller])
</span></span></span><span class="line"><span class="cl"><span class="go">Subsystem: ZOTAC International (MCO) Ltd. GP104 [GeForce GTX 1080]
</span></span></span><span class="line"><span class="cl"><span class="go">Control: I/O+ Mem+ BusMaster- SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx-
</span></span></span><span class="line"><span class="cl"><span class="go">Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast &gt;TAbort- &lt;TAbort- &lt;MAbort- &gt;SERR- &lt;PERR- INTx-
</span></span></span><span class="line"><span class="cl"><span class="go">Interrupt: pin A routed to IRQ 26
</span></span></span><span class="line"><span class="cl"><span class="go">NUMA node: 0
</span></span></span><span class="line"><span class="cl"><span class="go">IOMMU group: 45
</span></span></span><span class="line"><span class="cl"><span class="go">//   BAR 0         mmio_base                                mmio_size
</span></span></span><span class="line"><span class="cl"><span class="go">//     v               v                                       v
</span></span></span><span class="line"><span class="cl"><span class="go">Region 0: Memory at c6000000 (32-bit, non-prefetchable) [size=16M]
</span></span></span><span class="line"><span class="cl"><span class="go">Region 1: Memory at 27fe0000000 (64-bit, prefetchable) [size=256M]
</span></span></span><span class="line"><span class="cl"><span class="go">Region 3: Memory at 27ff0000000 (64-bit, prefetchable) [size=32M]
</span></span></span><span class="line"><span class="cl"><span class="go">Region 5: I/O ports at 6000 [size=128]
</span></span></span><span class="line"><span class="cl"><span class="go">Expansion ROM at c7000000 [disabled] [size=512K]
</span></span></span><span class="line"><span class="cl"><span class="go">[...]
</span></span></span><span class="line"><span class="cl"><span class="go">Kernel driver in use: vfio-pci
</span></span></span></code></pre></div></figure><h2 id="in-practice"><a class="h-anchor" href="#in-practice" data-marks="##" aria-hidden="true" tabindex="-1"></a>In practice</h2>
<p>Now we need to write a small piece of code that allows accessing my GPU via vfio, and read a value from the sensor at the previously retrieved offset, then apply the masks to extract the GPU temperature.</p>
<p>The code is quite basic and comes from the kernel documentation about vfio. In this example, I want to access the device <code>02:00.0</code> which is located in the <code>IOMMU group 45</code> (seen in the above lspci output).</p>
<figure class="code" data-lang="c" aria-label="c code"><figcaption><span class="code-lang" data-lang="c" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy c code"></button></figcaption><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;linux/vfio.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/ioctl.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;fcntl.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/mman.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl"><span class="cp">#include</span> <span class="cpf">&lt;stdint.h&gt;</span><span class="cp">
</span></span></span><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;errno.h&gt;</span><span class="cp">
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="kt">int</span> <span class="n">container</span><span class="p">,</span> <span class="n">group</span><span class="p">,</span> <span class="n">device</span><span class="p">,</span> <span class="n">i</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="k">struct</span> <span class="n">vfio_group_status</span> <span class="n">group_status</span> <span class="o">=</span>
</span></span><span class="line"><span class="cl">   <span class="p">{</span> <span class="p">.</span><span class="n">argsz</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">group_status</span><span class="p">)</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="k">struct</span> <span class="n">vfio_iommu_type1_info</span> <span class="n">iommu_info</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">argsz</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">iommu_info</span><span class="p">)</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="k">struct</span> <span class="n">vfio_iommu_type1_dma_map</span> <span class="n">dma_map</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">argsz</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dma_map</span><span class="p">)</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl"> <span class="k">struct</span> <span class="n">vfio_device_info</span> <span class="n">device_info</span> <span class="o">=</span> <span class="p">{</span> <span class="p">.</span><span class="n">argsz</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">device_info</span><span class="p">)</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Create a new container */</span>
</span></span><span class="line"><span class="cl"> <span class="n">container</span> <span class="o">=</span> <span class="nf">open</span><span class="p">(</span><span class="s">&#34;/dev/vfio/vfio&#34;</span><span class="p">,</span> <span class="n">O_RDWR</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">ioctl</span><span class="p">(</span><span class="n">container</span><span class="p">,</span> <span class="n">VFIO_GET_API_VERSION</span><span class="p">)</span> <span class="o">!=</span> <span class="n">VFIO_API_VERSION</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* Unknown API version */</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="o">!</span><span class="nf">ioctl</span><span class="p">(</span><span class="n">container</span><span class="p">,</span> <span class="n">VFIO_CHECK_EXTENSION</span><span class="p">,</span> <span class="n">VFIO_TYPE1_IOMMU</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">   <span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="cm">/* Doesn&#39;t support the IOMMU driver we want. */</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Open the group */</span>
</span></span><span class="line"><span class="cl"> <span class="n">group</span> <span class="o">=</span> <span class="nf">open</span><span class="p">(</span><span class="s">&#34;/dev/vfio/45&#34;</span><span class="p">,</span> <span class="n">O_RDONLY</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">group</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="k">return</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="cm">/* Test the group is viable and available */</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ioctl</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">VFIO_GROUP_GET_STATUS</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">group_status</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="o">!</span><span class="p">(</span><span class="n">group_status</span><span class="p">.</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">VFIO_GROUP_FLAGS_VIABLE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">   <span class="cm">/* Group is not viable (ie, not all devices bound for vfio) */</span>
</span></span><span class="line"><span class="cl">   <span class="k">return</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="cm">/* Add the group to the container */</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ioctl</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">VFIO_GROUP_SET_CONTAINER</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">container</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Enable the IOMMU model we want */</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ioctl</span><span class="p">(</span><span class="n">container</span><span class="p">,</span> <span class="n">VFIO_SET_IOMMU</span><span class="p">,</span> <span class="n">VFIO_TYPE1_IOMMU</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Get addition IOMMU info */</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ioctl</span><span class="p">(</span><span class="n">container</span><span class="p">,</span> <span class="n">VFIO_IOMMU_GET_INFO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">iommu_info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Allocate some space and setup a DMA mapping */</span>
</span></span><span class="line"><span class="cl"> <span class="n">dma_map</span><span class="p">.</span><span class="n">vaddr</span> <span class="o">=</span> <span class="nf">mmap</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">		      <span class="n">MAP_PRIVATE</span> <span class="o">|</span> <span class="n">MAP_ANONYMOUS</span><span class="p">,</span> <span class="mi">0</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">dma_map</span><span class="p">.</span><span class="n">size</span> <span class="o">=</span> <span class="mi">1024</span> <span class="o">*</span> <span class="mi">1024</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> <span class="n">dma_map</span><span class="p">.</span><span class="n">iova</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="cm">/* 1MB starting at 0x0 from device view */</span>
</span></span><span class="line"><span class="cl"> <span class="n">dma_map</span><span class="p">.</span><span class="n">flags</span> <span class="o">=</span> <span class="n">VFIO_DMA_MAP_FLAG_READ</span> <span class="o">|</span> <span class="n">VFIO_DMA_MAP_FLAG_WRITE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="nf">ioctl</span><span class="p">(</span><span class="n">container</span><span class="p">,</span> <span class="n">VFIO_IOMMU_MAP_DMA</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">dma_map</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Get a file descriptor for the device */</span>
</span></span><span class="line"><span class="cl"> <span class="n">device</span> <span class="o">=</span> <span class="nf">ioctl</span><span class="p">(</span><span class="n">group</span><span class="p">,</span> <span class="n">VFIO_GROUP_GET_DEVICE_FD</span><span class="p">,</span> <span class="s">&#34;0000:02:00.0&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Test and setup the device */</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ioctl</span><span class="p">(</span><span class="n">device</span><span class="p">,</span> <span class="n">VFIO_DEVICE_GET_INFO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">device_info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Working only on BAR 0 */</span>
</span></span><span class="line"><span class="cl"> <span class="k">struct</span> <span class="n">vfio_region_info</span> <span class="n">regs</span> <span class="o">=</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="p">.</span><span class="n">argsz</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">vfio_region_info</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">   <span class="p">.</span><span class="n">index</span>  <span class="o">=</span> <span class="mi">0</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">ioctl</span><span class="p">(</span><span class="n">device</span><span class="p">,</span> <span class="n">VFIO_DEVICE_GET_REGION_INFO</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">regs</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">ptr</span> <span class="o">=</span> <span class="nf">mmap</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">regs</span><span class="p">.</span><span class="n">size</span><span class="p">,</span> <span class="n">PROT_READ</span><span class="p">,</span> <span class="n">MAP_SHARED</span><span class="p">,</span> <span class="n">device</span><span class="p">,</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="cm">/* Stolen from you know where ;) */</span>
</span></span><span class="line"><span class="cl"> <span class="kt">uint32_t</span> <span class="n">tsensor</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="kt">uint32_t</span><span class="o">*</span><span class="p">)(</span><span class="n">ptr</span> <span class="o">+</span> <span class="mh">0x020460</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="kt">uint32_t</span> <span class="n">inttemp</span> <span class="o">=</span> <span class="p">(</span><span class="n">tsensor</span> <span class="o">&amp;</span> <span class="mh">0x0001fff8</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">tsensor</span> <span class="o">&amp;</span> <span class="mh">0x40000000</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;shadowed sensor</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">if</span> <span class="p">(</span><span class="n">tsensor</span> <span class="o">&amp;</span> <span class="mh">0x20000000</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;temp %d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="n">inttemp</span> <span class="o">&gt;&gt;</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"> <span class="cm">/* Gratuitous device reset and go... */</span>
</span></span><span class="line"><span class="cl"> <span class="nf">ioctl</span><span class="p">(</span><span class="n">device</span><span class="p">,</span> <span class="n">VFIO_DEVICE_RESET</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"> <span class="nf">munmap</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="n">regs</span><span class="p">.</span><span class="n">size</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></figure><figure class="code" data-lang="console" aria-label="console code"><figcaption><span class="code-lang" data-lang="console" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy console code"></button></figcaption><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> gcc vfio.c <span class="o">&amp;&amp;</span> ./a.out
</span></span><span class="line"><span class="cl"><span class="go">temp 28
</span></span></span></code></pre></div></figure><p>Here we can see that the GPU is at 28°C (they are water-cooled).
Unfortunately, the last problem is that if the GPU is already in use, for example via qemu, the <code>open()</code> call on the IOMMU group will return that it is already in use.
This time there is another solution, which is to directly access memory with qemu monitor, using the <code>xp</code> command.</p>
<p>I start a VM using this GPU, and using the base address retrieved from BAR0 (with pci info), we will access it by adding the offset of the register we previously retreived containing the temperature:</p>
<figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">(qemu) info pci
</span></span><span class="line"><span class="cl">[...]
</span></span><span class="line"><span class="cl">  Bus  2, device   0, function 0:
</span></span><span class="line"><span class="cl">    VGA controller: PCI device 10de:1b80
</span></span><span class="line"><span class="cl">      PCI subsystem 19da:1425
</span></span><span class="line"><span class="cl">      IRQ 0, pin A
</span></span><span class="line"><span class="cl">      BAR0: 32 bit memory at 0xc1000000 [0xc1ffffff].
</span></span><span class="line"><span class="cl">      BAR1: 64 bit prefetchable memory at 0x1000000000 [0x100fffffff].
</span></span><span class="line"><span class="cl">      BAR3: 64 bit prefetchable memory at 0x1010000000 [0x1011ffffff].
</span></span><span class="line"><span class="cl">      BAR5: I/O at 0xb000 [0xb07f].
</span></span><span class="line"><span class="cl">      BAR6: 32 bit memory at 0xffffffffffffffff [0x0007fffe].
</span></span><span class="line"><span class="cl">[..]
</span></span><span class="line"><span class="cl"># We read a 32-bit register at position 0xc1020460
</span></span><span class="line"><span class="cl">(qemu) xp /1w (0xc1000000 + 0x020460)
</span></span><span class="line"><span class="cl">00000000c1020460: 0x20001ca0</span></span></code></pre></div></figure><figure class="code" data-lang="text" aria-label="text code"><figcaption><span class="code-lang" data-lang="text" aria-hidden="true"></span><button class="code-copy" type="button" data-label="copy" data-copied="copied" aria-label="copy text code"></button></figcaption><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl"># We apply the shifts retrieved from the code in nouveau
</span></span><span class="line"><span class="cl">In [1]: (0x20001ca0 &amp; 0x0001fff8) &gt;&gt; 8
</span></span><span class="line"><span class="cl">Out[1]: 28 &lt;-- la temperature</span></span></code></pre></div></figure><h2 id="conclusion"><a class="h-anchor" href="#conclusion" data-marks="##" aria-hidden="true" tabindex="-1"></a>Conclusion</h2>
<p>With all this, it is possible to monitor the temperature of these GPUs very simply from the host, using these two techniques, provided that they do not interfere with the VM startup.</p>
<p>However, it&rsquo;s not over yet; on some models, there are still other sensors to retrieve&hellip;
It was a pretty fun little project; maybe I&rsquo;ll try it on other families of NVIDIA GPUs if I can get my hands on them, or even maybe some AMD GPUs.</p>
]]></content:encoded></item></channel></rss>