<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Eduardo Vasconcelos - Linux</title><link href="https://vasconcedu.github.io/" rel="alternate"/><link href="https://vasconcedu.github.io/feeds/linux.atom.xml" rel="self"/><id>https://vasconcedu.github.io/</id><updated>2026-06-25T00:00:00-03:00</updated><subtitle>--talk; ++theory; terminal += 2;</subtitle><entry><title>Study notes on the Linux Security Module (LSM) kernel framework--Part 2</title><link href="https://vasconcedu.github.io/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-2.html" rel="alternate"/><published>2026-06-25T00:00:00-03:00</published><updated>2026-06-25T00:00:00-03:00</updated><author><name>vasconcedu</name></author><id>tag:vasconcedu.github.io,2026-06-25:/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-2.html</id><summary type="html">&lt;p&gt;This blog post is a continuation to &lt;a href="https://vasconcedu.github.io/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-1.html"&gt;my previous study notes&lt;/a&gt; about the inner workings of the LSM kernel framework. Previously, I've established an understanding of where LSMs are positioned in the kernel's call sequence that leads to accessing system resources, how LSMs initialize, define and register call hooks based …&lt;/p&gt;</summary><content type="html">&lt;p&gt;This blog post is a continuation to &lt;a href="https://vasconcedu.github.io/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-1.html"&gt;my previous study notes&lt;/a&gt; about the inner workings of the LSM kernel framework. Previously, I've established an understanding of where LSMs are positioned in the kernel's call sequence that leads to accessing system resources, how LSMs initialize, define and register call hooks based on a static calls table data structure.&lt;/p&gt;
&lt;p&gt;In this blog post, I aim at understanding how an LSM's codebase integrates into the kernel from a development standpoint, and how one should go about programming an LSM from the ground up. In order to achieve that, I'll look at the functioning of a specific LSM--namely Yama--, by establishing intuition about its threat model, then collecting practical evidence on how it changes the behavior of a live system, and finally moving to looking at its source code to understand its implementation. To conclude, I'll leverage the insights resulting from that exercise to program my own proof of concept LSM and boot into a kernel that loads it to hopefully bring it to life.&lt;/p&gt;
&lt;h3&gt;Why Yama?&lt;/h3&gt;
&lt;p&gt;Before we begin, I should like to clarify why I picked Yama as a case study over the other LSMs in the kernel's codebase. The answer to that is because Yama hits a sweet spot between complexity and simplicity. With less than 500 LOC, only four security hooks, and a very well defined threat model, Yama delivers plenty of material for analysis, but still without being overwhelming to analyze. That said, before we delve into Yama's codebase, let's get a glimpse at the problem that led to its development in first place.&lt;/p&gt;
&lt;h3&gt;The threat model that Yama addresses&lt;/h3&gt;
&lt;p&gt;As stated in &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/LSM/Yama.html"&gt;the kernel's documentation for the Yama LSM&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Yama is a Linux Security Module that collects system-wide DAC security protections that are not handled by the core kernel itself."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;More specifically, Yama addresses a threat model where processes running under the same user UID may act maliciously toward each other. As obvious as that might read, such threat model has only emerged in the last few decades. In the early days of computing, &lt;a href="https://www.systemshardening.com/articles/linux/linux-ptrace-yama-hardening/"&gt;processes belonging to the same user were considered equally trusted&lt;/a&gt;. Of course, that premise simply doesn't hold anymore, but what that entails is that historically--as naive as it may appear from a modern security engineering perspective--, certain aspects of operating systems were originally designed with that threat model in mind. Such is the case of &lt;a href="https://man7.org/linux/man-pages/man2/ptrace.2.html"&gt;the &lt;code&gt;ptrace()&lt;/code&gt; syscall&lt;/a&gt;. From &lt;code&gt;man&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"The ptrace() system call provides a means by which one process (the "tracer") may observe and control the execution of another process (the "tracee"), and examine and change the tracee's memory and registers. It is primarily used to implement breakpoint debugging and system call tracing."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From the description above, it's not hard to understand why uncontrolled access to &lt;code&gt;ptrace()&lt;/code&gt; might be troublesome. If a malicious process were able to attach to another process through &lt;code&gt;ptrace()&lt;/code&gt;, it would have full control not only over the tracee's data, but also over its execution flow, leading to full process compromise.&lt;/p&gt;
&lt;p&gt;The way Yama addresses the threat model above is by establishing &lt;a href="https://www.systemshardening.com/articles/linux/linux-ptrace-yama-hardening/#the-four-scope-levels"&gt;four so called "scope levels"&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Scope level 0 is the default kernel policy where any process is able to &lt;code&gt;ptrace()&lt;/code&gt; processes belonging to the same UID;&lt;/li&gt;
&lt;li&gt;Scope level 1 allows parent processes to trace their children. Processes to whom tracees &lt;a href="https://man7.org/linux/man-pages/man2/pr_set_ptracer.2const.html"&gt;explicitly grant permission via &lt;code&gt;prctl()&lt;/code&gt;&lt;/a&gt; and root are also allowed to &lt;code&gt;ptrace()&lt;/code&gt; processes;&lt;/li&gt;
&lt;li&gt;Scope level 2 restricts tracing to processes with &lt;a href="https://man7.org/linux/man-pages/man7/capabilities.7.html"&gt;capability &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt;&lt;/a&gt;, such as root user processes; and&lt;/li&gt;
&lt;li&gt;Scope level 3 disables tracing entirely, even to the root user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When Yama is enabled, the scope level in which it is operating can be examined by issuing: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sysctl&lt;span class="w"&gt; &lt;/span&gt;kernel.yama.ptrace_scope
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Furthermore, it is possible to change Yama's scope level by issuing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;sysctl&lt;span class="w"&gt; &lt;/span&gt;-w&lt;span class="w"&gt; &lt;/span&gt;kernel.yama.ptrace_scope&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;scope&lt;span class="w"&gt; &lt;/span&gt;level&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Where &lt;code&gt;&amp;lt;scope level&amp;gt;&lt;/code&gt; is the desired Yama scope level.&lt;/p&gt;
&lt;h3&gt;How Yama changes the behavior of a live system&lt;/h3&gt;
&lt;p&gt;Before we delve into Yama's implementation internals, let's see how a live system behaves with respect to Yama's scope levels. To achieve that, we'll perform an anecdotal experiment. I've programmed &lt;a href="https://github.com/vasconcedu/yama-lsm-pocs"&gt;a pair of proof of concept user space programs to help in this exercise&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In the GitHub repository linked above, program &lt;code&gt;target.c&lt;/code&gt; implements the tracee; and&lt;/li&gt;
&lt;li&gt;Program &lt;code&gt;dump_mem.c&lt;/code&gt; implements the tracer. It attaches to &lt;code&gt;target.c&lt;/code&gt;'s process to actively dump part of its memory space--namely 64 words of its stack.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the idea here is not to exercise the entirety of Yama's functionality and configuration, such programs should help us establish enough intuition about how Yama changes the behavior of a live system before we look at its source code. Program &lt;code&gt;target.c&lt;/code&gt; is shown below:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;stdio.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;unistd.h&amp;gt;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;pid_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;My PID is: %d (0x%x)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a: %c&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b: %c&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;c: %c&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Press any key to continue...&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;getchar&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;argc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;pid_t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;a&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 61&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 62&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sc"&gt;&amp;#39;c&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 63&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By inspecting the code above, we see that &lt;code&gt;target.c&lt;/code&gt; reads its own PID, then passes it as an argument, along with the characters &lt;code&gt;'a'&lt;/code&gt;, &lt;code&gt;'b'&lt;/code&gt; and &lt;code&gt;'c'&lt;/code&gt;, to function &lt;code&gt;f()&lt;/code&gt;. In turn, &lt;code&gt;f()&lt;/code&gt; prints all such values to stdout, then waits for the user to press a key before returning, leading to the program exiting.&lt;/p&gt;
&lt;p&gt;In turn, &lt;code&gt;dump_mem.c&lt;/code&gt; expects the tracee's PID as a command line argument, attaches to it while it is waiting for user input at &lt;code&gt;getchar()&lt;/code&gt;, then does the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PTRACE_GETREGS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PTRACE_PEEKDATA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%016lx: %016lx&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;While usage details of &lt;code&gt;ptrace()&lt;/code&gt; syscalls in user space are out of the scope of this blog post--please look at &lt;a href="https://www.man7.org/linux/man-pages/man2/ptrace.2.html"&gt;the corresponding man page&lt;/a&gt; for detail--, the code snippet above shows that &lt;code&gt;dump_mem.c&lt;/code&gt; first retrieves the tracee's stack pointer, then dumps 64 words starting at the address in SP, namely the 64 topmost words of the tracee's stack. Since the tracee is blocked at &lt;code&gt;getchar()&lt;/code&gt; while the tracer dumps memory words, function &lt;code&gt;f()&lt;/code&gt;'s segment is dumped to stdout. The fact that the segment contains the sequence of characters from &lt;code&gt;'a'&lt;/code&gt; to &lt;code&gt;'c'&lt;/code&gt;--which translates to the sequence of integers from &lt;code&gt;61&lt;/code&gt; to &lt;code&gt;63&lt;/code&gt;--is intentional to aid in spotting it amid dumped data, serving as evidence that we're indeed dumping the tracee's stack.&lt;/p&gt;
&lt;p&gt;Let's first take a look at the scope level that is currently configured for Yama in our Arch Linux lab host. If we issue &lt;code&gt;sysctl kernel.yama.ptrace_scope&lt;/code&gt;, here's what we'll see:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Yama scope level 1 output." src="../images/yama-scope-1.png"&gt;&lt;/p&gt;
&lt;p&gt;Indicating that the system is at scope level 1, meaning that only a tracee's parent, root processes, or processes to whom the tracee has explicitly granted &lt;code&gt;PR_SET_PTRACER&lt;/code&gt; via &lt;code&gt;prctl()&lt;/code&gt; should be able to attach to any given process for tracing. While we won't exhaust these possibilities here, let's see what happens when an unprivileged &lt;code&gt;dump_mem.c&lt;/code&gt; process tries to attach to &lt;code&gt;target.c&lt;/code&gt;'s process. First, we run the target process:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Scope level 1: tracee execution." src="../images/yama-scope-poc-1.png"&gt;&lt;/p&gt;
&lt;p&gt;The tracee prints its PID to stdout--i.e. 576, or 240 in hexadecimal notation--, along with the arguments to &lt;code&gt;f()&lt;/code&gt;, and blocks at &lt;code&gt;getchar()&lt;/code&gt;. That gives us a chance to run the tracer. First, let's run it unprivileged:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Scope level 1: tracer error." src="../images/yama-scope-poc-1-error.png"&gt;&lt;/p&gt;
&lt;p&gt;As expected, the tracer is unable to attach to the tracee. Let's run it again, this time with root's UID:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Scope level 1: tracer execution." src="../images/yama-scope-poc-1-dumped.png"&gt;&lt;/p&gt;
&lt;p&gt;And the execution seems to succeed. Indeed, if we look at the output at &lt;code&gt;/tmp/dump.txt&lt;/code&gt; by issuing &lt;code&gt;cat /tmp/dump.txt | grep 616263&lt;/code&gt;, here's what we'll see:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Scope level 1: dumped output." src="../images/yama-scope-poc-1-dumped-output.png"&gt;&lt;/p&gt;
&lt;p&gt;Confirming that, indeed, we have dumped the tracee's stack. Not only that, we collected anecdotal evidence that Yama seems to behave--at least partially--as defined in its documentation at scope level 1. Let's move on to another scope level. If we change Yama's scope level to 0 and run the tracer process again, without root privilege:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Scope level 0: proof of concept." src="../images/yama-scope-poc-0.png"&gt;&lt;/p&gt;
&lt;p&gt;We confirm that we've been able to dump the tracee's stack, now from an unprivileged tracer--just as expected. Yama's scope level 2 restricts tracing to processes with capability &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt;. While &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt; is inherent to root processes, scope level 2 differs from scope level 1 in that it only allows &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt; processes to perform tracing. For simplicity's sake, we'll skip scope level 2 here, as the limitations inherent to our proof of concept programs would lead to observing system behavior identical to that of scope level 1 in scope level 2--succeeding under root's UID, failing otherwise. Distinguishing between scope levels 1 and 2 would require capability manipulation, which is out of the scope of the exercise herein. That said, let's move on to scope level 3, where tracing is disabled entirely:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NOTE: Please bear in mind that after changing Yama's scope level to 3, only a reboot will change it back to its original value at scope level 1 as scope level 3 is Yama's maximum security level, and &lt;code&gt;sysctl&lt;/code&gt; calls are not allowed to change it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Scope level 3: proof of concept." src="../images/yama-scope-poc-3.png"&gt;&lt;/p&gt;
&lt;p&gt;As expected, now, not even root processes are able to attach to the tracee.&lt;/p&gt;
&lt;p&gt;That concludes our anecdotal experiment. We've now established enough intuition about Yama's functioning and how it impacts the behavior of a live system, and we can now move on to studying the LSM's implementation at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/"&gt;security/yama/&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;A look at Yama's source code&lt;/h3&gt;
&lt;p&gt;In this section, we'll inspect part of the source code of the Yama LSM with the goal of understanding its implementation well enough to build our own proof of concept LSM later on.&lt;/p&gt;
&lt;p&gt;First, let's take a look at Yama's Kconfig file at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/Kconfig"&gt;security/yama/Kconfig&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# SPDX-License-Identifier: GPL-2.0-only&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SECURITY_YAMA&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb nb-Type"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Yama support&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;depends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SECURITY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;selects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Yama&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;which&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DAC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;support&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;additional&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;wide&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;beyond&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;regular&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Linux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;discretionary&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;access&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;controls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Currently&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ptrace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;restriction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;Like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stacks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LSMs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;Further&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;information&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;Documentation&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;guide&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;LSM&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Yama&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rst&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="n"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unsure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Kconfig files follow the conventions established in the kernel's &lt;a href="https://docs.kernel.org/kbuild/kconfig-language.html"&gt;Kconfig language&lt;/a&gt;, and &lt;a href="https://en.wikipedia.org/wiki/Menuconfig"&gt;serve the purpose of defining the configuration interface for kernel features and modules prior to compilation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What Yama's Kconfig file does is &lt;a href="https://docs.kernel.org/kbuild/kconfig-language.html#menu-entries"&gt;it defines a new configuration option&lt;/a&gt;, namely &lt;code&gt;SECURITY_YAMA&lt;/code&gt;, &lt;a href="https://docs.kernel.org/kbuild/kconfig-language.html#menu-attributes"&gt;of type &lt;code&gt;bool&lt;/code&gt;, and input prompt "Yama support"&lt;/a&gt;. The input prompt is the string that is displayed to the developer at compilation time. Such configuration option defaults to "n"--i.e. "no", meaning that it is disabled by default--, and depends on the &lt;code&gt;SECURITY&lt;/code&gt; configuration. In turn, the &lt;code&gt;SECURITY&lt;/code&gt; configuration is defined in the security subsystem's Kconfig file at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/Kconfig#n73"&gt;&lt;code&gt;security/Kconfig&lt;/code&gt;&lt;/a&gt;. Here's what it looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;#&lt;span class="w"&gt; &lt;/span&gt;...

&lt;span class="nv"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SECURITY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Enable different security models&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;depends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;SYSFS&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;depends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;MULTIUSER&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;help&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;allows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;choose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;different&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;security&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;modules&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;be&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;configured&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;into&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;kernel&lt;/span&gt;.

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;option&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;selected&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Linux&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;security&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nv"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;used&lt;/span&gt;.

&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="k"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;are&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;unsure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;how&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;answer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;question&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;answer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;N&lt;/span&gt;.

#&lt;span class="w"&gt; &lt;/span&gt;...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The help text in the snippet above makes things a lot clearer: in order for us to be able to use any given LSM, the security subsystem's &lt;code&gt;SECURITY&lt;/code&gt; configuration must be enabled, which explains the dependency specified in Yama's Kconfig.&lt;/p&gt;
&lt;p&gt;To conclude, Yama's Kconfig confirms what we've been discussing about Yama with respect to its scope, and adds that it stacks with other LSMs. In practice, this means that &lt;a href="https://www.apparmor.net/about/lsm_introduction/"&gt;Yama can be used in conjunction with other minor LSMs deployed in a system&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Having understood Yama's configuration, let's now briefly visit its &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/Makefile"&gt;Makefile at &lt;code&gt;/security/yama/Makefile&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# SPDX-License-Identifier: GPL-2.0-only&lt;/span&gt;
&lt;span class="nv"&gt;obj-$(CONFIG_SECURITY_YAMA)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yama.o

&lt;span class="nv"&gt;yama-y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;yama_lsm.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;What this file does is it checks whether &lt;code&gt;CONFIG_SECURITY_YAMA&lt;/code&gt; is enabled in the kernel's &lt;code&gt;.config&lt;/code&gt; file at compilation time. If so, this results in the build system creating object file &lt;code&gt;yama.o&lt;/code&gt; by linking &lt;code&gt;yama_lsm.o&lt;/code&gt;--compiled from &lt;code&gt;yama_lsm.c&lt;/code&gt;--into the resulting Yama LSM kernel module.&lt;/p&gt;
&lt;p&gt;The last file in Yama's codebase is the module's source code itself at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c"&gt;&lt;code&gt;security/yama/yama_lsm.c&lt;/code&gt;&lt;/a&gt;. We'll now delve into it by breaking it into pieces to make it easier to analyze. The first thing that draws our attention when we open &lt;code&gt;yama_lsm.c&lt;/code&gt; for inspection is &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c#n23"&gt;a list of preprocessor directives specifying Yama's scope levels&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;

&lt;span class="cp"&gt;#define YAMA_SCOPE_DISABLED 0&lt;/span&gt;
&lt;span class="cp"&gt;#define YAMA_SCOPE_RELATIONAL   1&lt;/span&gt;
&lt;span class="cp"&gt;#define YAMA_SCOPE_CAPABILITY   2&lt;/span&gt;
&lt;span class="cp"&gt;#define YAMA_SCOPE_NO_ATTACH    3&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ptrace_scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;YAMA_SCOPE_RELATIONAL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Furthermore, in accordance with what we observed in our anecdotal experiment, we see that Yama defines its default scope level at scope level 1, namely &lt;code&gt;YAMA_SCOPE_RELATIONAL&lt;/code&gt;. If we scroll further down into the file, we'll find &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c#n419"&gt;a couple of familiar data structures&lt;/a&gt;, namely the definition of the LSM's ID and hook list, similar to the ones discussed in &lt;a href="https://vasconcedu.github.io/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-1.html"&gt;my previous blog post where I case studied AppArmor's hook registration routine&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yama_lsmid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;yama&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LSM_ID_YAMA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yama_hooks&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__ro_after_init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptrace_access_check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yama_ptrace_access_check&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptrace_traceme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yama_ptrace_traceme&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_prctl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yama_task_prctl&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;task_free&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yama_task_free&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From the code snippet above, it's interesting to note that Yama initializes a total of four hooks specified in &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hook_defs.h"&gt;the LSM interface&lt;/a&gt;, namely:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/core-api/kernel-api.html#c.security_ptrace_access_check"&gt;&lt;code&gt;ptrace_access_check()&lt;/code&gt;&lt;/a&gt;: this hook is used to check whether a process should be allowed to trace a child process;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/core-api/kernel-api.html#c.security_ptrace_traceme"&gt;&lt;code&gt;ptrace_traceme()&lt;/code&gt;&lt;/a&gt;: this hook is used to check whether the parent of the current process is allowed to trace it prior to having the child process grant the parent tracing permission;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/core-api/kernel-api.html#c.security_task_prctl"&gt;&lt;code&gt;task_prctl()&lt;/code&gt;&lt;/a&gt;: this hook is used to check if a &lt;code&gt;prctl()&lt;/code&gt; operation should be allowed on the current process; and&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.kernel.org/core-api/kernel-api.html#c.security_task_free"&gt;&lt;code&gt;task_free()&lt;/code&gt;&lt;/a&gt;: this hook is used to free a process' LSM blob.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Furthermore, if we &lt;code&gt;git grep LSM_ID_YAMA&lt;/code&gt;, we'll find that the LSM's ID is defined at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/lsm.h#n59"&gt;&lt;code&gt;include/uapi/linux/lsm.h&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="cp"&gt;#define LSM_ID_YAMA     105&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;If we scroll further down, we'll find another mandatory code structure, namely &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c#n478"&gt;a call to macro &lt;code&gt;DEFINE_LSM&lt;/code&gt;&lt;/a&gt;, making the LSM framework recognize the Yama LSM by pointing to its ID and init function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="n"&gt;DEFINE_LSM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yama&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;yama_lsmid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yama_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Speaking of &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c#n470"&gt;Yama's init function&lt;/a&gt;, here's what it looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;yama_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pr_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yama: becoming mindful.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;security_add_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yama_hooks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ARRAY_SIZE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;yama_hooks&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;yama_lsmid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;yama_init_sysctl&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;From the code snippet above, we see that all the init function does is it registers Yama's hook list by calling &lt;code&gt;security_add_hooks()&lt;/code&gt;--again, I already examined this process while case studying AppArmor in &lt;a href="https://vasconcedu.github.io/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-1.html"&gt;my previous blog post about the kernel's LSM framework&lt;/a&gt;, so I won't get into the details here--, and initializes the LSM's &lt;code&gt;sysctl&lt;/code&gt; interface with a call to &lt;code&gt;yama_init_sysctl()&lt;/code&gt;. Thus, part of Yama's codebase deals with the LSM's &lt;code&gt;sysctl&lt;/code&gt; administration interface, and part deals with the security hooks themselves.&lt;/p&gt;
&lt;p&gt;We'll now focus on the latter and delve into one of Yama's hook definitions, namely &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c#n349"&gt;&lt;code&gt;yama_ptrace_access_check()&lt;/code&gt;&lt;/a&gt;, whose code I've reproduced below for convenience. What follows is an analysis of its inner workings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;yama_ptrace_access_check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;task_struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="kt"&gt;unsigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* require ptrace target be a child of ptracer on attach */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PTRACE_MODE_ATTACH&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptrace_scope&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YAMA_SCOPE_DISABLED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cm"&gt;/* No additional restrictions. */&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YAMA_SCOPE_RELATIONAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rcu_read_lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pid_alive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EPERM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;task_is_descendant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ptracer_exception_found&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ns_capable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__task_cred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CAP_SYS_PTRACE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EPERM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rcu_read_unlock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YAMA_SCOPE_CAPABILITY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rcu_read_lock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ns_capable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__task_cred&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;user_ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CAP_SYS_PTRACE&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EPERM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rcu_read_unlock&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;case&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;YAMA_SCOPE_NO_ATTACH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;EPERM&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PTRACE_MODE_NOAUDIT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;report_access&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;attach&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;child&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;rc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;By inspecting the code snippet above, we see that &lt;code&gt;yama_ptrace_access_check()&lt;/code&gt; expects two arguments, namely &lt;code&gt;child&lt;/code&gt;, a &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/sched.h#n826"&gt;struct of type &lt;code&gt;struct task_struct&lt;/code&gt; defined at &lt;code&gt;include/linux/sched.h&lt;/code&gt;, representing the process that the current process is trying to &lt;code&gt;ptrace()&lt;/code&gt;&lt;/a&gt;, and an integer containing ORed &lt;a href="https://www.man7.org/linux/man-pages/man2/ptrace.2.html"&gt;&lt;code&gt;PTRACE_MODE_*&lt;/code&gt; flags&lt;/a&gt;, defining the &lt;code&gt;ptrace()&lt;/code&gt; access mode of the operation in question.&lt;/p&gt;
&lt;p&gt;First, the function defines its return value, namely &lt;code&gt;rc&lt;/code&gt;, whose value is initialized to &lt;code&gt;0&lt;/code&gt;, &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/sched.h#n826"&gt;which translates to a permission grant&lt;/a&gt;. What follows is a check of whether the &lt;code&gt;PTRACE_MODE_*&lt;/code&gt; flag contains &lt;a href="https://www.man7.org/linux/man-pages/man2/ptrace.2.html"&gt;a write operation&lt;/a&gt;, in which case the function switches &lt;code&gt;ptrace_scope&lt;/code&gt;--i.e. Yama's current scope level--, branching to specific routines according to the scope level found.&lt;/p&gt;
&lt;p&gt;At scope level 0--i.e. &lt;code&gt;YAMA_SCOPE_DISABLED&lt;/code&gt;--, the case breaks and returns immediately. At scope level 3--i.e. &lt;code&gt;YAMA_SCOPE_NO_ATTACH&lt;/code&gt;--, the function assigns &lt;code&gt;rc&lt;/code&gt; to &lt;code&gt;-EPERM&lt;/code&gt;, indicating that the hook reached a state of insufficient permission, then the case breaks and the function falls into a check of whether &lt;code&gt;PTRACE_MODE_*&lt;/code&gt; does not contain &lt;code&gt;PTRACE_MODE_NOAUDIT&lt;/code&gt;. &lt;a href="https://www.man7.org/linux/man-pages/man2/ptrace.2.html"&gt;Such flag is used in certain use cases to indicate that the access should not be added to audit logs&lt;/a&gt;. If it is not present, the function goes on to call &lt;code&gt;report_access()&lt;/code&gt; to log the fact that the &lt;code&gt;ptrace()&lt;/code&gt; syscall resulted in a failure to attach.&lt;/p&gt;
&lt;p&gt;Now, let's look at the other cases. Scope level 1--i.e. &lt;code&gt;YAMA_SCOPE_RELATIONAL&lt;/code&gt;--results in the following routine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, the function acquires &lt;a href="https://docs.kernel.org/RCU/whatisRCU.html#rcu-read-lock"&gt;the thread's RCU read lock&lt;/a&gt;, ensuring that the task data structures that it manipulates in the routine aren't deallocated prior to it exiting the critical region where it reads them;&lt;/li&gt;
&lt;li&gt;Next, it checks to see whether the child process is not dead, and if it is, the function assigns &lt;code&gt;rc&lt;/code&gt; to &lt;code&gt;-EPERM&lt;/code&gt;, and the routine concludes with the release of the RCU read lock, with the case breaking and the aforementioned check regarding logging being performed;&lt;/li&gt;
&lt;li&gt;In turn, if &lt;code&gt;rc&lt;/code&gt; is still &lt;code&gt;0&lt;/code&gt; at this point, the actual permission checks are performed, and if all of them fail, &lt;code&gt;rc&lt;/code&gt; is assigned to &lt;code&gt;-EPERM&lt;/code&gt;, the lock is released, the case breaks, and the very same check as before regarding logging is performed. Let's now step into the permission checks that this step actually entails.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first permission check is &lt;code&gt;!task_is_descendant(current, child)&lt;/code&gt;. &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c#n267"&gt;&lt;code&gt;task_is_descendant()&lt;/code&gt;&lt;/a&gt; checks whether &lt;code&gt;child&lt;/code&gt; is a descendant of &lt;code&gt;current&lt;/code&gt;, returning &lt;code&gt;1&lt;/code&gt; in case it is, and &lt;code&gt;0&lt;/code&gt; otherwise. Thus, &lt;code&gt;!task_is_descendant(current, child)&lt;/code&gt; resolves to &lt;code&gt;1&lt;/code&gt; when &lt;code&gt;child&lt;/code&gt; is not a descendant of &lt;code&gt;current&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The second permission check is &lt;code&gt;!ptracer_exception_found(current, child)&lt;/code&gt;. &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/yama/yama_lsm.c#n300"&gt;&lt;code&gt;ptracer_exception_found()&lt;/code&gt;&lt;/a&gt; checks whether &lt;code&gt;current&lt;/code&gt; is an authorized tracer of &lt;code&gt;child&lt;/code&gt;, either due to &lt;code&gt;current&lt;/code&gt; belonging to the same thread group as &lt;code&gt;child&lt;/code&gt;'s parent or due to &lt;code&gt;child&lt;/code&gt; having actively &lt;code&gt;prctl()&lt;/code&gt;ed to grant &lt;code&gt;current&lt;/code&gt; tracer permission, returning &lt;code&gt;1&lt;/code&gt; in case &lt;code&gt;current&lt;/code&gt; is an authorized tracer of &lt;code&gt;child&lt;/code&gt;, and &lt;code&gt;0&lt;/code&gt; otherwise. Thus, &lt;code&gt;!ptracer_exception_found(current, child)&lt;/code&gt; resolves to &lt;code&gt;1&lt;/code&gt; when &lt;code&gt;current&lt;/code&gt; is not allowed to trace &lt;code&gt;child&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, the third permission check is &lt;code&gt;!ns_capable(__task_cred(child)-&amp;gt;user_ns, CAP_SYS_PTRACE)&lt;/code&gt;. &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/capability.c#n361"&gt;&lt;code&gt;ns_capable()&lt;/code&gt;&lt;/a&gt; is a function defined at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/capability.c"&gt;kernel/capability.c&lt;/a&gt; that takes &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/user_namespace.h#n76"&gt;a struct of type &lt;code&gt;struct user_namespace&lt;/code&gt;&lt;/a&gt;--containing data such as the respective task owner's UID and GID, among other security-related fields--, and checks whether the current process has a given capability in that namespace, returning &lt;code&gt;1&lt;/code&gt; if so, and &lt;code&gt;0&lt;/code&gt; otherwise. More specifically, the call herein takes the user namespace field of the &lt;code&gt;child&lt;/code&gt;'s &lt;a href="https://docs.kernel.org/security/credentials.html"&gt;task credentials&lt;/a&gt;--i.e. the &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/cred.h#n115"&gt;security context of &lt;code&gt;child&lt;/code&gt;&lt;/a&gt;--, and checks to see if the current process has &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt; in said user namespace, in which case it should be allowed to trace &lt;code&gt;child&lt;/code&gt;. Thus, &lt;code&gt;!ns_capable(__task_cred(child)-&amp;gt;user_ns, CAP_SYS_PTRACE)&lt;/code&gt; resolves to &lt;code&gt;1&lt;/code&gt; when the current process does not have &lt;code&gt;CAP_SYS_PTRACE&lt;/code&gt; in &lt;code&gt;child&lt;/code&gt;'s namespace.&lt;/p&gt;
&lt;p&gt;To conclude, scope level 2--i.e. &lt;code&gt;YAMA_SCOPE_CAPABILITY&lt;/code&gt;--leads to executing the same permission check discussed in the paragraph above.&lt;/p&gt;
&lt;p&gt;All in all, we see that &lt;code&gt;yama_ptrace_access_check()&lt;/code&gt; performs LSM context-specific checks regarding internal kernel data structures to take decisions on whether access to certain resources that the LSM aims at protecting should be allowed or not, resulting in the hook returning &lt;code&gt;0&lt;/code&gt; when access should be granted, and &lt;code&gt;-EPERM&lt;/code&gt; otherwise. The other hooks adopt the same behavior, and apart from the configuration and compilation infrastructures, and the &lt;code&gt;sysctl&lt;/code&gt; interface that Yama exports, the core infrastructure of the LSM is entirely built around the hooks and their auxiliary functions, constituting the code that allows the LSM to make said access decisions on behalf of the kernel.&lt;/p&gt;
&lt;p&gt;That understanding seems enough for us to dare building our own proof of concept LSM now. From this point onward, that's precisely what we'll do.&lt;/p&gt;
&lt;h3&gt;Building a proof of concept LSM&lt;/h3&gt;
&lt;p&gt;We'll now work towards building a proof of concept LSM that simply implements a given hook and prints kernel log messages whenever said hook is called. The intent here is to exercise the integration of an LSM into the kernel's codebase.&lt;/p&gt;
&lt;p&gt;First off, we'll create a new diretory at &lt;code&gt;security/poc/&lt;/code&gt; and populate it with files &lt;code&gt;Kconfig&lt;/code&gt;, &lt;code&gt;Makefile&lt;/code&gt;, and &lt;code&gt;poc.c&lt;/code&gt;. Next, we'll fill in &lt;code&gt;Kconfig&lt;/code&gt; and &lt;code&gt;Makefile&lt;/code&gt; based on what we learned from looking at Yama's codebase. Here's what our &lt;code&gt;Kconfig&lt;/code&gt; looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt; SPDX-License-Identifier: GPL-2.0-only
config SECURITY_POC
    bool &amp;quot;PoC support&amp;quot;
    depends on SECURITY
    default n
    help
      This selects the PoC LSM.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And the module's &lt;code&gt;Makefile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# SPDX-License-Identifier: GPL-2.0-only&lt;/span&gt;
&lt;span class="nv"&gt;obj-$(CONFIG_SECURITY_POC)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poc.o

&lt;span class="nv"&gt;poc-y&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poc.o
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we'll program the module itself by writing &lt;code&gt;poc.c&lt;/code&gt;. We'll start by including basic LSM headers and code structures, namely the LSM's ID, init function, and macro definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// SPDX-License-Identifier: GPL-2.0-only&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/lsm_hooks.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;uapi/linux/lsm.h&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poc_lsmid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;poc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LSM_ID_POC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;poc_lsm_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pr_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;PoC LSM active&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;DEFINE_LSM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;poc_lsmid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poc_lsm_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course, as we've seen, we must also define &lt;code&gt;LSM_ID_POC&lt;/code&gt; in &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/uapi/linux/lsm.h"&gt;&lt;code&gt;include/uapi/linux/lsm.h&lt;/code&gt;&lt;/a&gt;. Let's go ahead and add a new LSM ID at the end of the ID list and assign our PoC LSM the next available ID in the sequence:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="cp"&gt;#define LSM_ID_EVM      112&lt;/span&gt;
&lt;span class="cp"&gt;#define LSM_ID_IPE      113&lt;/span&gt;
&lt;span class="cp"&gt;#define LSM_ID_POC      114&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;In turn, let's define a hook. We'll have our PoC LSM define a single hook, namely &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hook_defs.h#n215"&gt;&lt;code&gt;file_open()&lt;/code&gt;&lt;/a&gt;, to have the kernel call our LSM everytime a file is opened. Let's now create our hook list containing that single hook and a hook implementation for it. We'll also add a call to &lt;code&gt;security_add_hooks()&lt;/code&gt; to the LSM's init function in order for us to register our hook:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;// SPDX-License-Identifier: GPL-2.0-only&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/lsm_hooks.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;uapi/linux/lsm.h&amp;gt;&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poc_lsmid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;poc&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LSM_ID_POC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;poc_file_open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pr_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Entered PoC LSM file_info&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* Do something */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pr_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Exiting PoC LSM file_info&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poc_lsm_hooks&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__ro_after_init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poc_file_open&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;poc_lsm_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;security_add_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poc_lsm_hooks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ARRAY_SIZE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poc_lsm_hooks&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;poc_lsmid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pr_info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;PoC LSM active&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;DEFINE_LSM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;poc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;poc_lsmid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;poc_lsm_init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;NOTE: in the paragraphs that follow, we'll address some further changes that we'll need to make to the kernel's codebase before we're able to compile and boot into a kernel with our PoC LSM enabled. I had missed them in my initial exploration of Yama's codebase documented above, and I only understood my mistake after some additional rounds of trial and error.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As obvious as it may seem now, I had missed the need to change both the security subsystem's Kconfig file at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/Kconfig"&gt;&lt;code&gt;security/Kconfig&lt;/code&gt;&lt;/a&gt; to source our own PoC LSM's Kconfig file, and its Makefile at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/Makefile"&gt;&lt;code&gt;security/Makefile&lt;/code&gt;&lt;/a&gt; to add the object file from our newly created &lt;code&gt;security/poc/&lt;/code&gt; security subsystem subdirectory.&lt;/p&gt;
&lt;p&gt;That said, we must instruct the security subsystem's Kconfig file to source that of our own PoC module, and while we're at it, we'll also add our LSM to the lists of enabled LSMs in the default configurations section of &lt;code&gt;security/Kconfig&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;security/landlock/Kconfig&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;security/ipe/Kconfig&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;security/poc/Kconfig&amp;quot;&lt;/span&gt;

&lt;span class="c1"&gt;# ... &lt;/span&gt;

&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;LSM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Ordered list of enabled LSMs&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;depends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;SECURITY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;landlock,lockdown,yama,loadpin,safesetid,smack,selinux,tomoyo,apparmor,ipe,bpf,poc&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEFAULT_SECURITY_SMACK&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;landlock,lockdown,yama,loadpin,safesetid,apparmor,selinux,smack,tomoyo,ipe,bpf,poc&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEFAULT_SECURITY_APPARMOR&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;landlock,lockdown,yama,loadpin,safesetid,tomoyo,ipe,bpf,poc&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEFAULT_SECURITY_TOMOYO&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;landlock,lockdown,yama,loadpin,safesetid,ipe,bpf,poc&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;DEFAULT_SECURITY_DAC&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;landlock,lockdown,yama,loadpin,safesetid,selinux,smack,tomoyo,apparmor,ipe,bpf,poc&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;help&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we must implement the change to &lt;code&gt;security/Makefile&lt;/code&gt;, adding the object files from &lt;code&gt;security/poc/&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# ...&lt;/span&gt;

&lt;span class="nv"&gt;obj-$(CONFIG_SECURITY_LANDLOCK)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;landlock/
&lt;span class="nv"&gt;obj-$(CONFIG_SECURITY_IPE)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ipe/
&lt;span class="nv"&gt;obj-$(CONFIG_SECURITY_POC)&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;poc/

&lt;span class="c"&gt;# ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After doing the above, we can finally &lt;code&gt;cd&lt;/code&gt; into the repository's root and &lt;code&gt;source Kconfig&lt;/code&gt;. Now, if we &lt;code&gt;make menuconfig&lt;/code&gt; and look at the security subsystem's configuration submenu, here's what it looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Security subsystem's compilation submenu showing our LSM." src="../images/lsm-menuconfig-1.png"&gt;&lt;/p&gt;
&lt;p&gt;We can now activate our PoC module by toggling its option and adding it to the enabled LSM's list, whose value seems to have been obtained from our previous configuration:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Security subsystem's compilation submenu after configuring our LSM." src="../images/lsm-menuconfig-2.png"&gt;&lt;/p&gt;
&lt;p&gt;Finally, we can save the configuration and proceed to compiling and installing the kernel as usual. After booting into the new kernel, if we look at the list of enabled LSMs, we'll see our PoC LSM listed. Furthermore, the kernel log displays the messages our LSM has been printing every time a file is opened, proving that the kernel is indeed calling the &lt;code&gt;file_open()&lt;/code&gt; hook that our LSM registered:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Kernel log showing our PoC LSM's log messages." src="../images/lsm-dmesg-poc.png"&gt;&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This second part of my LSM study notes addressed the integration of a new LSM built from the ground up to the kernel's codebase. We looked at the threat model and the implementation of an already existing LSM, namely Yama, and by leveraging the knowledge that we acquired from inspecting its codebase, we've successfully built a proof of concept LSM and integrated it to the kernel's build system, allowing us to boot into a kernel that loads it and to inspect its functioning.&lt;/p&gt;
&lt;p&gt;By changing the security subsystem to add our own proof of concept LSM, we've learned that extending ther kernel's security subsystem is within arm's reach of anyone willing to venture into navigating its codebase and persisting in making things work out. Not only that, it made me a lot more confident about my basic understanding of an LSM built into the Linux kernel, and it helped me consolidate some of my learnings from part 1 of this series.&lt;/p&gt;
&lt;p&gt;I'm not sure where I'll go with respect to part 3 of this blog post series. I might further extend the proof of concept LSM defined herein to implement some actual policy enforcement by establishing an actual threat model for it to address. Additionally, one takeaway of exploring Yama's codebase was noticing the need to deeply understand the different kernel data structures that form the basis of the security checks that LSMs implement, and I might look into that next. Another possibility is further delving into the kernel's generic LSM infrastructure--there's a lot more to look at--while further case studying a major LSM such as AppArmor to see what actual LSM implementations look like. All in all, I'm not sure what my next step will be, but at least I've got some options mapped.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy hacking.&lt;/em&gt;&lt;/p&gt;</content><category term="Linux"/></entry><entry><title>Cross-compiling the Linux kernel for ARM64 on the RPi using a QEMU x86_64 Arch Linux compilation host</title><link href="https://vasconcedu.github.io/cross-compiling-the-linux-kernel-for-arm64-on-the-rpi-using-a-qemu-x86_64-arch-linux-compilation-host.html" rel="alternate"/><published>2026-06-19T15:00:00-03:00</published><updated>2026-06-19T15:00:00-03:00</updated><author><name>vasconcedu</name></author><id>tag:vasconcedu.github.io,2026-06-19:/cross-compiling-the-linux-kernel-for-arm64-on-the-rpi-using-a-qemu-x86_64-arch-linux-compilation-host.html</id><summary type="html">&lt;p&gt;I've been tinkering with the RPi to build a toy example in the context of my PhD research in the last couple of days, trying to get a custom kernel to work on a &lt;a href="https://pip-assets.raspberrypi.com/categories/584-raspberry-pi-zero-2-w/documents/RP-008359-DS-1-raspberry-pi-zero-2-w-product-brief.pdf"&gt;Zero 2 W board&lt;/a&gt;--for those unfamiliar with it, here's a picture:&lt;/p&gt;
&lt;p&gt;&lt;img alt="RPi Zero 2 W." src="../images/rpi-zero-2-w.png"&gt; &lt;/p&gt;
&lt;p&gt;As much as &lt;a href="https://www.raspberrypi.com/documentation/computers/linux_kernel.html"&gt;Raspberry …&lt;/a&gt;&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been tinkering with the RPi to build a toy example in the context of my PhD research in the last couple of days, trying to get a custom kernel to work on a &lt;a href="https://pip-assets.raspberrypi.com/categories/584-raspberry-pi-zero-2-w/documents/RP-008359-DS-1-raspberry-pi-zero-2-w-product-brief.pdf"&gt;Zero 2 W board&lt;/a&gt;--for those unfamiliar with it, here's a picture:&lt;/p&gt;
&lt;p&gt;&lt;img alt="RPi Zero 2 W." src="../images/rpi-zero-2-w.png"&gt; &lt;/p&gt;
&lt;p&gt;As much as &lt;a href="https://www.raspberrypi.com/documentation/computers/linux_kernel.html"&gt;Raspberry Pi's official documentation&lt;/a&gt; is very well organized and thorough about the theme of building a custom kernel for RPi boards, as I discussed in &lt;a href="https://vasconcedu.github.io/linux-kernel-development-setup-in-arch-linux.html"&gt;my blog post on setting up a kernel development environment&lt;/a&gt;, I've chosen Arch Linux as the base distro for my virtualized compilation host, and as the instructions in the official documentation target Debian-based distros--and seem to aim at physical compilation hosts--, I had to MacGyver my way to a functioning cross-compilation setup for the RPi using the virtualized Arch Linux-based setup that I already had. This blog post documents that procedure.&lt;/p&gt;
&lt;h3&gt;The cross-compilation scenario herein&lt;/h3&gt;
&lt;p&gt;Before we begin, it's important to understand that explanations involving cross-compilation scenarios might get rather convoluted to follow, so first of all, let's define our cross-compilation scenario clearly. The figure below shows the three systems involved in our scenario, and where we're positioned:&lt;/p&gt;
&lt;p&gt;&lt;img alt="RPi cross-compilation scenario." src="../images/rpi-cross-compilation-scenario.png"&gt;&lt;/p&gt;
&lt;p&gt;From the figure, it's easy to realize that three systems are involved in our cross-compilation scenario, namely: a physical host, which happens to be an x86_64 laptop running Arch Linux; a compilation host, which happens to be an x86_64 QEMU Arch Linux VM running on said physical host; and our target system, namely the physical ARM64 RPi Zero 2 W board. It so happens that the RPi board is connected to out lab network, so working from the physical host, we're able to SSH both into the compilation host VM and the RPi board.&lt;/p&gt;
&lt;p&gt;Now, of course the RPi boots from a micro SD card containing its boot and root filesystems. In order for us to write to that micro SD card during kernel installation, we'll connect the card to our physical host, and we'll leverage QEMU's virtual filesystem functionality to mount the RPi's filesystem on the micro SD card to a mount point within the compilation host's filesystem. That will allow us to install kernel modules to the RPi's root filesytem. &lt;/p&gt;
&lt;p&gt;Not only that, we'll leverage the fact that our physical host also has access to the micro SD card's mount point to &lt;code&gt;scp&lt;/code&gt; the kernel image and device tree blobs from the compilation host to the RPi's boot filesystem through the physical host.&lt;/p&gt;
&lt;p&gt;Those things we'll become clearer as we advance. For now, the important part is that we understand the different entities that constitute our cross-compilation scenario and their roles.&lt;/p&gt;
&lt;h3&gt;Installing the stock Raspberry Pi OS image&lt;/h3&gt;
&lt;p&gt;First off, we need a functioning RPi system as a starting point. I used the Raspberry Pi OS 64-bit stock image for the Zero 2 W board. In order to install the imager to the Arch Linux physical host, we need to issue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;pacman&lt;span class="w"&gt; &lt;/span&gt;-S&lt;span class="w"&gt; &lt;/span&gt;rpi-imager
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The process of flashing the stock OS is rather straightforward, but in my experience, it's best to configure the system during the flashing procedure. That will save us time later on when we'll setup the development environment, so it should payoff to setup Wi-Fi connectivity, SSH, and other configurations at this stage.&lt;/p&gt;
&lt;p&gt;At least that's what I did, but of course, depending on one's lab infrastructure, one might want to do things differently. In my case, setting up Wi-Fi and SSH a priori was handy because I was able to SSH into the board from my laptop right away after installing the OS, without the need to hook it to peripherals. Alternatively, if one chooses to customize later on, one will be able to change system configurations via the &lt;a href="https://raspberrytips.com/raspi-config-guide/"&gt;&lt;code&gt;raspi-config&lt;/code&gt; utilitary&lt;/a&gt; after logging into the RPi system some other way.&lt;/p&gt;
&lt;p&gt;After the flashing procedure is done, we should be able to boot the RPi and SSH into it--of course, we'll need to check in our router what IP address has been assigned to it. Speaking of that, it might also be interesting to assign the RPi a static IP. That said, the system will boot into the stock kernel, as expected:&lt;/p&gt;
&lt;p&gt;&lt;img alt="First SSH login into the RPi system." src="../images/rpi-ssh.png"&gt;&lt;/p&gt;
&lt;p&gt;After booting the RPi to confirm that everything is okay, we can shut it down. We won't use it for now.&lt;/p&gt;
&lt;h3&gt;Compiling the kernel for the RPi Zero 2 W&lt;/h3&gt;
&lt;p&gt;After flashing the stock system, it's time to start the compilation VM on QEMU and prepare to compile the kernel for the Zero 2 W. Again, &lt;a href="https://vasconcedu.github.io/linux-kernel-development-setup-in-arch-linux.html"&gt;my previous blog post about setting up a kernel development environment&lt;/a&gt; shows how to setup a virtualized Arch Linux compilation host using QEMU.&lt;/p&gt;
&lt;p&gt;As usual, we'll clone RPi's kernel by issuing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;clone&lt;span class="w"&gt; &lt;/span&gt;--depth&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;https://github.com/raspberrypi/linux&lt;span class="w"&gt; &lt;/span&gt;linux-rpi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point, an important sidenote is that &lt;a href="https://www.raspberrypi.com/documentation/computers/linux_kernel.html#kernel"&gt;RPi's kernel lags behind the Linux kernel&lt;/a&gt;. RPi's kernel development team integrates Linux kernel changes into the RPi kernel only after LTS releases of the Linux kernel are made.&lt;/p&gt;
&lt;p&gt;Having cloned the repository, it's time to install our cross-compilation toolchain. Luckily for us, since we're using our preexisting kernel compilation host as a base for our ARM64 cross-compilation host, we'll only need to install package &lt;a href="https://archlinux.org/packages/extra/x86_64/aarch64-linux-gnu-gcc/"&gt;&lt;code&gt;aarch64-linux-gnu-gcc&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;pacman&lt;span class="w"&gt; &lt;/span&gt;-S&lt;span class="w"&gt; &lt;/span&gt;aarch64-linux-gnu-gcc
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And that's it. We're ready to compile our own kernel for the Zero 2 W. What follows is a pretty standard kernel compilation procedure where we'll &lt;code&gt;cd&lt;/code&gt; into the repository's directory, clean it to make sure we're not working with any reminiscent garbage, build our cross-compilation kernel config, and then proceed to compilation. An important caveat is that RPi's documentation &lt;a href="https://www.raspberrypi.com/documentation/computers/linux_kernel.html#cross-compiled-build-configuration"&gt;specifically instructs the creation of env var &lt;code&gt;KERNEL=kernel8&lt;/code&gt;&lt;/a&gt;, so we'll make sure to do that. Translating to commands, up until config file creation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;linux-rpi
make&lt;span class="w"&gt; &lt;/span&gt;mrproper
&lt;span class="nv"&gt;KERNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kernel8
make&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aarch64-linux-gnu-&lt;span class="w"&gt; &lt;/span&gt;bcm2711_defconfig
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Something important to notice here is that despite the fact that &lt;a href="https://pip-assets.raspberrypi.com/categories/584-raspberry-pi-zero-2-w/documents/RP-008359-DS-1-raspberry-pi-zero-2-w-product-brief.pdf"&gt;the RPi Zero 2 W board ships with Broadcom's BCM2710A1 SoC&lt;/a&gt;, the base kernel config we're using is that of &lt;a href="https://www.raspberrypi.com/documentation/computers/processors.html#bcm2711"&gt;Broadcom's BCM2711 SoC, namely the chip that ships with the RPi 4 family of boards&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We can finally proceed to compiling. This subject is out of the scope of this blog post, but notice I'm using &lt;a href="https://ccache.dev/"&gt;&lt;code&gt;ccache&lt;/code&gt;&lt;/a&gt; to optimize future compilation performance here, and one might also want to adjust the number of cores used in compilation according to the configuration of one's own compilation host VM. I used &lt;code&gt;-j 7&lt;/code&gt; here due to my own previous kernel compilation experience in the last few months. Using 7 cores--which translates to &lt;code&gt;nproc&lt;/code&gt; + 1 in how my QEMU VM is configured--seems to hit the sweet spot between compilation host performance and physical host usability in my own lab setup, but &lt;a href="https://stackoverflow.com/questions/23279178/how-to-speed-up-linux-kernel-compilation"&gt;there's a lot to be said about kernel compilation performance optimization&lt;/a&gt;, and I guess it's really on each of us to do the hard work and find our preferred configuration after some rounds of frustration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;ccache&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aarch64-linux-gnu-&lt;span class="w"&gt; &lt;/span&gt;Image&lt;span class="w"&gt; &lt;/span&gt;modules&lt;span class="w"&gt; &lt;/span&gt;dtbs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Compilation should complete without any issues, and after it's done, we can proceed to installing the newly compiled kernel in our stock system's filesystem.&lt;/p&gt;
&lt;h3&gt;Installing the newly compiled kernel in the stock system's filesystem&lt;/h3&gt;
&lt;p&gt;After compilation finishes, we'll go ahead and shutdown the compilation host, connect the RPi's micro SD card to the physical host, and mount both the RPi's root and boot filesystems as the root user--more on that shortly--in the physical host. Here, I used mount points &lt;code&gt;/mnt/rootfs&lt;/code&gt; and &lt;code&gt;/mnt/bootfs&lt;/code&gt;, respectively. Next, we'll restart the compilation host specifying our mount points as virtual filesystems that the compilation host will be able to access, by adding &lt;code&gt;-virtfs&lt;/code&gt; flags to the startup command as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qemu-system-x86_64&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-virtfs&lt;span class="w"&gt; &lt;/span&gt;local,path&lt;span class="o"&gt;=&lt;/span&gt;/mnt/rootfs/,mount_tag&lt;span class="o"&gt;=&lt;/span&gt;rootshare,security_model&lt;span class="o"&gt;=&lt;/span&gt;mapped,id&lt;span class="o"&gt;=&lt;/span&gt;rootfs_share&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-virtfs&lt;span class="w"&gt; &lt;/span&gt;local,path&lt;span class="o"&gt;=&lt;/span&gt;/mnt/bootfs/,mount_tag&lt;span class="o"&gt;=&lt;/span&gt;bootshare,security_model&lt;span class="o"&gt;=&lt;/span&gt;mapped,id&lt;span class="o"&gt;=&lt;/span&gt;bootfs_share
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;One possible matter of confusion here is which filesystem is root and which is boot after connecting the RPi's micro SD card to the physical host and looking at the output of &lt;code&gt;lsblk&lt;/code&gt;. But of course, the boot filesystem is the smaller one, and the root filesystem is the bigger one. For instance, in the figure below, &lt;code&gt;/boot&lt;/code&gt; is in &lt;code&gt;sdb1&lt;/code&gt;, and &lt;code&gt;/&lt;/code&gt; is in &lt;code&gt;sdb2&lt;/code&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="RPi filesystem's lsblk output." src="../images/rpi-lsblk.png"&gt;&lt;/p&gt;
&lt;p&gt;Another important observation is that since non-root UIDs between the systems in the cross-compilation scenario will almost certainly diverge, specifying &lt;code&gt;security_model=mapped&lt;/code&gt; prevents us from falling into filesystem permission issues while writing to the root filesystem, which truly is owned by the RPi's root. That is why mounting the filesystems as root in the physical host matters.&lt;/p&gt;
&lt;p&gt;In fact, our next step is to mount the filesystems--also as root--in the compilation host. Here, I also used mount points &lt;code&gt;/mnt/rootfs&lt;/code&gt; and &lt;code&gt;/mnt/bootfs&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;/mnt/rootfs
mkdir&lt;span class="w"&gt; &lt;/span&gt;/mnt/bootfs
mount&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;9p&lt;span class="w"&gt; &lt;/span&gt;rootshare&lt;span class="w"&gt; &lt;/span&gt;/mnt/rootfs
mount&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;9p&lt;span class="w"&gt; &lt;/span&gt;bootshare&lt;span class="w"&gt; &lt;/span&gt;/mnt/bootfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, we can go ahead and install the compiled kernel modules to RPi's root filesystem. First, we'll &lt;code&gt;cd&lt;/code&gt; into the repository: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/path/to/linux-rpi
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, as root, we'll recreate that same env var that we had created before when we were compiling the kernel:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;KERNEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;kernel8
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Finally--still as root--, we can go ahead and install the modules to the RPi's root filesystem:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;make&lt;span class="w"&gt; &lt;/span&gt;-j&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arm64&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;CROSS_COMPILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aarch64-linux-gnu-&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;INSTALL_MOD_PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/mnt/rootfs&lt;span class="w"&gt; &lt;/span&gt;modules_install
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Next, we'll proceed to the physical host and &lt;code&gt;scp&lt;/code&gt; device tree blobs from the compilation host to the RPi's boot filesystem. "And why is that? Why don't we &lt;code&gt;cp&lt;/code&gt; device tree blobs from the repository directly into the boot filesystem? Didn't we just mount the RPi's entire filesystem in the compilation host, including &lt;code&gt;/boot&lt;/code&gt;?"--one might ask.&lt;/p&gt;
&lt;p&gt;And the answer to that has to do with the different filesystems used in the RPi's &lt;code&gt;/boot&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; partitions. The boot filesystem is FAT32, while the root filesystem is ext4. Because we're mapping UIDs between the physical host and the compilation host with QEMU virtual filesystem's &lt;code&gt;security_model=mapped&lt;/code&gt; flag, and because UIDs are simply not present in FAT32, my hypothesis is that there's no way for our VM to infer that we're allowed to write to &lt;code&gt;/mnt/bootfs&lt;/code&gt;, leading to "permission denied" errors whenever we try to write anything to &lt;code&gt;/mnt/bootfs&lt;/code&gt;. While I suppose there might be a better solution to this in QEMU's &lt;code&gt;-virtfs&lt;/code&gt; itself, I wasn't able to find one so far, hence why I resorted to the &lt;code&gt;scp&lt;/code&gt; workaround narrated here.&lt;/p&gt;
&lt;p&gt;That said, let's get back to our physical host and, as root, run the following commands. One should adapt them to suit one's own setup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/mnt/bootfs/
cp&lt;span class="w"&gt; &lt;/span&gt;kernel8.img&lt;span class="w"&gt; &lt;/span&gt;kernel8.img.bak
scp&lt;span class="w"&gt; &lt;/span&gt;-P&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;user@localhost:/path/to/linux-rpi/arch/arm64/boot/Image&lt;span class="w"&gt; &lt;/span&gt;./kernel8.img
scp&lt;span class="w"&gt; &lt;/span&gt;-P&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;user@localhost:/path/to/linux-rpi/arch/arm64/boot/dts/broadcom/*.dtb&lt;span class="w"&gt; &lt;/span&gt;./
scp&lt;span class="w"&gt; &lt;/span&gt;-P&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;user@localhost:/path/to/linux-rpi/arch/arm64/boot/dts/overlays/*.dtb*&lt;span class="w"&gt; &lt;/span&gt;./overlays/
scp&lt;span class="w"&gt; &lt;/span&gt;-P&lt;span class="w"&gt; &lt;/span&gt;port&lt;span class="w"&gt; &lt;/span&gt;user@localhost:/path/to/linux-rpi/arch/arm64/boot/dts/overlays/README&lt;span class="w"&gt; &lt;/span&gt;./overlays/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, back to our compilation host, we &lt;code&gt;umount&lt;/code&gt; RPi's filesystem by issuing (as root):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;umount&lt;span class="w"&gt; &lt;/span&gt;/mnt/rootfs
umount&lt;span class="w"&gt; &lt;/span&gt;/mnt/bootfs
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point, we can shutdown our compilation host. That will allow us to also &lt;code&gt;umount&lt;/code&gt; RPi's filesystem from the physical host.&lt;/p&gt;
&lt;h3&gt;Booting into the newly compiled kernel&lt;/h3&gt;
&lt;p&gt;Now, we can go ahead and disconnect the RPi's micro SD card from the physical host, connect it back to the board's micro SD card slot, and hopefully boot into our newly compiled kernel.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ta-da!&lt;/em&gt; The compilation timestamp &lt;code&gt;Fri Jun 19 13:27:01 -03 2026&lt;/code&gt; shows that we booted into our newly compiled kernel:&lt;/p&gt;
&lt;p&gt;&lt;img alt="SSH login into the RPi system with the new kernel." src="../images/rpi-ssh-2.png"&gt;&lt;/p&gt;
&lt;p&gt;After enabling VNC via &lt;code&gt;raspi-config&lt;/code&gt; and performing some basic regression testing, we can verify that we haven't destroyed anything: the system seems fully functional:&lt;/p&gt;
&lt;p&gt;&lt;img alt="VNC session into the RPi with the newly compiled kernel." src="../images/rpi-vnc.png"&gt;&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This blog post documented my approach to finding a viable workflow for cross-compiling the Linux kernel for the RPi Zero 2 W using my preexisting virtualized Arch Linux kernel compilation host running on QEMU. While the official RPi docs target Debian-based physical hosts, the steps herein show how to use a virtualized setup to achieve functional RPi kernel cross-compilation.&lt;/p&gt;
&lt;p&gt;Nevertheless, if I had to do this at scale--which I might have to do for my research--, I reckon using a physical host will make automation a lot easier to achieve, not to mention less time intensive since I'll be cross-compiling the kernel directly on bare metal. A truly scalable approach would also likely include updating the board's kernel over the air, and I might have to think about that in the future.&lt;/p&gt;
&lt;p&gt;Anyway, I'm happy with what this little toy example has taught me, and having cross-compiled for ARM64 for the first time was certainly an important step in my adventures in kernel development. That's all.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy hacking.&lt;/em&gt;&lt;/p&gt;</content><category term="Linux"/></entry><entry><title>On Linux kernel synchronization</title><link href="https://vasconcedu.github.io/on-linux-kernel-synchronization.html" rel="alternate"/><published>2026-06-16T04:00:00-03:00</published><updated>2026-06-16T04:00:00-03:00</updated><author><name>vasconcedu</name></author><id>tag:vasconcedu.github.io,2026-06-16:/on-linux-kernel-synchronization.html</id><summary type="html">&lt;p&gt;From my practical work in kernel development so far, I realized that synchronization concerns are present in pretty much every single corner of kernel code. Hence, kernel synchronization is a topic that deserves special attention from kernel newbies like myself who want to build a working understanding of the Linux …&lt;/p&gt;</summary><content type="html">&lt;p&gt;From my practical work in kernel development so far, I realized that synchronization concerns are present in pretty much every single corner of kernel code. Hence, kernel synchronization is a topic that deserves special attention from kernel newbies like myself who want to build a working understanding of the Linux kernel and its inner workings. That realization led me to delve into kernel synchronization fundamentals in the last couple of weeks.&lt;/p&gt;
&lt;p&gt;In this blog post, I summarize what I've learned about this subject so far, partly from Kaiwan N. Billimoria's &lt;em&gt;Linux Kernel Programming&lt;/em&gt; book, partly from revisiting Andrew S. Tanenbaum's &lt;em&gt;Operating Systems--Design and Implementation&lt;/em&gt; textbook (2 ed.), partly from reading kernel docs and other materials about the subject, and in large measure--and most importantly--from navigating the Linux kernel's codebase and programming C proofs of concept to actually see textbook materials and documentation claims come into life. First, we'll revisit some basic concepts of operating systems, then we'll gradually build on top of them to achieve an initial comprehension of Linux kernel synchronization mechanisms.&lt;/p&gt;
&lt;h3&gt;The trouble with multithreaded environments&lt;/h3&gt;
&lt;p&gt;Multithreading is greatly advantageous in terms of throughput, but it comes with the caveat that if threads are not correctly synchronized while working upon shared writeable data, this might result in a form of unpredictable outcome known as a data race (a.k.a. race condition).&lt;/p&gt;
&lt;p&gt;I've crafted the figure below to help illustrate the issue:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing a data race." src="../images/data-race.png"&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, two threads, namely thread 1 and thread 2, both read and write shared memory. Let's imagine that the shared memory contains a data structure that receives updates from both threads.&lt;/p&gt;
&lt;p&gt;It is easy to imagine a troublesome scenario as follows: thread 1 reads the data structure from shared memory. Before it's done updating it, thread 2 does the same and reads the data structure from shared memory. Next, thread 1 finishes updating the data structure and writes it back to shared memory. Just after that happens, thread 2 does the same, which results in it blindly overwriting the update that thread 1 had just done, thus corrupting the data.&lt;/p&gt;
&lt;p&gt;While I was studying Billimoria's text, I extrapolated the book's proposed exercises to program &lt;a href="https://github.com/vasconcedu/billimoria-linux-kernel-programming/src/branch/master/ch12/data_race"&gt;a proof of concept&lt;/a&gt; in user space, demonstrating a race similar to the one described above.&lt;/p&gt;
&lt;h3&gt;A formal definition of data race&lt;/h3&gt;
&lt;p&gt;As much as the anecdotal example above might serve the purpose of building some intuition, data races are such an important issue to be aware of that the Linux kernel documentation contains a very formal definition of when data races occur and what to look for in order to find them. &lt;/p&gt;
&lt;p&gt;Billimoria points to that part of the documentation in his text, and I quote--from &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/memory-model/Documentation/explanation.txt#n2231"&gt;&lt;code&gt;tools/memory-model/Documentation/explanation.txt&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"A "data race" occurs when there are two memory accesses such that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;they access the same location,&lt;/li&gt;
&lt;li&gt;at least one of them is a store,&lt;/li&gt;
&lt;li&gt;at least one of them is plain,&lt;/li&gt;
&lt;li&gt;they occur on different CPUs (or in different threads on the same CPU), and&lt;/li&gt;
&lt;li&gt;they execute concurrently.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In the literature, two accesses are said to "conflict" if they satisfy 1 and 2 above.  We'll go a little farther and say that two accesses are "race candidates" if they satisfy 1 - 4.  Thus, whether or not two race candidates actually do race in a given execution depends on whether they are concurrent."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's important to realize that what the documentation refers to as a "plain" access is an ordinary access to memory performed without resorting to kernel macros aimed at guaranteeing atomicity.&lt;/p&gt;
&lt;p&gt;The definition above is very unambiguous, and it implies that in order to address data races adequately, it is important to describe the concepts around them rather systematically. In the following sections, I attempt to do that by delving further into the concepts behind data race prevention mechanisms built into the Linux kernel. The first topic we'll look into is that of critical sections.&lt;/p&gt;
&lt;h3&gt;Critical sections&lt;/h3&gt;
&lt;p&gt;Billimoria defines a critical section as a section of code that fulfills these two conditions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, there is the possibility that the code will run in parallel; and&lt;/li&gt;
&lt;li&gt;Second, the code changes writeable data.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From that definition, the author goes on to conclude that a critical section must run exclusively, sometimes even atomically. Now, running exclusively means that critical sections must run in a serialized fashion, so that no two threads ever run the same critical section at the same time. Running atomically means that critical sections must be allowed to finish uninterrupted before preemption of the running thread occurs. As we'll see, this is sometimes required, but not always.&lt;/p&gt;
&lt;p&gt;In turn, Tanenbaum defines critical sections as the portions of a program that access shared memory. According to the author, no two processes must ever enter critical sections at the same time to prevent race conditions.&lt;/p&gt;
&lt;p&gt;Both authors converge in saying that critical sections occur when concurrent code accesses shared data, and that in order to prevent them from causing trouble, we must come up with a way of guaranteeing exclusive execution. That leads us to discussing the topic of locks.&lt;/p&gt;
&lt;h3&gt;Locks&lt;/h3&gt;
&lt;p&gt;A lock is an entity that can be "owned" by running threads, with a guarantee that at any given point in time, only one single thread can own it.&lt;/p&gt;
&lt;p&gt;Thus, a lock can be leveraged to protect a critical section by binding access to the critical section to lock ownership. Since a lock can be owned by only one single thread at any given point in time, critical section serialization is guaranteed, because threads must wait for their turn to own the lock before they are allowed to enter the critical section.&lt;/p&gt;
&lt;p&gt;After the thread that owns the lock finishes executing the critical section, it releases the lock--i.e. it "unlocks" the critical section. Only then, the other threads--that have been waiting--, get a chance to acquire ownership of the lock. The next thread that succeeds at acquiring ownership of the lock is then allowed to enter the critical section, while all the others remain waiting. This process repeats until all threads have a chance to acquire ownership of the lock and enter the critical section.&lt;/p&gt;
&lt;p&gt;To illustrate, let's get back to the previous anecdotal example and see how a conceptual lock could be used to address the data race that emerged from those two threads accessing shared memory concurrently.&lt;/p&gt;
&lt;p&gt;Here's an updated version of the previous figure to account for lock usage:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing a lock." src="../images/lock.png"&gt;&lt;/p&gt;
&lt;p&gt;In the figure above, thread 1 acquires ownership of the lock, then enters the critical section and proceeds with updating the data structure in shared memory. While thread 1 is at it, thread 2 tries to acquire ownership of the lock to enter the critical section, which is blocked, because thread 1 already owns the lock. Thread 2 must wait until thread 1 releases the lock before it gets a chance to acquire it and enter the critical section. After thread 1 finishes the critical section, it releases the lock--"unlock"--, and thread 2 succeeds at acquiring it. Thread 2 is now allowed to enter the critical section and update the data structure in shared memory. After it's done, it also releases the lock, "unlocking" the critical section.&lt;/p&gt;
&lt;p&gt;We'll now begin to look at some actual realizations of locks built into the Linux kernel.&lt;/p&gt;
&lt;p&gt;Historically, many approaches have been proposed to locking critical sections and solving the problem of data races. Tanenbaum's textbook is very thorough about them. While I strongly recommend anyone who's serious about learning systems programming to read through that portion of his text, in this blog post, I'll be a bit more pragmatic and focus specifically on the excerpts that build the theoretical foundations to the core synchronization mechanisms found in the kernel's modern API, while leveraging the practicality of Billimoria's text to further develop such ideas and contextualize them in terms of contemporary kernel development.&lt;/p&gt;
&lt;p&gt;That said, bear with me for a brief exploration of a fundamental synchronization primitive--the semaphore. We'll build upon semaphores to discuss the first type of lock widely used in modern kernel development later on--the mutex.&lt;/p&gt;
&lt;h3&gt;The semaphore&lt;/h3&gt;
&lt;p&gt;Semaphores constitute a synchronization primitive &lt;a href="https://en.wikipedia.org/wiki/Semaphore_(programming)"&gt;invented in the 60s by Edsger Dijktra&lt;/a&gt;. In order to understand how semaphores work, let's look at a synchronization problem they help solve: &lt;a href="https://en.wikipedia.org/wiki/Producer%E2%80%93consumer_problem"&gt;the producer-consumer problem&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Imagine that two processes share a buffer. Process P, the producer, puts data into said buffer, while process C, the consumer, retrieves data from it. Process C can only ever retrieve data from the buffer if it is not empty, while process P can only ever put data into the buffer if it is not full. The figure below helps in visualizing this arrangement:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing the producer-consumer problem." src="../images/producer-consumer-problem.png"&gt;&lt;/p&gt;
&lt;p&gt;When process P tries to put data into the buffer to find that it is full, it goes to sleep. When a slot becomes available, process P is awaken. The same happens to process C when it tries to retrieve data from the buffer to find that it is empty. It goes to sleep, to be awaken when data becomes available. Each process checks whether the other is asleep and sends wake up signals accordingly.&lt;/p&gt;
&lt;p&gt;Such setting is inherently racy. To illustrate, suppose that process P tries to put data into the buffer. It checks to see if the buffer is full, to find that it is, as illustrated in the figure below:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing a race in the producer-consumer problem." src="../images/producer-consumer-problem-race-1.png"&gt;&lt;/p&gt;
&lt;p&gt;That results in process P going to sleep, but the scheduler says "Not so fast!": before process P has a chance to go to sleep, it is preempted, while process C is scheduled. Process C checks to see if there's data available for retrieval, to find that there is. So, it retrieves data from the buffer, and it so happens that the scheduler gives it enough processor time to empty the entire buffer:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing a race in the producer-consumer problem." src="../images/producer-consumer-problem-race-2.png"&gt;&lt;/p&gt;
&lt;p&gt;Next, process C checks to see if process P should be sleeping, to find that it shouldn't--because there's now plenty of empty slots in the buffer--, resulting in it signaling process P to wake up. Then, it goes to sleep, because the buffer is empty. The trouble is since process P is not actually asleep, process C's wake up signal is lost:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing a race in the producer-consumer problem." src="../images/producer-consumer-problem-race-3.png"&gt;&lt;/p&gt;
&lt;p&gt;Next, process P is scheduled. It resumes execution by going to sleep. At this point, both processes are asleep, and they will remain in that state indefinitely:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing a race in the producer-consumer problem." src="../images/producer-consumer-problem-race-4.png"&gt;&lt;/p&gt;
&lt;p&gt;Semaphores introduce the idea of saving wake up signals for future use. This is done using an integer whose value is initialized to &lt;code&gt;0&lt;/code&gt;, but that can be incremented or decremented using the atomic operations &lt;code&gt;up()&lt;/code&gt; and &lt;code&gt;down()&lt;/code&gt;, respectively. &lt;code&gt;down()&lt;/code&gt; checks if the value of the semaphore is greater than &lt;code&gt;0&lt;/code&gt;. If so, it decrements its value. If not, the process goes to sleep. In turn, &lt;code&gt;up()&lt;/code&gt; increments the value of the semaphore. In case any processes were asleep on said semaphore, the system wakes one of them up and allows it to complete its &lt;code&gt;down()&lt;/code&gt; operation, immediately decrementing the value of the semaphore, but resulting in having one less process asleep on it.&lt;/p&gt;
&lt;p&gt;Concrete semaphore implementations must guarantee that the operations described above are atomic, and that no two processes ever access the semaphore at the same time. If such premises hold, it is possible to use 3 semaphores to solve the producer-consumer problem. Tanenbaum names them as follows:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Semaphore "full" keeps track of the number of slots in the buffer that are occupied;&lt;/li&gt;
&lt;li&gt;Semaphore "empty" keeps track of the number of slots in the buffer that are free; and&lt;/li&gt;
&lt;li&gt;Semaphore "mutual exclusion"--a.k.a. "mutex"--guarantees that processes P and C do not ever access the buffer at the same time. Semaphores of this kind are also referred to as "binary semaphores".&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Initially, full's value is &lt;code&gt;0&lt;/code&gt;, and empty's value is equal to the total number of slots in the buffer. Mutex's value is 1, representing that no process is accessing the buffer. Whenever a process is about to access the buffer--hence entering the critical section--, it must first &lt;code&gt;down()&lt;/code&gt; mutex.&lt;/p&gt;
&lt;p&gt;Let's examine how full works. Whenever the consumer is about to retrieve data from the buffer, it calls &lt;code&gt;down()&lt;/code&gt; on full. If the value of full is &lt;code&gt;0&lt;/code&gt;--meaning the buffer is empty--, this results in the consumer going to sleep on full, preventing it from proceeding while the buffer is empty. Whenever the producer calls &lt;code&gt;up()&lt;/code&gt; on full--which happens when the producer exits the critical section, after putting data into the buffer--, the system wakes the consumer up and allows it to complete its &lt;code&gt;down()&lt;/code&gt; operation. On the other hand, if the value of full is greater than &lt;code&gt;0&lt;/code&gt;, this results in the consumer being allowed to enter the critical section, accessing the buffer--which in turn is protected by mutex, as described below.&lt;/p&gt;
&lt;p&gt;Conversely, empty works as follows: whenever the producer is about to put data into the buffer, it calls &lt;code&gt;down()&lt;/code&gt; on empty. If the value of empty is &lt;code&gt;0&lt;/code&gt;, meaning there are no empty slots--i.e. the buffer is full--, this results in the producer going to sleep on empty, preventing it from proceeding while the buffer is full. Whenever the consumer calls &lt;code&gt;up()&lt;/code&gt; on empty--which happens when the consumer exits the critical section, after retrieving data from the buffer--, the system wakes the producer up and allows it to complete its &lt;code&gt;down()&lt;/code&gt; operation. On the other hand, if the value of empty is greater than &lt;code&gt;0&lt;/code&gt;, this results in the producer being allowed to enter the critical section, accessing the buffer--which in turn is protected by mutex, as described below.&lt;/p&gt;
&lt;p&gt;Now, let's examine what happens when either process is about to enter the critical section and the other process is not inside of it. It's quite simple: the process calls &lt;code&gt;down()&lt;/code&gt; on mutex, whose value becomes &lt;code&gt;0&lt;/code&gt;, effectively locking the critical section and allowing the process to access the buffer safely. Before it exits the critical section, it must call &lt;code&gt;up()&lt;/code&gt; on mutex, restoring its value to &lt;code&gt;1&lt;/code&gt;, thus unlocking the critical section. Now, what happens when a process wants to enter the critical section, but the other process is inside of it? It calls &lt;code&gt;down()&lt;/code&gt; on mutex. &lt;code&gt;down()&lt;/code&gt; verifies that the value of mutex is already &lt;code&gt;0&lt;/code&gt;, which makes the process sleep on mutex. This results in mutex preventing concurrent accesses to the buffer, thus serializing critical section access.&lt;/p&gt;
&lt;p&gt;The paragraphs above might be rather convoluted, and perhaps the best way to understand the solution they describe is to revisit them a couple of times while looking at the implementation's pseudocode with pen and paper at hand. The pseudocode below, adapted from Tanenbaum's text, presents the solution described above. If you're interested, I've also written &lt;a href="https://github.com/vasconcedu/billimoria-linux-kernel-programming/src/branch/master/ch12/producer_consumer/producer_consumer.c"&gt;a user space C program implementing Tanenbaum's solution for the producer-consumer problem&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mutex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// N is the number of slots in the buffer&lt;/span&gt;
&lt;span class="n"&gt;semaphore&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;producer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mutex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;put_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* Data */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mutex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;consumer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;while&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;full&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;down&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mutex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;retrieve_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="cm"&gt;/* Data */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mutex&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now that we've established an intuition about semaphores, we can finally look at some of the kernel's concrete lock implementations. The first type of lock that we'll delve into is the mutex, and it surely helps that we've just visited Tanenbaum's theoretical development on its inner workings by looking at the producer-consumer problem.&lt;/p&gt;
&lt;h3&gt;The mutex&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://docs.kernel.org/locking/mutex-design.html"&gt;In his text &lt;em&gt;Generic Mutex Subsystem&lt;/em&gt; in the kernel docs&lt;/a&gt;, kernel maintainer Ingo Molnar defines mutexes as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"In the Linux kernel, mutexes refer to a particular locking primitive that enforces serialization on shared memory systems, and not only to the generic term referring to "mutual exclusion" found in academia or similar theoretical text books. Mutexes are sleeping locks which behave similarly to binary semaphores, and were introduced in 2006 as an alternative to these."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you're interested, I've programmed &lt;a href="https://github.com/vasconcedu/billimoria-linux-kernel-programming/src/branch/master/ch12/mutex/mutex.c"&gt;a proof of concept kernel module to demonstrate mutex utilization, including a forced race condition&lt;/a&gt;, but it's easy to summarize basic mutex usage in pseudocode as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/mutex.h&amp;gt;&lt;/span&gt;&lt;span class="c1"&gt; // Defines the mutex kernel API&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="n"&gt;DEFINE_MUTEX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* Mutex name */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// DEFINE_MUTEX() defines and initializes an instance of mutex&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="n"&gt;mutex_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="cm"&gt;/* Mutex name */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Locks the critical section&lt;/span&gt;

&lt;span class="cm"&gt;/* Critical section */&lt;/span&gt;

&lt;span class="n"&gt;mutex_unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="cm"&gt;/* Mutex name */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Unlocks the critical section&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;As much as things might look rather straightforward, here's what kernel development has really taught me so far: if you understand something, you haven't looked hard enough yet--look again. The truth is all sorts of caveats apply here. Here's a few that I've been able to map so far.&lt;/p&gt;
&lt;p&gt;First, let us remember that after a thread succeeds at acquiring ownership of a mutex, further attempts to acquire it will block, which theoretically--as we've seen from studying Tanenbaum's text--should result in the blocked thread being put to sleep. The Linux kernel's implementation of mutex differs a bit from textbook material, though. Blocking &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/kernel/locking/mutex.c#n293"&gt;results in the &lt;em&gt;possibility&lt;/em&gt;--called the "slow path"--that blocked threads are put to sleep&lt;/a&gt; until the thread that owns the mutex exits the critical section and unlocks it, when the mutex becomes available again. This is the reason why Ingo Molnar refers to mutexes as "sleeping locks" in his text. But mind the word &lt;em&gt;possibility&lt;/em&gt; here.&lt;/p&gt;
&lt;p&gt;One alternative to the slow path, &lt;a href="https://docs.kernel.org/locking/mutex-design.html#implementation"&gt;that happens when there are no threads whose priority is higher than that of the candidate thread ready to acquire the lock and enter the critical section&lt;/a&gt;, is the "mid path". This path to lock acquisition is based on the heuristic that the owner thread should release the lock soon, which justifies avoiding the slow path that would result in putting the candidate thread to sleep. When the mid path happens, the candidate thread enters a state in which it is said to "spin for lock acquisition"--what this entails will become clearer soon, when we delve into the topic of spinlocks. The performance gain that the mid path introduces in mutex implementation is very clear. As Ingo Molnar puts it in the aforementioned kernel docs text:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"By simply not interrupting a task and busy-waiting for a few cycles instead of immediately sleeping, the performance of this lock has been seen to significantly improve a number of workloads."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With all that said, it's easy to understand why Billimoria's text is emphatic about the fact that mutexes should only ever be used in scenarios where sleeping while waiting for lock release is possible. A consequence of that behavior is that mutexes introduce the potential need to fully switch the context of blocked threads off the processor and switch them back onto the processor after the mutex unlocks, which is inherently expensive.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.kernel.org/locking/mutex-design.html#disadvantages"&gt;Mutexes also fall short in efficiency with regards to their size&lt;/a&gt;, as they are among some of the largest locks in the kernel API, leading to more significant caching and memory footprints if compared to other locking mechanisms.&lt;/p&gt;
&lt;p&gt;Despite their drawbacks, mutexes are still the default locking primitive for good reason. &lt;a href="https://docs.kernel.org/locking/mutex-design.html#when-to-use-mutexes"&gt;Ingo Molnar's guidance is clear&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Unless the strict semantics of mutexes are unsuitable and/or the critical region prevents the lock from being shared, always prefer them to any other locking primitive."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Which means kernel newbies like myself can rest assured that mutexes are likely the locking mechanism of choice, unless the specific situation simply has special requirements that mutexes can't handle--e.g. because serialization is not necessary and would actually hurt performance, or because the critical section wouldn't allow multiple accesses anyway.&lt;/p&gt;
&lt;p&gt;Having explored the mutex lock, let's now look at the next concrete implementation of lock in the Linux kernel API: the spinlock.&lt;/p&gt;
&lt;h3&gt;The spinlock&lt;/h3&gt;
&lt;p&gt;The spinlock is based on the concept of "spinning", which means having a thread "spin" for lock acquisition until it succeeds. In other words, using the spinlock means we'll have a thread perform busy waiting for the lock, &lt;a href="https://documentation.ubuntu.com/real-time/latest/explanation/locks/#spinlocks"&gt;essentially polling for it&lt;/a&gt;. Hence, differently from the mutex, &lt;a href="https://kernel-internals.org/locking/spinlock/"&gt;the thread never sleeps, which makes the spinlock usable in contexts where sleeping is not an option&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Before we continue, in order to illustrate this concept, let's take a look at another theoretical excerpt of Tanenbaum's text where he defines &lt;a href="https://en.wikipedia.org/wiki/Test-and-set"&gt;an instruction called "Test and Set Lock", or TSL&lt;/a&gt;. TSL is an atomic instruction that reads the value of a shared variable, then sets its value to &lt;code&gt;1&lt;/code&gt;, while storing its previous value in a register. Since TSL is atomic, it can be leveraged to implement spinning for lock acquisition. Suppose that we have a variable--e.g. &lt;code&gt;lock&lt;/code&gt;--which is used to protect the critical section of a program. When &lt;code&gt;lock == 0&lt;/code&gt;, any thread can spin for lock acquisition and evetually succeed at acquiring it. When &lt;code&gt;lock == 1&lt;/code&gt;, it means there's a thread actively accessing the critical section, and no other thread should be allowed to enter it. When a thread exits the critical section, it's expected to actively set the value of &lt;code&gt;lock&lt;/code&gt; back to &lt;code&gt;0&lt;/code&gt;. The assembly pseudocode snippet below shows Tanenbaum's theoretical implementation of a spinlock leveraging the TSL instruction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nl"&gt;enter_crit_sec:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;tsl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;lock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; Reads the value of lock to register, while setting the value of lock to 1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;cmp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;reg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;#0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; Checks to see if register == 0--i.e. if the previous value of lock was 0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;jne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;enter_crit_sec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; If the previous comparison failed--i.e. register != 0 =&amp;gt; spin&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;

&lt;span class="nl"&gt;leave_crit_sec:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;move&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;lock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;#0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;; Set the value of lock back to 0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;ret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The code snippet above makes it easy to see why it is said that busy waiting wastes processor time: whenever &lt;code&gt;cmp reg, #0&lt;/code&gt; fails, the code jumps back to &lt;code&gt;enter_crit_sec&lt;/code&gt;, leading to the lock acquisition logic executing all over again, until it succeeds and the thread is allowed to enter the critical section. In fact, due to this very drawback, &lt;a href="https://kernel-internals.org/locking/spinlock/"&gt;spinlocks should only ever be used in very short critical sections of the order of microseconds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Having understood the fundamental concepts behind spinlocks, we can now get back to the kernel's implementation. We can summarize basic spinlock usage in pseudocode as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/spinlock.h&amp;gt;&lt;/span&gt;&lt;span class="c1"&gt; // Defines the spinlock kernel API&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="n"&gt;DEFINE_SPINLOCK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* Spinlock name */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// DEFINE_SPINLOCK() defines and initializes an instance of spinlock&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="n"&gt;spin_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="cm"&gt;/* Spinlock name */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Locks the critical section--spinning for lock acquisition&lt;/span&gt;

&lt;span class="cm"&gt;/* Critical section */&lt;/span&gt;

&lt;span class="n"&gt;spin_unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="cm"&gt;/* Spinlock name */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Unlocks the critical section&lt;/span&gt;

&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Instead of looking at a proof of concept implementation of spinlock usage, let's look at an excerpt of the AppArmor LSM that actively uses the spinlock API. That will allow us to unveil a very interesting aspect of the API while studying a real use case. Before delving into the analysis, one should know that AppArmor applies a caching strategy comprising of local per-CPU caches and a global cache shared across all CPUs. That strategy allows AppArmor to increase performance by minimizing taking code paths that would result in busy waiting for access to the global cache.&lt;/p&gt;
&lt;p&gt;As much as a detailed look at the internals of such mechanism is out of the scope of this blog post, &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/apparmor/lsm.c#n2136"&gt;function &lt;code&gt;aa_get_buffer()&lt;/code&gt;&lt;/a&gt;, defined in AppArmor's &lt;code&gt;lsm.c&lt;/code&gt;, provides a rather interesting perspective to the spinlock API. Particularly, it illustrates the combined usage of &lt;code&gt;spin_lock()&lt;/code&gt; and &lt;code&gt;spin_trylock()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;spin_trylock()&lt;/code&gt; API &lt;a href="https://www.kernel.org/doc/html/latest/kernel-hacking/locking.html#the-trylock-functions"&gt;tries to acquire a given lock, returning non-zero if it succeeds, and &lt;code&gt;0&lt;/code&gt; otherwise&lt;/a&gt;. As we've seen, the &lt;code&gt;spin_lock()&lt;/code&gt; API makes the thread spin for lock acquisition, effectively making it enter a state of busy waiting, thus wasting processor time. Because the &lt;code&gt;spin_trylock()&lt;/code&gt; API does not necessarily result in entering busy waiting on failure, it allows the thread to do something else in case acquiring the lock fails.&lt;/p&gt;
&lt;p&gt;The relevant portions of the &lt;code&gt;aa_get_buffer()&lt;/code&gt; function are shown below, with my analysis included as inline comments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;aa_get_buffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* &lt;/span&gt;
&lt;span class="cm"&gt;     * **OMITTED**: first, the function tries the fast path &lt;/span&gt;
&lt;span class="cm"&gt;     * without any locking, which would result in returning&lt;/span&gt;
&lt;span class="cm"&gt;     * a buffer from the local cache. This is always safe&lt;/span&gt;
&lt;span class="cm"&gt;     * because each CPU has its own instance of local cache.&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * If the above fails...&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Try to acquire the global cache lock:&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;spin_trylock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aa_buffers_lock&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* If the above fails, handle cache contention... */&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* ... then fallback to spinning for lock acquisition: */&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;spin_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aa_buffers_lock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nl"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * At this point, it is important to realize that the lock has&lt;/span&gt;
&lt;span class="cm"&gt;     * necessarily been acquired, either by spin_trylock() or by&lt;/span&gt;
&lt;span class="cm"&gt;     * spin_lock() above. Next, check if a buffer can be taken from the&lt;/span&gt;
&lt;span class="cm"&gt;     * global cache. Supposing that the function has been called from&lt;/span&gt;
&lt;span class="cm"&gt;     * a non-atomic context (which would introduce further complexity),&lt;/span&gt;
&lt;span class="cm"&gt;     * this should only be allowed if the number of buffers cached&lt;/span&gt;
&lt;span class="cm"&gt;     * globally (buffer_count) is greater than the number of buffers&lt;/span&gt;
&lt;span class="cm"&gt;     * that should always be cached globally (reserve_count).&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;reserve_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;         * If the condition above holds, take a buffer from the&lt;/span&gt;
&lt;span class="cm"&gt;         * global cache, perform additional caching logic and...&lt;/span&gt;
&lt;span class="cm"&gt;         */&lt;/span&gt;

&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* ... unlock the global cache... */&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;spin_unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aa_buffers_lock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa_buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... before returning said buffer.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* **OMITTED**: atomic context logic.  */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* &lt;/span&gt;
&lt;span class="cm"&gt;     * If buffer_count &amp;lt;= reserve_count, then this means that &lt;/span&gt;
&lt;span class="cm"&gt;     * no buffer has been returned yet. First, release the lock:&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;spin_unlock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aa_buffers_lock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* **OMITTED**: atomic context logic.  */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;     * Since no locks are being held, it is now safe to allocate&lt;/span&gt;
&lt;span class="cm"&gt;     * memory for a new buffer:&lt;/span&gt;
&lt;span class="cm"&gt;     */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;aa_buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kmalloc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aa_g_path_max&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;aa_buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// If the allocation fails...&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;try_again&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;try_again&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;spin_lock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;aa_buffers_lock&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// ... spin for the lock again...&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="cm"&gt;/*&lt;/span&gt;
&lt;span class="cm"&gt;             * ... and try once more. This seems to handle a race condition&lt;/span&gt;
&lt;span class="cm"&gt;             * between the time buffer availability has been checked--i.e.&lt;/span&gt;
&lt;span class="cm"&gt;             * buffer_count &amp;gt; reserve_count and buffer allocation:&lt;/span&gt;
&lt;span class="cm"&gt;             */&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;goto&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;pr_warn_once&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;AppArmor: Failed to allocate a memory buffer.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aa_buf&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hopefully, the case study above provided some insight into the nuances involved in using the spinlock API, and served the purpose of shedding light on the fact that as much as basic usage is rather straightforward, just as for the mutex API, there's a lot more to the spinlock API.&lt;/p&gt;
&lt;p&gt;In that sense, it seems relevant to further stress the important caveat--which the snippet above makes very clear--that &lt;a href="https://kernel-internals.org/locking/spinlock/"&gt;spinlocks should not be held across sleeping calls such as &lt;code&gt;kmalloc()&lt;/code&gt;&lt;/a&gt;. In fact, trying to sleep while holding a spinlock results in an error. Billimoria shows an instance of this bug in his text, but I wanted to cause this error myself to actually see what happened in my development box, and I ended up programming yet &lt;a href="https://github.com/vasconcedu/billimoria-linux-kernel-programming/src/branch/master/ch12/buggy_spinlock/buggy_spinlock.c"&gt;another proof of concept in C to simulate the situation where a buggy module tries to hold a spinlock across a sleeping call&lt;/a&gt;. Here's the error message that showed up in &lt;code&gt;dmesg&lt;/code&gt; after running it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Error log of a buggy module trying to hold a spinlock across sleeping calls." src="../images/buggy-spinlock.png"&gt;&lt;/p&gt;
&lt;p&gt;The error log says that the module tried "scheduling while atomic". If we follow the call trace that led to the error, we'll be able to make a bit more of sense out of it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Error log call trace resulting in a bug of &amp;quot;scheduling while atomic&amp;quot;." src="../images/buggy-spinlock-call-trace.png"&gt;&lt;/p&gt;
&lt;p&gt;We see that our &lt;code&gt;work()&lt;/code&gt; function called &lt;code&gt;msleep()&lt;/code&gt;--&lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/delay.h#n101"&gt;&lt;code&gt;ssleep()&lt;/code&gt; is a wrapper to &lt;code&gt;msleep()&lt;/code&gt;&lt;/a&gt;, so the beginning of the call trace makes sense. In turn, &lt;code&gt;msleep()&lt;/code&gt; calls &lt;code&gt;schedule_timeout()&lt;/code&gt;, which finally calls &lt;code&gt;schedule()&lt;/code&gt;, leading to the error. Ultimately, the trouble seems to be that the call sequence after &lt;code&gt;ssleep()&lt;/code&gt; ends up resorting to the scheduler subsystem to make the thread sleep until the specified time has elapsed, which is simply not allowed due to the fact that the spinlock is held. In fact, that claim finds support in the explanation that kernel maintainer Jonathan Corbet gave about that matter in &lt;a href="https://lwn.net/Articles/274695/"&gt;his article &lt;em&gt;Atomic context and kernel API design&lt;/em&gt; in LWN.net&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Kernel code generally runs in one of two fundamental contexts. Process context reigns when the kernel is running directly on behalf of a (usually) user-space process; the code which implements system calls is one example. When the kernel is running in process context, it is allowed to go to sleep if necessary. But when the kernel is running in atomic context, things like sleeping are not allowed. Code which handles hardware and software interrupts is one obvious example of atomic context.&lt;/p&gt;
&lt;p&gt;There is more to it than that, though: any kernel function moves into atomic context the moment it acquires a spinlock. Given the way spinlocks are implemented, going to sleep while holding one would be a fatal error; if some other kernel function tried to acquire the same lock, the system would almost certainly deadlock forever."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That explains why the kernel treated and logged the excerpt as a bug. While the consequences in lab environment were mild, in real life, sleeping while a spinlock is held could lead to really drastic outcomes.&lt;/p&gt;
&lt;p&gt;At this point, we have already built an initial working understanding of the most prominent kernel synchronization mechanisms, namely mutexes and spinlocks. Before wrapping up the discussion herein, let's turn our attention to what's perhaps the most insidious synchronization bug: the deadlock.&lt;/p&gt;
&lt;h3&gt;A few words on deadlocks&lt;/h3&gt;
&lt;p&gt;In this section, we'll try to reach an understanding of what deadlocks are by looking at Tanenbaum's text on the subject. Then, we'll try to understand how to prevent them in practice by looking at several kernel documentation resources that address the issue of deadlocks.&lt;/p&gt;
&lt;p&gt;Tanenbaum defines a deadlock as a situation involving a set of processes where each process in the set is waiting for an event that can only be triggered by another process in the set. The figure below illustrates the issue: &lt;/p&gt;
&lt;p&gt;&lt;img alt="Diagram representing a deadlock." src="../images/deadlock.png"&gt;&lt;/p&gt;
&lt;p&gt;In the figure, process 1 is waiting for process 2 to trigger some event. In turn, process 2 is also waiting for process 1 to trigger some event. This leads to a situation where both processes are unable to proceed due to depending on the other, configuring a deadlock. As much as the figure illustrates a situation consisting of a deadlock between two processes, more complex scenarios called "deadly embraces" comprising of multiple processes and/or locks might emerge--&lt;a href="https://www.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/c412.html"&gt;as explained by kernel hacker and ex-maintainer Rusty Russell in his &lt;em&gt;Unreliable Guide To Locking&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Tanenbaum's text also presents &lt;a href="https://en.wikipedia.org/wiki/Deadlock_(computer_science)"&gt;Coffman's conditions, which resulted from the work of Edward Coffman published in 1971&lt;/a&gt;. These four conditions must all be simultaneously satisfied for a deadlock to occur:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Mutual exclusion--i.e. each resource can only be held by one process at a time;&lt;/li&gt;
&lt;li&gt;Resource holding--i.e. each process is holding at least one resource and requesting to hold resources that are being held by other proceses;&lt;/li&gt;
&lt;li&gt;No preemption--i.e. each process must voluntarily release the resource that it is holding; and&lt;/li&gt;
&lt;li&gt;Circular wait--i.e. there must be a circular chain of processes waiting for resources that are being held by the next processes in the chain.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If all of Coffman's conditions are satisfied simultaneously, then there's a deadlock. If at least one of Coffman's conditions is not satisfied, then there's no deadlock.&lt;/p&gt;
&lt;p&gt;Coffman's work was fundamental in developing deadlock prevention and recovery algorithms, and Tanenbaum's text is very thorough about the subject. In this blog post, though, I'll try to be a bit more pragmatic and explore the concrete guidelines that apply to deadlock prevention in the specific context of Linux kernel development. Nevertheless, Coffman's conditions provide rather useful guidance--especially to kernel newbies like myself--on what to aim for while developing and what to look for while debugging potential deadlocks.&lt;/p&gt;
&lt;p&gt;That said, some general guidelines apply to deadlock prevention in the Linux kernel. First, lock ordering is very important, meaning locks should always be acquired in the same order across all threads. But why is that? &lt;a href="https://litux.nl/mirror/kerneldevelopment/0672327201/ch08lev1sec3.html"&gt;This article provides a rather convincing counterexample&lt;/a&gt;, which I reproduce--adapted--here: suppose two threads, thread 1 and thread 2, must acquire locks eeny, meeny, miny, and moe. Thread 1 acquires locks eeny, meeny, and miny, then tries to acquire lock moe, but it blocks, because thread 2 had already acquired it. In the meantime, thread 2 acquires lock moe, then tries to acquire lock eeny, but it blocks, because thread 1 had already acquired it. Now, thread 1 is waiting for thread 2 to release lock moe, and thread 2 is waiting for thread 1 to release lock eeny: a deadlock. More specifically, a deadly embrace. If both threads tried to acquire the locks in the same order, this situation wouldn't have happened.&lt;/p&gt;
&lt;p&gt;As much as the recommendation above seems to appear frequently in kernel deadlock prevention materials, Rusty Russell has a more pragmatic view on lock ordering, and I quote, from &lt;a href="https://www.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/c412.html"&gt;section 7.2. Preventing Deadlock in &lt;em&gt;Unreliable Guide To Locking&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Textbooks will tell you that if you always lock in the same order, you will never get this kind of deadlock. Practice will tell you that this approach doesn't scale: when I create a new lock, I don't understand enough of the kernel to figure out where in the 5000 lock hierarchy it will fit."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That said, he goes on to provide a rule of thumb to guide lock development:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"The best locks are encapsulated: they never get exposed in headers, and are never held around calls to non-trivial functions outside the same file. You can read through this code and see that it will never deadlock, because it never tries to grab another lock while it has that one. People using your code don't even need to know you are using a lock."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is also the view kernel maintainer Jonathan Corbet expressed &lt;a href="https://lwn.net/Articles/185666/"&gt;in his article &lt;em&gt;The kernel lock validator&lt;/em&gt; in LWN.net&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"[...] kernel developers try to define rules for the order in which locks should be acquired. But, in a system with many thousands of locks, defining a comprehensive set of rules is challenging at best, and enforcing them is even harder."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thus, one should aim at simplicity with regards to locking. Locking mechanisms are already complex in themselves, and the simpler one is able to keep lock design, the better.&lt;/p&gt;
&lt;p&gt;Several other aspects to be aware of in lock design include paying attention to the possibility of starvation--i.e. having a process wait indefinitely for a lock simply because the process holding it never releases it--, and trying to acquire the same lock a second time while still holding it, thus leading to &lt;a href="https://docs.oracle.com/cd/E19455-01/806-5257/6je9h0347/index.html"&gt;a type of deadlock called "self-deadlock"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Finally, one should be aware of &lt;a href="https://kernel-internals.org/locking/lockdep/"&gt;the Lockdep subsystem&lt;/a&gt;. Lockdep is a subsystem that is targetet at performing lock validation. It is enabled by configuration option &lt;code&gt;CONFIG_PROVE_LOCKING&lt;/code&gt;, and it works by tracking lock classes in runtime, keeping a lock dependency graph in memory that is updated every time a lock is acquired. Whenever a buggy lock dependency is observed, Lockdep issues a warning message. I compiled a kernel for my development box with &lt;code&gt;CONFIG_PROVE_LOCKING=y&lt;/code&gt;--while keeping default values to other Lockdep configuration options--to see what actual Lockdep messages look like, and after reinstalling the kernel and rerunning &lt;a href="https://github.com/vasconcedu/billimoria-linux-kernel-programming/src/branch/master/ch12/buggy_spinlock/buggy_spinlock.c"&gt;my buggy spinlock proof of concept against it&lt;/a&gt;, apart from the subsystem's data interface at &lt;code&gt;/proc/lockdep*&lt;/code&gt;, I saw new log lines in &lt;code&gt;dmesg&lt;/code&gt;, demonstrating that the subsystem was up and actively monitoring acquired locks in runtime:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lockdep dmesg output." src="../images/lockdep.png"&gt;&lt;/p&gt;
&lt;p&gt;The trouble with Lockdep is that &lt;a href="https://kernel-internals.org/locking/lockdep/"&gt;it introduces non-negligible performance overheads&lt;/a&gt;, thus it should only be used for development/testing purposes, while remaining deactivated in production kernels. Nevertheless, it is surely an interesting tool to be aware of as it provides powerful practical validation to the rather complex exercise of developing adequate kernel locking.&lt;/p&gt;
&lt;h3&gt;Conclusion and where to go from here&lt;/h3&gt;
&lt;p&gt;In this blog post, we explored the issues associated with multithreading, defining data races and critical sections, then we moved on to establishing an intuition about the theoretical aspects of locks, and discussed the most prominent locks in the Linux kernel, namely mutexes and spinlocks. We looked at their use cases and drawbacks. Finally, we briefly discussed the topic of deadlocks.&lt;/p&gt;
&lt;p&gt;To conclude, despite its length, I must stress that this blog post only provided an introductory discussion to the topic of kernel synchronization. There is much more to discuss regarding this topic, but for now, I'm satisfied. I feel that the last couple of weeks of delving into kernel synchronization have armed me with much stronger tools to perform kernel work involving locking primitives.&lt;/p&gt;
&lt;p&gt;The next steps in my systems programming journey will probably be getting back to my LSM studies--I've got a very decent continuation idea in mind for part II of &lt;a href="https://vasconcedu.github.io/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-1.html"&gt;my blog post on LSM internals--&lt;/a&gt;, and looking at the memory management subsystem. While I'm at it, I'll also probaly consolidate a project idea in which I've been working for my PhD, and certainly look for further opportunities to contribute to upstream, which I haven't done in a while to concentrate on studying synchronization.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy hacking.&lt;/em&gt;&lt;/p&gt;</content><category term="Linux"/></entry><entry><title>Study notes on the Linux Security Module (LSM) kernel framework--Part 1</title><link href="https://vasconcedu.github.io/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-1.html" rel="alternate"/><published>2026-06-03T00:00:00-03:00</published><updated>2026-06-03T00:00:00-03:00</updated><author><name>vasconcedu</name></author><id>tag:vasconcedu.github.io,2026-06-03:/study-notes-on-the-linux-security-module-lsm-kernel-framework-part-1.html</id><summary type="html">&lt;p&gt;My journey in Linux systems programming has been advancing quite well, and during the last month or so, I've been really delving into the Linux Security Module (LSM) kernel framework, in an attempt to get a grasp of its inner workings and to prepare the ground for my studies on …&lt;/p&gt;</summary><content type="html">&lt;p&gt;My journey in Linux systems programming has been advancing quite well, and during the last month or so, I've been really delving into the Linux Security Module (LSM) kernel framework, in an attempt to get a grasp of its inner workings and to prepare the ground for my studies on specific LSMs later on.&lt;/p&gt;
&lt;p&gt;Even though I've been trying to focus on the framework itself, it's inevitable to look at LSM module implementations along the way, and I've been case studying AppArmor. In fact, I found a rather interesting bug in AppArmor's &lt;code&gt;lsm.c&lt;/code&gt; along the way and patched it, so my studies resulted in the opportunity to submit &lt;a href="https://lore.kernel.org/all/20260521151314.8683-1-eduardo@eduardovasconcelos.com/"&gt;a new patch&lt;/a&gt; to upstream, with another one in the oven. Another side effect was submitting &lt;a href="https://gitlab.com/apparmor/apparmor.net/-/merge_requests/59"&gt;a pedantic merge request to AppArmor's documentation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What I realized is that LSM is a topic whose documentation is rather fragmented, and this blog post is an attempt to consolidate what I've learned so far from consuming a rather diverse range of documentation sources and navigating LSM sources in the kernel's codebase.&lt;/p&gt;
&lt;h3&gt;LSM's debut&lt;/h3&gt;
&lt;p&gt;In their paper &lt;a href="https://www.kernel.org/doc/ols/2002/ols2002-pages-604-617.pdf"&gt;&lt;em&gt;Linux Security Module Framework&lt;/em&gt;&lt;/a&gt;, early LSM developers report that back in the 2001 Linux Kernel Summit, NSA presented their work in SELinux. Linus Torvalds accepted the need for an access control framework in the kernel, but he preferred to have security modules implemented as loadable kernel modules that could be easily exchanged, while remaining simple, efficient, and non-intrusive.&lt;/p&gt;
&lt;h3&gt;Major vs. minor LSM modules&lt;/h3&gt;
&lt;p&gt;The LSM framework distinguishes between two categories of LSM modules, namely major and minor. Major LSM modules implement full Mandatory Access Control (MAC) and are mutually exclusive, meaning one cannot run two major LSM modules at once--such as AppArmor and SELinux, for instance. In turn, minor LSM modules implement specific security features and can be stacked on top of each other.&lt;/p&gt;
&lt;p&gt;Ordinary systems usually run one major LSM and a several minor ones. Loaded LSM modules can be listed by issuing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/sys/kernel/security/lsm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Basic design&lt;/h3&gt;
&lt;p&gt;LSM was designed in such a way as to remain agnostic to the different approaches to access control that security modules implement. In order to achieve this, the LSM mediates access to the kernel's internal objects by interfacing with loaded LSM modules. The diagram below, extracted from the aforementioned &lt;em&gt;Linux Security Module Framework&lt;/em&gt; paper, illustrates this concept.&lt;/p&gt;
&lt;p&gt;&lt;img alt="LSM call diagram." src="../images/lsm-diagram.png"&gt;&lt;/p&gt;
&lt;p&gt;The diagram shows that whenever a process in user space executes a syscall, it passes through the existing kernel logic, including DAC. Just before accessing the object that is the intended target of the syscall, an LSM hook makes a call to the LSM module asking whether it allows the access to proceed. In turn, the module responds either allowing or denying access to the object.&lt;/p&gt;
&lt;h3&gt;LSM hook definitions&lt;/h3&gt;
&lt;p&gt;Based on that description, it is easy to notice that hooks constitute a core element in the design of the LSM framework. In fact, an important component of the LSM is precisely the definition of its hook interface at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hook_defs.h"&gt;&lt;code&gt;include/linux/lsm_hook_defs.h&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For each hook, &lt;code&gt;lsm_hook_defs.h&lt;/code&gt; invokes the macro &lt;code&gt;LSM_HOOK&lt;/code&gt;, specifying the hook's return type, default value, name, and argument list. Take &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hook_defs.h#n215"&gt;the following line&lt;/a&gt;, for instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;LSM_HOOK&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file_open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, &lt;code&gt;LSM_HOOK&lt;/code&gt; defines hook &lt;code&gt;file_open&lt;/code&gt;, whose return type is &lt;code&gt;int&lt;/code&gt; with default value &lt;code&gt;0&lt;/code&gt;, and whose only argument is a pointer to &lt;code&gt;struct file&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;The static calls table&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;WARNING: things might get rather convoluted from this point onwards, but there's no way around it, really.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Each hook in &lt;code&gt;lsm_hook_defs.h&lt;/code&gt; is added to a static calls table defined in &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/security.c#n139"&gt;&lt;code&gt;security/security.c&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;security.c&lt;/code&gt; includes &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h"&gt;&lt;code&gt;include/linux/lsm_hooks.h&lt;/code&gt;&lt;/a&gt; (&lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/security.c#n21"&gt;here&lt;/a&gt;), which in turn includes the aforementioned &lt;code&gt;lsm_hook_defs.h&lt;/code&gt;, right at the &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h#n67"&gt;definition of &lt;code&gt;struct lsm_static_calls_table&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_calls_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#define LSM_HOOK(RET, DEFAULT, NAME, ...) \&lt;/span&gt;
&lt;span class="cp"&gt;        struct lsm_static_call NAME[MAX_LSM_COUNT];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/lsm_hook_defs.h&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#undef LSM_HOOK&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__packed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__randomize_layout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We'll get back to that struct in a minute, but first, let's take a look at its realization in &lt;code&gt;security.c&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_calls_table&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;static_calls_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__ro_after_init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__aligned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#define INIT_LSM_STATIC_CALL(NUM, NAME)                 \&lt;/span&gt;
&lt;span class="cp"&gt;    (struct lsm_static_call) {                  \&lt;/span&gt;
&lt;span class="cp"&gt;        .key = &amp;amp;STATIC_CALL_KEY(LSM_STATIC_CALL(NAME, NUM)),    \&lt;/span&gt;
&lt;span class="cp"&gt;        .trampoline = LSM_HOOK_TRAMP(NAME, NUM),        \&lt;/span&gt;
&lt;span class="cp"&gt;        .active = &amp;amp;SECURITY_HOOK_ACTIVE_KEY(NAME, NUM),     \&lt;/span&gt;
&lt;span class="cp"&gt;    },&lt;/span&gt;
&lt;span class="cp"&gt;#define LSM_HOOK(RET, DEFAULT, NAME, ...)               \&lt;/span&gt;
&lt;span class="cp"&gt;    .NAME = {                           \&lt;/span&gt;
&lt;span class="cp"&gt;        LSM_DEFINE_UNROLL(INIT_LSM_STATIC_CALL, NAME)       \&lt;/span&gt;
&lt;span class="cp"&gt;    },&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/lsm_hook_defs.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#undef LSM_HOOK&lt;/span&gt;
&lt;span class="cp"&gt;#undef INIT_LSM_STATIC_CALL&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, the code above is certainly hard to grasp at first glance. In order to understand what on Earth is going on here, one needs to break it down and look at it one piece at a time.&lt;/p&gt;
&lt;p&gt;As we've already seen, &lt;code&gt;static_calls_table&lt;/code&gt; is of type &lt;code&gt;struct lsm_static_calls_table&lt;/code&gt;, and the very first &lt;code&gt;#define&lt;/code&gt; directive in that type expands each aforementioned &lt;code&gt;LSM_HOOK&lt;/code&gt; from &lt;code&gt;lsm_hook_defs.h&lt;/code&gt; into an array of &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h#n51"&gt;&lt;code&gt;struct lsm_static_call&lt;/code&gt;&lt;/a&gt; of size &lt;code&gt;MAX_LSM_COUNT&lt;/code&gt;. Let's take a look at type &lt;code&gt;struct lsm_static_call&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;static_call_key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;trampoline&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;static_key_false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__randomize_layout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point in our introspection, we see that &lt;code&gt;struct lsm_static_call&lt;/code&gt; is composed of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A pointer to a static call key, which in turn essentially points to a target function (see &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/static_call_types.h#n63"&gt;&lt;code&gt;include/linux/static_call_types.h&lt;/code&gt;&lt;/a&gt; for detail);&lt;/li&gt;
&lt;li&gt;A pointer to what's called the &lt;a href="https://en.wikipedia.org/wiki/Trampoline_(computing)"&gt;trampoline function&lt;/a&gt;--i.e. an assembly function that provides a way of calling the function pointed to by the static call key, respecting architecture-specific calling conventions;&lt;/li&gt;
&lt;li&gt;A pointer to the hook list to which this static call belongs, which in turn maps directly to a specific LSM (see &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h#n95"&gt;&lt;code&gt;include/linux/lsm_hooks.h&lt;/code&gt;&lt;/a&gt; for detail); and&lt;/li&gt;
&lt;li&gt;A pointer to a struct that tracks whether the static call is active or not--i.e. whether it's been activated by the LSM to which it belongs (see &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/jump_label.h#n355"&gt;&lt;code&gt;include/linux/jump_label.h&lt;/code&gt;&lt;/a&gt; for detail).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally &lt;a href="https://lwn.net/Articles/722293/"&gt;annotation &lt;code&gt;__randomize_layout&lt;/code&gt;&lt;/a&gt; serves the purpose of randomizing the struct's layout for security.&lt;/p&gt;
&lt;p&gt;After establishing this intuition about &lt;code&gt;struct lsm_static_call&lt;/code&gt;, we can go back to &lt;code&gt;lsm_static_calls_table&lt;/code&gt;. I've repeated the corresponding code below for convenience: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_calls_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#define LSM_HOOK(RET, DEFAULT, NAME, ...) \&lt;/span&gt;
&lt;span class="cp"&gt;        struct lsm_static_call NAME[MAX_LSM_COUNT];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/lsm_hook_defs.h&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#undef LSM_HOOK&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__packed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__randomize_layout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So essentially, if the hooks in &lt;code&gt;lsm_hook_defs.h&lt;/code&gt; were named "eeny", "meeny", "miny", and "moe", &lt;code&gt;struct lsm_static_calls_table&lt;/code&gt; would expand into something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_calls_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;eeny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_LSM_COUNT&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;meeny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_LSM_COUNT&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;miny&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_LSM_COUNT&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;moe&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;MAX_LSM_COUNT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hence, each hook names a static calls array. With that in mind, let's get back to the realization of &lt;code&gt;struct lsm_static_calls_table&lt;/code&gt; in &lt;code&gt;security.c&lt;/code&gt;--also repeated below for convenience--, and analyze what code the preprocessor actually generates:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_calls_table&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;static_calls_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__ro_after_init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__aligned&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="cp"&gt;#define INIT_LSM_STATIC_CALL(NUM, NAME)                 \&lt;/span&gt;
&lt;span class="cp"&gt;    (struct lsm_static_call) {                  \&lt;/span&gt;
&lt;span class="cp"&gt;        .key = &amp;amp;STATIC_CALL_KEY(LSM_STATIC_CALL(NAME, NUM)),    \&lt;/span&gt;
&lt;span class="cp"&gt;        .trampoline = LSM_HOOK_TRAMP(NAME, NUM),        \&lt;/span&gt;
&lt;span class="cp"&gt;        .active = &amp;amp;SECURITY_HOOK_ACTIVE_KEY(NAME, NUM),     \&lt;/span&gt;
&lt;span class="cp"&gt;    },&lt;/span&gt;
&lt;span class="cp"&gt;#define LSM_HOOK(RET, DEFAULT, NAME, ...)               \&lt;/span&gt;
&lt;span class="cp"&gt;    .NAME = {                           \&lt;/span&gt;
&lt;span class="cp"&gt;        LSM_DEFINE_UNROLL(INIT_LSM_STATIC_CALL, NAME)       \&lt;/span&gt;
&lt;span class="cp"&gt;    },&lt;/span&gt;
&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;lt;linux/lsm_hook_defs.h&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;#undef LSM_HOOK&lt;/span&gt;
&lt;span class="cp"&gt;#undef INIT_LSM_STATIC_CALL&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here, &lt;a href="https://www.kernel.org/doc/html/latest/security/self-protection.html"&gt;&lt;code&gt;__ro_after_init&lt;/code&gt; is a kernel self-protection annotation&lt;/a&gt; that serves the purpose of marking &lt;code&gt;static_calls_table&lt;/code&gt; as read only after initialization, meaning that it will live in the kernel's read only data section at &lt;code&gt;.ro-data&lt;/code&gt; instead of &lt;code&gt;.data&lt;/code&gt;, benefiting from the kernel's strict memory permissions and preventing the struct from being written, resulting in potential tampering with LSM execution flow. In turn, &lt;code&gt;__aligned(sizeof(u64))&lt;/code&gt; serves the purpose of preventing unaligned access to the table, which may result in faults in some architectures.&lt;/p&gt;
&lt;p&gt;The second &lt;code&gt;#define&lt;/code&gt; directive expands the hooks defined in &lt;code&gt;lsm_hook_defs.h&lt;/code&gt; to initialize the static calls array for each hook. It leverages another code generation mechanism to do that, namely &lt;code&gt;LSM_DEFINE_UNROLL&lt;/code&gt;. Here's &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/security.c#n105"&gt;its definition&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define LSM_DEFINE_UNROLL(M, ...) UNROLL(MAX_LSM_COUNT, M, __VA_ARGS__)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Now, what that macro does is it takes macro &lt;code&gt;M&lt;/code&gt; and expands it &lt;code&gt;MAX_LSM_COUNT&lt;/code&gt; times. Combining that with the first &lt;code&gt;#define&lt;/code&gt; directive that defines &lt;code&gt;INIT_LSM_STATIC_CALL&lt;/code&gt; means that each hook "eeny" in &lt;code&gt;lsm_hook_defs.h&lt;/code&gt;, will expand into an array like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="na"&gt;.eeny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* 0 */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* 1 */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="na"&gt;...&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nf"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* MAX_LSM_COUNT - 1 */&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Of course, each &lt;code&gt;struct lsm_static_call&lt;/code&gt; is populated accordingly, and it shall have its fields updated at kernel initialization later on, when each LSM module has the opportunity to register its hooks. The following section documents this process.&lt;/p&gt;
&lt;h3&gt;Hook registration&lt;/h3&gt;
&lt;p&gt;If we look at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/init/main.c#n1199"&gt;LSM initialization&lt;/a&gt; at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/lsm_init.c#n485"&gt;&lt;code&gt;security_init()&lt;/code&gt;&lt;/a&gt;, we'll find the following code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;security_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;lsm_order_for_each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lsm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* skip the &amp;quot;early&amp;quot; LSMs as they have already been setup */&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cnt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lsm_count_early&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;lsm_init_single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lsm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And if we step into &lt;code&gt;lsm_init_single()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;lsm_init_single&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lsm&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;ret&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lsm&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;So, what's happening here is that the kernel is calling each LSM's &lt;code&gt;init()&lt;/code&gt; function in order to initialize it.&lt;/p&gt;
&lt;p&gt;Next, we'll try to understand what exactly the &lt;code&gt;init()&lt;/code&gt; function of an LSM module does in order to register hooks, by looking at AppArmor's initialization.&lt;/p&gt;
&lt;h3&gt;LSM initialization: a case study of AppArmor&lt;/h3&gt;
&lt;p&gt;AppArmor's initialization is at &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/apparmor/lsm.c#n2499"&gt;&lt;code&gt;security/apparmor/lsm.c&lt;/code&gt;&lt;/a&gt;. Here's what we'll find more or less half way through the module's init function: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;apparmor_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;security_add_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apparmor_hooks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ARRAY_SIZE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apparmor_hooks&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;apparmor_lsmid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The function calls &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/lsm_init.c#n369"&gt;&lt;code&gt;security_add_hooks()&lt;/code&gt;&lt;/a&gt;, providing &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/apparmor/lsm.c#n1656"&gt;&lt;code&gt;apparmor_hooks&lt;/code&gt;--AppArmor's hook list--&lt;/a&gt;, along with the size of said hook list and AppArmor's LSM ID.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apparmor_hooks&lt;/code&gt; is &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/apparmor/lsm.c#n1656"&gt;defined as&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_hooks&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__ro_after_init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptrace_access_check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_ptrace_access_check&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ptrace_traceme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_ptrace_traceme&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capget&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_capget&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_capable&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;move_mount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_move_mount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sb_mount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_sb_mount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sb_umount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_sb_umount&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;LSM_HOOK_INIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sb_pivotroot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;apparmor_sb_pivotroot&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Which translates to an array of mappings of type &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h#n95"&gt;&lt;code&gt;struct security_hook_list&lt;/code&gt;&lt;/a&gt;, each mapping an LSM hook to an AppArmor implementation of that hook. The definition of &lt;code&gt;struct security_hook_list&lt;/code&gt; is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scalls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;union&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_list_options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lsmid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__randomize_layout&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The struct contains a list of static calls as &lt;code&gt;scalls&lt;/code&gt;--you might want to revisit the definition of &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h#n51"&gt;&lt;code&gt;struct lsm_static_call&lt;/code&gt;&lt;/a&gt;--, a union of type &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h#n38"&gt;&lt;code&gt;union security_list_options&lt;/code&gt;&lt;/a&gt;, and a pointer to the LSM's ID.&lt;/p&gt;
&lt;p&gt;If we introspect into &lt;code&gt;security_list_options&lt;/code&gt;, here's what we'll find:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;union&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_list_options&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#define LSM_HOOK(RET, DEFAULT, NAME, ...) RET (*NAME)(__VA_ARGS__);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cpf"&gt;&amp;quot;lsm_hook_defs.h&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#undef LSM_HOOK&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lsm_func_addr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Thus, the union expands the definitions of &lt;code&gt;LSM_HOOK&lt;/code&gt; from &lt;code&gt;lsm_hook_defs.h&lt;/code&gt; into a list of function pointers to each LSM hook. In turn, macro &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/lsm_hooks.h#n137"&gt;&lt;code&gt;LSM_HOOK_INIT&lt;/code&gt;&lt;/a&gt; translates to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;#define LSM_HOOK_INIT(NAME, HOOK)           \&lt;/span&gt;
&lt;span class="cp"&gt;    {                       \&lt;/span&gt;
&lt;span class="cp"&gt;        .scalls = static_calls_table.NAME,  \&lt;/span&gt;
&lt;span class="cp"&gt;        .hook = { .NAME = HOOK }        \&lt;/span&gt;
&lt;span class="cp"&gt;    }&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Namely, it initializes &lt;code&gt;struct security_hook_list.scalls&lt;/code&gt; with a pointer to the array in &lt;code&gt;static_calls_table&lt;/code&gt; that corresponds to the LSM hook, and &lt;code&gt;struct security_hook_list.security_list_options.hook&lt;/code&gt;'s field corresponding to the name of the given LSM hook with a pointer to AppArmor's implementation of the hook.&lt;/p&gt;
&lt;p&gt;Putting it all together, this results in &lt;code&gt;apparmor_hooks&lt;/code&gt; expanding into something like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;apparmor_hooks&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scalls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;static_calls_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsm_hook_0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsm_hook_0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apparmor_hook_0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsmid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scalls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;static_calls_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsm_hook_1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsm_hook_1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apparmor_hook_1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsmid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cm"&gt;/* ... */&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Hence, each slot in &lt;code&gt;apparmor_hooks&lt;/code&gt; keeps a reference to a specific LSM hook's array in &lt;code&gt;static_calls_table&lt;/code&gt;, along with the mapping of said LSM hook to AppArmor's implementation.&lt;/p&gt;
&lt;h3&gt;The final steps of hook registration&lt;/h3&gt;
&lt;p&gt;After looking at AppArmor's initialization to understand what the data structure provided as an argument to &lt;code&gt;security_add_hooks()&lt;/code&gt; looks like, we can resume our analysis of LSM hook registration by finally stepping into that function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;security_add_hooks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lsmid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;lsmid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;lsmid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lsm_static_call_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;exhausted LSM callback slots with LSM %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="n"&gt;lsmid&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We see that &lt;code&gt;security_add_hooks&lt;/code&gt; iterates through the security hook list initializing the static calls of the given LSM, which if you'll remember were supposed to be updated at kernel initialization--i.e. precisely where we are at this point.&lt;/p&gt;
&lt;p&gt;Let's get deeper into our introspection and step into &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/security/lsm_init.c#n341"&gt;&lt;code&gt;lsm_static_call_init()&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;static&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__init&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;lsm_static_call_init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;security_hook_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;lsm_static_call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scall&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;scalls&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MAX_LSM_COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="cm"&gt;/* Update the first static call that is not used yet */&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;scall&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;__static_call_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scall&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;scall&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;trampoline&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;hook&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lsm_func_addr&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;scall&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;static_branch_enable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scall&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;active&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;scall&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ENOSPC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this stage, the function locates the first empty slot in the &lt;code&gt;static_calls_table&lt;/code&gt; array corresponding to the LSM hook being registered, and then updates that slot by associating it with the LSM module's implementation of the hook.&lt;/p&gt;
&lt;p&gt;This way, the LSM should be able to query the LSM module that registered that hook for access approval.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Wrapping up this first part of my study notes on LSM, I'll say that I'm happy with what I've learned so far.&lt;/p&gt;
&lt;p&gt;I've only scratched the surface of the LSM framework, but the elegantly simple inner workings I've touched so far have already proven to be a masterpiece in software engineering, from its solid design principles to the intricante relationship between its static calls table and its hook registration routines. Every layer of it has deepened my appreciation for Linux.&lt;/p&gt;
&lt;p&gt;But again, I've only scratched the surface. There's so much more to learn about LSM, and I still want to go deeper into AppArmor and then SELinux. This rabbit hole goes much deeper than what I initially envisioned, but I'm eager to dive in.&lt;/p&gt;
&lt;p&gt;When I post Part 2 of these study notes, I hope to have more kernel patches to share and a much deeper understanding of the LSM to offer.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy hacking.&lt;/em&gt;&lt;/p&gt;</content><category term="Linux"/></entry><entry><title>How I became a contributor to the Linux kernel</title><link href="https://vasconcedu.github.io/how-i-became-a-contributor-to-the-linux-kernel.html" rel="alternate"/><published>2026-04-29T20:07:00-03:00</published><updated>2026-04-29T20:07:00-03:00</updated><author><name>vasconcedu</name></author><id>tag:vasconcedu.github.io,2026-04-29:/how-i-became-a-contributor-to-the-linux-kernel.html</id><summary type="html">&lt;p&gt;After finishing &lt;a href="https://training.linuxfoundation.org/training/a-beginners-guide-to-linux-kernel-development-lfd103/"&gt;Linux Foundation's course on kernel development (LFD103)&lt;/a&gt;, I was feeling ready and eager to make my first contribution to the Linux kernel. The entire process from starting LFD103 and having my first patch accepted into upstream took me two weeks. This post provides insight on what my first …&lt;/p&gt;</summary><content type="html">&lt;p&gt;After finishing &lt;a href="https://training.linuxfoundation.org/training/a-beginners-guide-to-linux-kernel-development-lfd103/"&gt;Linux Foundation's course on kernel development (LFD103)&lt;/a&gt;, I was feeling ready and eager to make my first contribution to the Linux kernel. The entire process from starting LFD103 and having my first patch accepted into upstream took me two weeks. This post provides insight on what my first patch was about and describes the submission process.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IMPORTANT: the timespan to my first accepted patch was relatively short, but I didn't start from zero: I already had some 15 years or experience with Linux both as a hobby and as a professional tool--including the experience of compiling Linux From Scratch--, considerable programming experience in C, and almost 10 years of experience in security engineering. Due to this background, I was able to go from no experience in kernel development to an accepted patch relatively faster than average, so my debut as a kernel newbie may differ from that of most people.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;Patch no. 1&lt;/h3&gt;
&lt;p&gt;As a first exercise of what I had learned in LFD103, I decided to make a very simple and unrisky contribution to the DRM subsystem, namely fixing a comment. I've prepared a patch and sent it on April 24, 2026. Here's the link to it on the kernel archives:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lore.kernel.org/all/20260424140515.2019-1-eduardo@eduardovasconcelos.com/"&gt;https://lore.kernel.org/all/20260424140515.2019-1-eduardo@eduardovasconcelos.com/&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There was a typo on a word, and my patch fixed it. After a while, I noticed that I had made a mistake: I had forgotten to apply column limiting both to my commit subject and to my commit message! &lt;a href="https://www.kernel.org/doc/html/latest/process/submitting-patches.html"&gt;Kernel contribution guidelines&lt;/a&gt; take formatting very seriously, and as much as I knew from browsing &lt;a href="https://lore.kernel.org/"&gt;lore.kernel.org&lt;/a&gt; that mistakes like these might even go unnoticed, I wanted to make sure to follow the entire submission process accordingly, so I decided to submit a second version of my patch.&lt;/p&gt;
&lt;h3&gt;Patch no. 1 v2&lt;/h3&gt;
&lt;p&gt;Subsequent versions of patches must follow specific submission guidelines. After fixing some Emacs line wrapping rules to make sure my commit would be formatted accordingly this time--FIY I was a Vim user for more than a decade before converting, but that's another story--, I have prepared and tagged my patch as &lt;code&gt;v2&lt;/code&gt;, and sent it the same day, on April 24, 2026:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lore.kernel.org/all/20260424183630.3764-1-eduardo@eduardovasconcelos.com/"&gt;https://lore.kernel.org/all/20260424183630.3764-1-eduardo@eduardovasconcelos.com/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This process--and the fact that I made a submission mistake and had to send a second version of my patch--gave me a good understanding of the submission process, and made me a lot more confident about it. I felt ready to look for opportunities to contribute with some actual code changes.&lt;/p&gt;
&lt;h3&gt;Patch no. 2&lt;/h3&gt;
&lt;p&gt;LFD103 discusses kernel compilation and the patch submission process, but it doesn't teach you about any kernel drivers or subsystems, so submitting patches consisting of substantial changes to kernel code was a no-no.&lt;/p&gt;
&lt;p&gt;That's why I decided to look at a simpler driver for a piece of hardware I'm very much passionate about: &lt;code&gt;thinkpad_acpi&lt;/code&gt;. This driver is under &lt;code&gt;/drivers/platform/x86/lenovo/thinkpad_acpi.c&lt;/code&gt;, and it interfaces with general IBM/Lenovo ThinkPad HW. Among other things, it controls hotkey events, battery charging, LED activation, lid closing events, etc. What's nice about this driver is that since I own two ThinkPads and my wife owns another one, I had plenty of real HW available to test any changes that I made.&lt;/p&gt;
&lt;p&gt;So I decided to look for opportunities to submit code improvements to this driver. After some code reviewing, I found one: there was an unneeded &lt;code&gt;goto&lt;/code&gt; statement in the &lt;a href="https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/tree/drivers/platform/x86/lenovo/thinkpad_acpi.c#n2461"&gt;hotkey polling routine, namely &lt;code&gt;hotkey_kthread()&lt;/code&gt;&lt;/a&gt;: the routine had a single exit location, with no cleanup code preceding its &lt;code&gt;return&lt;/code&gt; statement, but it still performed a &lt;code&gt;goto&lt;/code&gt; jump to its only exit location, introducing unneeded control flow logic in disaccordance with &lt;a href="https://www.kernel.org/doc/html/latest/process/coding-style.html#centralized-exiting-of-functions"&gt;the Linux kernel coding style, that explicitly mandates otherwise&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I refactored the routine's exit, compiled and installed the kernel with the change on my old ThinkPad T420si, and after confirming that everything was working as expected, I submitted the patch on the ungodly hour of 3:40 a.m. on April 25, 2026:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lore.kernel.org/all/20260425063936.9360-1-eduardo@eduardovasconcelos.com/"&gt;https://lore.kernel.org/all/20260425063936.9360-1-eduardo@eduardovasconcelos.com/&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Patch no. 2 accepted&lt;/h3&gt;
&lt;p&gt;Mark Pearson acknowledged my submission and applied his revision tag to it a few hours later, at 9:49 a.m.:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lore.kernel.org/all/16d45e47-8234-4983-84d4-91b66c5a67db@app.fastmail.com/"&gt;https://lore.kernel.org/all/16d45e47-8234-4983-84d4-91b66c5a67db@app.fastmail.com/&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Patch no. 2 applied&lt;/h3&gt;
&lt;p&gt;Maintainer Ilpo Järvinen reported that he had applied my patch to his next tree on April 28, 2026:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lore.kernel.org/all/177739236303.10973.18101852287173259208.b4-ty@linux.intel.com/"&gt;https://lore.kernel.org/all/177739236303.10973.18101852287173259208.b4-ty@linux.intel.com/&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Patch no. 1 accepted&lt;/h3&gt;
&lt;p&gt;In the meantime, my initial patch to the DRM subsystem had also been reviewed by maintainer Thomas Zimmermann, who applied his revision tag to it:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://lore.kernel.org/all/23bd7123-f22e-4ab2-b3f0-03833e25bf7d@suse.de/"&gt;https://lore.kernel.org/all/23bd7123-f22e-4ab2-b3f0-03833e25bf7d@suse.de/&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Next steps&lt;/h3&gt;
&lt;p&gt;This exercise of submitting a couple of patches gave me an initial grasp at the contribution process. None of the reviewers pointed out any mistakes or inconsistencies on my initial patches, so I assume that I have understood the submission process and the documentation that I read so far sufficiently well. Under such circumstances, I felt ready to transition towards studying the kernel more deeply as a preparation to tackle more substantial contributions.&lt;/p&gt;
&lt;p&gt;That is why I have pivoted to studying the Linux kernel architecture and some of its main subsystems. I'm currently working on Kaiwan N. Billimoria's &lt;em&gt;Linux Kernel Programming&lt;/em&gt; book--currently doing module development--, and in the near future, I intend to leverage my experience as a security engineer to specialize in kernel security. It feels like a natural path to take. So after I develop an initial broad understanding of the kernel, I intend to narrow down to AppArmor, then pivot to SELinux, and gradually work towards a broader understanding of LSM.&lt;/p&gt;</content><category term="Linux"/></entry><entry><title>Linux kernel development setup in Arch Linux</title><link href="https://vasconcedu.github.io/linux-kernel-development-setup-in-arch-linux.html" rel="alternate"/><published>2026-04-22T19:00:00-03:00</published><updated>2026-04-22T19:00:00-03:00</updated><author><name>vasconcedu</name></author><id>tag:vasconcedu.github.io,2026-04-22:/linux-kernel-development-setup-in-arch-linux.html</id><summary type="html">&lt;p&gt;I've been working on &lt;a href="https://training.linuxfoundation.org/training/a-beginners-guide-to-linux-kernel-development-lfd103/"&gt;Linux Foundation's kernel development course (LFD103)&lt;/a&gt;, and I have chosen a virtual Arch box running on QEMU on top of a physical Arch host as my kernel development machine.&lt;/p&gt;
&lt;p&gt;LFD103 recommends using either Ubuntu or Debian for course work, but I wanted to use Arch anyway …&lt;/p&gt;</summary><content type="html">&lt;p&gt;I've been working on &lt;a href="https://training.linuxfoundation.org/training/a-beginners-guide-to-linux-kernel-development-lfd103/"&gt;Linux Foundation's kernel development course (LFD103)&lt;/a&gt;, and I have chosen a virtual Arch box running on QEMU on top of a physical Arch host as my kernel development machine.&lt;/p&gt;
&lt;p&gt;LFD103 recommends using either Ubuntu or Debian for course work, but I wanted to use Arch anyway, and--to no surprise--I have found that Arch Linux is not as straightforward to setup as the recommended distros. &lt;a href="https://www.kernel.org/doc/html/latest/process/changes.html"&gt;kernel.org recommends a few dependencies&lt;/a&gt; that aren't even packaged for Arch and must be built manually. Furthermore, configuring GRUB to boot a newly compiled kernel is not as straightforward either.&lt;/p&gt;
&lt;p&gt;In the end, I have succeeded in compiling a kernel image from upstream and booting it using my Arch setup, but not without having to overcome some hardship first. The post herein aims at documenting this process. It assumes prior knowledge in Arch Linux installation and some knowledge in Linux virtualization and general system administration.&lt;/p&gt;
&lt;h3&gt;Partitioniong and installation&lt;/h3&gt;
&lt;p&gt;It's no surprise one must save extra space in &lt;code&gt;/boot&lt;/code&gt; to pursue kernel development. LFD103 recommends saving at least 3 GiB. I went with 4 GiB, saved another 4 GiB for swapping, and left 40 GiB for my root filesystem, totalling 48 GiB for my virtual drive. &lt;/p&gt;
&lt;p&gt;I opted for a minimal Arch installation, with &lt;code&gt;emacs&lt;/code&gt; and &lt;code&gt;openssh&lt;/code&gt; as extra packages. I also enabled &lt;code&gt;root&lt;/code&gt; and created a sudoer user. &lt;/p&gt;
&lt;h3&gt;SSH setup&lt;/h3&gt;
&lt;p&gt;I've then configured an SSH server to allow me to access the development machine from my host system.&lt;/p&gt;
&lt;h3&gt;Packaged development dependencies&lt;/h3&gt;
&lt;p&gt;After logging in through SSH and configuring &lt;code&gt;~/.emacs&lt;/code&gt; to suit my preferences, I've proceeded to installing packages needed for development.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;IMPORTANT: As much as not all dependencies are necessary on all systems, this post covers the installation of all tools listed under &lt;a href="https://www.kernel.org/doc/html/latest/process/changes.html"&gt;kernel.org's minimal requirements list&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first step is to install basic development dependencies. LFD103 points to the Ubuntu ones, but these won't do it for Arch. Some Arch dependencies are packaged or named differently, and some others aren't even packaged and must be installed manually from AUR. The installation commands herein are the result of translating suggested Ubuntu dependency installation steps to Arch:&lt;/p&gt;
&lt;p&gt;First, one must issue:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;pacman&lt;span class="w"&gt; &lt;/span&gt;-S&lt;span class="w"&gt; &lt;/span&gt;base-devel&lt;span class="w"&gt; &lt;/span&gt;git&lt;span class="w"&gt; &lt;/span&gt;cscope&lt;span class="w"&gt; &lt;/span&gt;ncurses&lt;span class="w"&gt; &lt;/span&gt;openssl&lt;span class="w"&gt; &lt;/span&gt;bison&lt;span class="w"&gt; &lt;/span&gt;flex
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And then:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;pacman&lt;span class="w"&gt; &lt;/span&gt;-S&lt;span class="w"&gt; &lt;/span&gt;pahole&lt;span class="w"&gt; &lt;/span&gt;jfsutils&lt;span class="w"&gt; &lt;/span&gt;xfsprogs&lt;span class="w"&gt; &lt;/span&gt;squashfs-tools&lt;span class="w"&gt; &lt;/span&gt;btrfs-progs&lt;span class="w"&gt; &lt;/span&gt;quota-tools&lt;span class="w"&gt; &lt;/span&gt;ppp&lt;span class="w"&gt; &lt;/span&gt;nfs-utils&lt;span class="w"&gt; &lt;/span&gt;udev&lt;span class="w"&gt; &lt;/span&gt;grub&lt;span class="w"&gt; &lt;/span&gt;python3&lt;span class="w"&gt; &lt;/span&gt;bc&lt;span class="w"&gt; &lt;/span&gt;python-sphinx&lt;span class="w"&gt; &lt;/span&gt;ccache&lt;span class="w"&gt; &lt;/span&gt;wget
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That installs most development dependencies, but one in particular, namely &lt;code&gt;udev&lt;/code&gt;, won't work as expected unless we link it to &lt;code&gt;/usr/bin/udevd&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;ln&lt;span class="w"&gt; &lt;/span&gt;-s&lt;span class="w"&gt; &lt;/span&gt;/lib/systemd/systemd-udevd&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/udevd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Unpackaged dependencies&lt;/h3&gt;
&lt;p&gt;Some kernel development dependencies are not packaged for Arch, however they're available on AUR and one may install them from there.&lt;/p&gt;
&lt;p&gt;The first one is &lt;code&gt;pcmciautils&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://aur.archlinux.org/cgit/aur.git/snapshot/pcmciautils.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;After downloading the snapshot, one may proceed to the standard AUR build and installation procedure, but first, we must satisfy yet another build dependency:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;pacman&lt;span class="w"&gt; &lt;/span&gt;-S&lt;span class="w"&gt; &lt;/span&gt;sysfsutils
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then build and install the AUR package normally.&lt;/p&gt;
&lt;p&gt;The next unpackaged dependency we'll need is &lt;code&gt;mcelog&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;wget&lt;span class="w"&gt; &lt;/span&gt;https://aur.archlinux.org/cgit/aur.git/snapshot/mcelog.tar.gz
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Standard AUR build and installation procedures should work without any issues.&lt;/p&gt;
&lt;h3&gt;Checking minimal requirements against kernel.org&lt;/h3&gt;
&lt;p&gt;At this point, the system should be good to go, but it's a good idea to check minimal requirements against the &lt;a href="https://www.kernel.org/doc/html/latest/process/changes.html"&gt;dependencies in kernel.org&lt;/a&gt; to make sure installed versions satisfy the requirements.&lt;/p&gt;
&lt;p&gt;If everything is okay, one may proceed to compiling the kernel. This matter exceeds the scope of the post herein, but if all steps above were executed accordingly, one should be able to compile and boot the upstream kernel without any issues. Here's a screenshot of a successful boot with an image I've compiled from &lt;code&gt;linux-stable&lt;/code&gt;--7.0.1 as of the time of writing on April 22, 2026:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Linux kernel 7.0.1 uname output." src="../images/kernel-7.0.1.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy hacking.&lt;/em&gt;&lt;/p&gt;</content><category term="Linux"/></entry><entry><title>[pt-BR] Compilando Linux From Scratch (LFS)</title><link href="https://vasconcedu.github.io/pt-br-compilando-linux-from-scratch-lfs.html" rel="alternate"/><published>2026-04-04T18:00:00-03:00</published><updated>2026-04-04T18:00:00-03:00</updated><author><name>vasconcedu</name></author><id>tag:vasconcedu.github.io,2026-04-04:/pt-br-compilando-linux-from-scratch-lfs.html</id><summary type="html">&lt;blockquote&gt;
&lt;p&gt;"Playfully doing something difficult, whether useful or not, that is hacking."&lt;/p&gt;
&lt;p&gt;-- Richard Stallman, &lt;em&gt;On Hacking&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ao longo das últimas semanas, em detrimento dos devidos sono e asseio, trabalhei, pela primeira vez, na compilação do meu próprio sistema Linux. Usei como guia o livro &lt;em&gt;Linux From Scratch: Version 13.0-systemd&lt;/em&gt;, publicado …&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;"Playfully doing something difficult, whether useful or not, that is hacking."&lt;/p&gt;
&lt;p&gt;-- Richard Stallman, &lt;em&gt;On Hacking&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ao longo das últimas semanas, em detrimento dos devidos sono e asseio, trabalhei, pela primeira vez, na compilação do meu próprio sistema Linux. Usei como guia o livro &lt;em&gt;Linux From Scratch: Version 13.0-systemd&lt;/em&gt;, publicado pelo projeto homônimo, &lt;a href="https://linuxfromscratch.org/index.html"&gt;Linux From Scratch&lt;/a&gt;. Os processos de compilação e configuração básica já estão finalizados. Obtive um sistema LFS plenamente funcional, em dual boot com o host virtual Arch que utilizei para compilá-lo.&lt;/p&gt;
&lt;p&gt;Eu uso--e amo--Linux há cerca de 15 anos. Comecei pelo Ubuntu 8.04 LTS "Hardy Heron", em meados de 2010. Desde então, não parei mais: já tive a minha parcela de distro hopping; converti algumas pessoas e ajudei outras tantas a instalarem ou configurarem Linux; instalei e administrei servidores Linux críticos no meu primeiro emprego formal, como sysadmin; venho usando Linux como ferramenta de trabalho há anos, como engenheiro de segurança; e a minha computação pessoal, como não poderia deixar de ser, é completamente baseada em Linux. Atualmente, o meu amado daily driver é um ThinkPad T480--usado--com Arch, &lt;em&gt;btw&lt;/em&gt;. No entanto, até hoje, uma das peças faltantes na minha trajetória era compilar o meu próprio sistema Linux. Esse já era um sonho de longa data. Há pouco, ele foi realizado.&lt;/p&gt;
&lt;p&gt;Em um primeiro momento, esse exercício pode até parecer algo muito difícil de ser cumprido, mas a verdade é que compilar LFS é muito mais simples do que parece, dada a devida assiduidade no uso de Linux, e o grande "plot twist" é que esse processo serviu para revelar muita, mas muita coisa sobre Linux que eu simplesmente não sei. Vejo que compilar o meu próprio sistema pela primeira vez foi apenas o início da fase de desconstrução em um processo de aprendizado que, estou certo, ainda vai durar muito tempo.&lt;/p&gt;
&lt;p&gt;Sem mais delongas, a seguir, relato humildemente como foi a minha experiência de compilar Linux From Scratch.&lt;/p&gt;
&lt;h3&gt;Para quem é o desafio de compilar Linux From Scratch?&lt;/h3&gt;
&lt;p&gt;Eu não diria que seria impossível que uma pessoa que tivesse apenas iniciado a sua trajetória com Linux conseguisse compilar o seu próprio sistema Linux com base no Linux From Scratch, mas esse processo seria extremamente demorado, trabalhoso e frustrante, demandaria uma força de vontade e uma paciência muito grandes, e seria possível que o tempo investido nisso fosse mais bem aproveitado se essa pessoa se dedicasse a aprender assuntos mais básicos, adquirindo pelo menos alguns anos de experiência com Linux como daily driver antes de atacar esse projeto.&lt;/p&gt;
&lt;p&gt;Não se engane: apesar de o livro ser muito detalhado, ele assume certos conhecimentos a priori, de forma que compilar Linux From Scratch demanda bastante afinidade com o terminal e conhecimento prático sobre particionamento, sistemas de arquivos, permissões e builds a partir de código-fonte, dentre outros assuntos.&lt;/p&gt;
&lt;p&gt;O livro deixa muita coisa a cargo do leitor, e eu diria que conhecimento prático de Linux--que me parece que só pode ser adquirido ao longo de um tempo razoável de daily driving--é fundamental para o sucesso de certos procedimentos de compilação e até mesmo de setup. Ter afinidade com virtualização de sistemas Linux, por exemplo, me parece algo particularmente interessante, por simplificar--muito--o setup do host de compilação. Sem falar que colocar o sistema de pé depois da compilação é praticamente por sua conta: o livro fala muito pouco sobre configurações de boot, até porque estas dependem muito do host que você tiver escolhido, do uso ou não de dual boot e/ou das características da máquina em que você for colocar o LFS de pé.&lt;/p&gt;
&lt;h3&gt;Instalação do host&lt;/h3&gt;
&lt;p&gt;Eu optei por utilizar uma máquina virtual Arch como host de compilação, usando o QEMU em uma máquina Arch física como virtualizador. O primeiro passo foi criar um disco virtual para o host. Usei o &lt;code&gt;qemu-img&lt;/code&gt; para isso:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qemu-img&lt;span class="w"&gt; &lt;/span&gt;create&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;qcow2&lt;span class="w"&gt; &lt;/span&gt;arch.qcow2&lt;span class="w"&gt; &lt;/span&gt;24G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;24 GB me pareceram razoáveis diante da &lt;a href="https://www.linuxfromscratch.org/hints/downloads/files/partitioning-for-lfs.txt"&gt;documentação suplementar do LFS sobre particionamento&lt;/a&gt;. Em seguida, iniciei a VM com:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qemu-system-x86_64&lt;span class="w"&gt; &lt;/span&gt;-cdrom&lt;span class="w"&gt; &lt;/span&gt;archlinux-2026.03.01-x86_64.iso&lt;span class="w"&gt; &lt;/span&gt;-boot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;d&lt;span class="w"&gt; &lt;/span&gt;-drive&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arch.qcow2,format&lt;span class="o"&gt;=&lt;/span&gt;qcow2&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;8G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Não cabe cobrir todo o processo de instalação do Arch aqui, mas eu particionei o disco virtual da seguinte forma:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Particionamento do host." src="../images/particionamento.png"&gt;&lt;/p&gt;
&lt;p&gt;Quanto ao tipo de instalação do Arch, optei por &lt;code&gt;Minimal&lt;/code&gt;. Também incluí os pacotes &lt;code&gt;openssh&lt;/code&gt; e &lt;code&gt;vim&lt;/code&gt;. De uma próxima vez que for compilar LFS, também vou incluir &lt;code&gt;wget&lt;/code&gt; e, possivelmente, trocar o Vim pelo Nano, por uma questão de manter a simplicidade. Como constatei depois, as necessidades de edição para compilar LFS são bastante básicas. Além disso, criei um usuário e fiz dele sudoer. Teria sido interessante, ainda, já instalar os pacotes das dependências de compilação do LFS para o host durante a instalação do Arch. Como não fiz isso, precisei instalá-las mais adiante.&lt;/p&gt;
&lt;p&gt;Finalizada a instalação, deliguei e reiniciei a VM com:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qemu-system-x86_64 -boot order=d -drive file=arch.qcow2,format=qcow2 -m 8G
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Seria interessante já ter utilizado &lt;code&gt;-smp 4&lt;/code&gt; aqui para iniciar o host com 4 processadores. Eu acabei não fazendo isso e precisando desligar a VM para reiniciá-la com essa configuração mais adiante, no meio do processo, o que resultou em ter de refazer parte do setup de compilação no host, conforme a seção 2.3 e o final do capítulo 7 do livro discutem.&lt;/p&gt;
&lt;h3&gt;Configuração do SSH&lt;/h3&gt;
&lt;p&gt;Após acessar a VM via VNC, segui com a configuração de SSH para que eu pudesse ter um terminal mais bem servido de features para trabalhar durante a compilação. Eu diria que esse passo é quase imprescindível ao optar por compilar LFS em ambiente virtualizado. Poder colar comandos copiados da versão HTML do livro em uma sessão de SSH entre a sua máquina física e o host de compilação me parece ser a maneira mais segura de seguir os passos do livro, por evitar problemas relacionados a erros de digitação ao reproduzir comandos em um terminal em que você não consegue copiar e colar, além de possíveis inconsistências ao copiar comandos a partir da versão PDF do livro.&lt;/p&gt;
&lt;p&gt;No Arch--com o &lt;code&gt;openssh&lt;/code&gt; devidamente instalado--, configurar SSH consiste em editar &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt;. É uma boa ideia fazer um backup desse arquivo antes. É preciso descomentar:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Port&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;22&lt;/span&gt;
AddressFamily&lt;span class="w"&gt; &lt;/span&gt;any
ListenAddress&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0.0.0
ListenAddress&lt;span class="w"&gt; &lt;/span&gt;::
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Além de incluir:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;AllowUsers&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;usuário&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Onde &lt;code&gt;&amp;lt;usuário&amp;gt;&lt;/code&gt; é o usuário que você criou durante a instalação do Arch. Depois disso, reiniciei o &lt;code&gt;sshd&lt;/code&gt; e verifiquei se estava tudo certo com:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;start&lt;span class="w"&gt; &lt;/span&gt;sshd.service
sudo&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;sshd.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Por fim, configurei o serviço para iniciar automaticamente com: &lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;systemctl&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;enable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;sshd.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Feito isso, desliguei e voltei a iniciar o host de compilação, redirecionando a porta 60022 da minha máquina física para a porta 22 da VM:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qemu-system-x86_64&lt;span class="w"&gt; &lt;/span&gt;-boot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;d&lt;span class="w"&gt; &lt;/span&gt;-drive&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arch.qcow2,format&lt;span class="o"&gt;=&lt;/span&gt;qcow2&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;8G&lt;span class="w"&gt; &lt;/span&gt;-nic&lt;span class="w"&gt; &lt;/span&gt;user,hostfwd&lt;span class="o"&gt;=&lt;/span&gt;tcp::60022-:22
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Com isso, pude conectar à VM usando SSH e trabalhar de maneira muito mais simples. A essa altura, fiz a instalação das dependências previstas na seção 2.2 do livro. Como já mencionei, de uma próxima fez, farei a instalação destas durante a instalação do Arch para poder pular esse passo.&lt;/p&gt;
&lt;h3&gt;Partição de LFS&lt;/h3&gt;
&lt;p&gt;Criei um segundo disco virtual de 24 GB com o &lt;code&gt;qemu-img&lt;/code&gt;. Em seguida, desliguei e voltei a iniciar a VM, dessa vez com os dois discos:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;qemu-system-x86_64&lt;span class="w"&gt; &lt;/span&gt;-boot&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;d&lt;span class="w"&gt; &lt;/span&gt;-drive&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;arch.qcow2,format&lt;span class="o"&gt;=&lt;/span&gt;qcow2&lt;span class="w"&gt; &lt;/span&gt;-drive&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;lfs.qcow2,format&lt;span class="o"&gt;=&lt;/span&gt;qcow2&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;8G&lt;span class="w"&gt; &lt;/span&gt;-nic&lt;span class="w"&gt; &lt;/span&gt;user,hostfwd&lt;span class="o"&gt;=&lt;/span&gt;tcp::60022-:22
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Pude confirmar que o segundo disco estava disponível com &lt;code&gt;lsblk&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;NAME&lt;span class="w"&gt;   &lt;/span&gt;MAJ:MIN&lt;span class="w"&gt; &lt;/span&gt;RM&lt;span class="w"&gt;  &lt;/span&gt;SIZE&lt;span class="w"&gt; &lt;/span&gt;RO&lt;span class="w"&gt; &lt;/span&gt;TYPE&lt;span class="w"&gt; &lt;/span&gt;MOUNTPOINTS
fd0&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;:0&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;4K&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;
sda&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;:0&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;24G&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;
├─sda1&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;:1&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;200M&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;part&lt;span class="w"&gt; &lt;/span&gt;/boot
├─sda2&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;:2&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;2G&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;part&lt;span class="w"&gt; &lt;/span&gt;
└─sda3&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;:3&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;21&lt;/span&gt;.8G&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;part&lt;span class="w"&gt; &lt;/span&gt;/
sdb&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;:16&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;24G&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;
sr0&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;11&lt;/span&gt;:0&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;1024M&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;rom&lt;span class="w"&gt;  &lt;/span&gt;
zram0&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;253&lt;/span&gt;:0&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;4G&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;disk&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;SWAP&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;O próximo passo foi formatá-lo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;sudo&lt;span class="w"&gt; &lt;/span&gt;mkfs&lt;span class="w"&gt; &lt;/span&gt;-v&lt;span class="w"&gt; &lt;/span&gt;-t&lt;span class="w"&gt; &lt;/span&gt;ext4&lt;span class="w"&gt; &lt;/span&gt;/dev/sdb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Em seguida, pude seguir com a definição de &lt;code&gt;$LFS&lt;/code&gt;, a partir da seção 2.6, e seguir as instruções de compilação e configuração, até o começo do capítulo 10. Essa foi a parte mais difícil do processo, em grande medida por ser muito repetitiva, e levei algo como duas semanas para concluí-la, mas é preciso levar em consideração que eu havia iniciado o host com um processador só. A certa altura, eu o reiniciei com 4 processadores para melhorar em alguns pontos percentuais a minha qualidade de vida. Esse tempo poderia ter sido menor se eu o tivesse iniciado com 4 processadores desde o começo. Além disso, eu não me dediquei em tempo integral, mas apenas no tempo livre, especialmente à noite e nos finais de semana. Ademais, como eu executei todos os testes, a compilação de alguns pacotes, como o GCC, foi particularmente demorada.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;p&gt;.&lt;/p&gt;
&lt;h3&gt;/etc/fstab&lt;/h3&gt;
&lt;p&gt;Finalizada a compilação dos pacotes, segui aos últimos passos da instalação. A seção 10.2 discute a criação de &lt;code&gt;/etc/fstab&lt;/code&gt;. Dadas as características do meu setup, o meu arquivo ficou assim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bash-5.3#&lt;span class="w"&gt; &lt;/span&gt;cat&lt;span class="w"&gt; &lt;/span&gt;/mnt/lfs/etc/fstab&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="c1"&gt;# Begin /etc/fstab&lt;/span&gt;

&lt;span class="c1"&gt;# file system  mount-point  type     options             dump  fsck&lt;/span&gt;
&lt;span class="c1"&gt;#                                                              order&lt;/span&gt;

/dev/sda1&lt;span class="w"&gt;     &lt;/span&gt;/boot&lt;span class="w"&gt;        &lt;/span&gt;ext4&lt;span class="w"&gt;     &lt;/span&gt;defaults&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
/dev/sdb&lt;span class="w"&gt;      &lt;/span&gt;/&lt;span class="w"&gt;            &lt;/span&gt;ext4&lt;span class="w"&gt;     &lt;/span&gt;defaults&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
/dev/sda2&lt;span class="w"&gt;     &lt;/span&gt;swap&lt;span class="w"&gt;         &lt;/span&gt;swap&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="nv"&gt;pri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;

&lt;span class="c1"&gt;# End /etc/fstab&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Compilação e instalação do kernel&lt;/h3&gt;
&lt;p&gt;A compilação do kernel foi mais simples e, sobretudo, mais rápida do que eu imaginava. Chequei os parâmetros de compilação duas vezes. O processo se deu sem intercorrências e durou menos de uma noite--iniciei a compilação por volta das 20h30 e, às 6h00 do outro dia, ela já havia finalizado. Como o meu plano era manter o LFS em dual boot com o host Arch, montei a partição de boot e copiei a imagem compilada do kernel para ela--no meu caso, a imagem x86_64 &lt;code&gt;vmlinuz-6.18.10-lfs-13.0-systemd&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;bash-5.3#&lt;span class="w"&gt; &lt;/span&gt;ls&lt;span class="w"&gt; &lt;/span&gt;/boot/vmlinuz-6.18.10-lfs-13.0-systemd&lt;span class="w"&gt; &lt;/span&gt;
/boot/vmlinuz-6.18.10-lfs-13.0-systemd
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3&gt;Configuração do GRUB&lt;/h3&gt;
&lt;p&gt;Finalmente, alterei as configurações do GRUB. Optei por adicionar a entrada de menu do GRUB do LFS no &lt;code&gt;grub.cfg&lt;/code&gt; criado pelo próprio Arch, em uma seção destinada especificamente a personalizações. Apesar de ser fácil gerar um novo arquivo de configuração do GRUB com o host Arch caso o original seja corrompido, é uma boa ideia fazer um backup desse arquivo antes de editá-lo:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;### BEGIN /etc/grub.d/40_custom ###&lt;/span&gt;
&lt;span class="c1"&gt;# This file provides an easy way to add custom menu entries.  Simply type the&lt;/span&gt;
&lt;span class="c1"&gt;# menu entries you want to add after this comment.  Be careful not to change&lt;/span&gt;
&lt;span class="c1"&gt;# the &amp;#39;exec tail&amp;#39; line above.&lt;/span&gt;

insmod&lt;span class="w"&gt; &lt;/span&gt;part_msdos
insmod&lt;span class="w"&gt; &lt;/span&gt;ext2
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hd0,msdos1&amp;#39;&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;gfxpayload&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1024x768x32
menuentry&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GNU/Linux, Linux 6.18.10-lfs-13.0-systemd&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;linux&lt;span class="w"&gt;   &lt;/span&gt;/vmlinuz-6.18.10-lfs-13.0-systemd&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/dev/sdb&lt;span class="w"&gt; &lt;/span&gt;ro
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;### END /etc/grub.d/40_custom ###&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Note-se que o processo de atualização do GRUB conforme documentado &lt;a href="https://wiki.archlinux.org/title/GRUB"&gt;na Wiki do Arch&lt;/a&gt;--i.e. usando &lt;code&gt;grub-mkconfig&lt;/code&gt;, &lt;strong&gt;mesmo com &lt;code&gt;GRUB_DISABLE_OS_PROBER=false&lt;/code&gt;&lt;/strong&gt;--não funciona aqui. É preciso atualizar o GRUB manualmente, conforme o próprio livro previne, no final da seção 10.4.4.&lt;/p&gt;
&lt;h3&gt;Boot&lt;/h3&gt;
&lt;p&gt;Após algumas iterações de reescrita do &lt;code&gt;grub.cfg&lt;/code&gt;, &lt;em&gt;voilà&lt;/em&gt;!&lt;/p&gt;
&lt;p&gt;&lt;img alt="Sistema LFS de pé." src="../images/lfs.png"&gt;&lt;/p&gt;
&lt;h3&gt;Registro na base de usuários do LFS&lt;/h3&gt;
&lt;p&gt;Depois de finalmente colocar o sistema de pé, decidi registrar o meu nome na &lt;a href="https://www.linuxfromscratch.org/cgi-bin/lfscounter.php"&gt;base de usuários do LFS&lt;/a&gt;. Sou o usuário número 32222--de quebra, fiquei com um identificador super fácil de lembrar:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Meu nome na base de LFS users." src="../images/lfscounter.png"&gt;&lt;/p&gt;
&lt;h3&gt;E agora?&lt;/h3&gt;
&lt;p&gt;Eu não poderia estar mais satisfeito. Pus em prática um anseio de longa data. Além disso, não apenas obtive um sistema funcional, mas também aprendi várias coisas e descobri outras tantas que eu ainda preciso aprender sobre Linux.&lt;/p&gt;
&lt;p&gt;Isso me leva a concluir que eu preciso refazer esse exercício de compilar LFS. Tão logo tenha acabado de "curtir" a minha primeira instalação e descansado um pouco--este relato pode não deixar transparecer, mas o projeto aqui descrito é bastante cansativo--, vou reiniciar o processo, do zero.&lt;/p&gt;
&lt;p&gt;Isso vai me permitir olhar com mais calma e maturidade para alguns aspectos do processo de compilação que ainda não estão completamente claros para mim, em especial a teoria por trás de cross compiling, além de modificar o código de alguns pacotes e experimentar com alguns parâmetros de compilação. Antes, porém, planejo fazer algumas rodadas de experimentação com o sistema funcional que eu já tenho, além de portá-lo para uma máquina física. A vítima vai ser um ThinkPad T420si--usado--velho de guerra.&lt;/p&gt;
&lt;p&gt;É isso. Estando o leitor munido do significado de "hacking", enunciado no começo deste relato, eu me despeço:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Happy hacking, and may Linux be with you.&lt;/em&gt;&lt;/p&gt;</content><category term="Linux"/></entry></feed>