
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/">
    <channel>
        <title><![CDATA[ The Cloudflare Blog ]]></title>
        <description><![CDATA[ Get the latest news on how products at Cloudflare are built, technologies used, and join the teams helping to build a better Internet. ]]></description>
        <link>https://blog.cloudflare.com</link>
        <atom:link href="https://blog.cloudflare.com/" rel="self" type="application/rss+xml"/>
        <language>en-us</language>
        <image>
            <url>https://blog.cloudflare.com/favicon.png</url>
            <title>The Cloudflare Blog</title>
            <link>https://blog.cloudflare.com</link>
        </image>
        <lastBuildDate>Mon, 13 Apr 2026 21:43:10 GMT</lastBuildDate>
        <item>
            <title><![CDATA[Linux kernel security tunables everyone should consider adopting]]></title>
            <link>https://blog.cloudflare.com/linux-kernel-hardening/</link>
            <pubDate>Wed, 06 Mar 2024 14:00:43 GMT</pubDate>
            <description><![CDATA[ This post illustrates some of the Linux Kernel features, which are helping us to keep our production systems more secure. We will deep dive into how they work and why you may consider enabling them as well ]]></description>
            <content:encoded><![CDATA[ 
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1MwDdpmFexb2YBRb6Y4VkD/f8fe7a101729ee9fa518a457b8bb170a/Technical-deep-dive-for-Security-week.png" />
            
            </figure><p>The Linux kernel is the heart of many modern production systems. It decides when any code is allowed to run and which programs/users can access which resources. It manages memory, mediates access to hardware, and does a bulk of work under the hood on behalf of programs running on top. Since the kernel is always involved in any code execution, it is in the best position to protect the system from malicious programs, enforce the desired system security policy, and provide security features for safer production environments.</p><p>In this post, we will review some Linux kernel security configurations we use at Cloudflare and how they help to block or minimize a potential system compromise.</p>
    <div>
      <h2>Secure boot</h2>
      <a href="#secure-boot">
        
      </a>
    </div>
    <p>When a machine (either a laptop or a server) boots, it goes through several boot stages:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7LULhRh5qh02zFnil5ZPnl/b2e1867ba56492628bbb95ba6850448e/image3-17.png" />
            
            </figure><p>Within a secure boot architecture each stage from the above diagram verifies the integrity of the next stage before passing execution to it, thus forming a so-called secure boot chain. This way “trustworthiness” is extended to every component in the boot chain, because if we verified the code integrity of a particular stage, we can trust this code to verify the integrity of the next stage.</p><p>We <a href="/anchoring-trust-a-hardware-secure-boot-story">have previously covered</a> how Cloudflare implements secure boot in the initial stages of the boot process. In this post, we will focus on the Linux kernel.</p><p>Secure boot is the cornerstone of any operating system security mechanism. The Linux kernel is the primary enforcer of the operating system security configuration and policy, so we have to be sure that the Linux kernel itself has not been tampered with. In our previous <a href="/anchoring-trust-a-hardware-secure-boot-story">post about secure boot</a> we showed how we use UEFI Secure Boot to ensure the integrity of the Linux kernel.</p><p>But what happens next? After the kernel gets executed, it may try to load additional drivers, or as they are called in the Linux world, kernel modules. And kernel module loading is not confined just to the boot process. A module can be loaded at any time during runtime — a new device being plugged in and a driver is needed, some additional extensions in the networking stack are required (for example, for fine-grained firewall rules), or just manually by the system administrator.</p><p>However, uncontrolled kernel module loading might pose a significant risk to system integrity. Unlike regular programs, which get executed as user space processes, kernel modules are pieces of code which get injected and executed directly in the Linux kernel address space. There is no separation between the code and data in different kernel modules and core kernel subsystems, so everything can access everything. This means that a rogue kernel module can completely nullify the trustworthiness of the operating system and make secure boot useless. As an example, consider a simple Debian 12 (Bookworm installation), but with <a href="https://selinuxproject.org/">SELinux</a> configured and enforced:</p>
            <pre><code>ignat@dev:~$ lsb_release --all
No LSB modules are available.
Distributor ID:	Debian
Description:	Debian GNU/Linux 12 (bookworm)
Release:	12
Codename:	bookworm
ignat@dev:~$ uname -a
Linux dev 6.1.0-18-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64 GNU/Linux
ignat@dev:~$ sudo getenforce
Enforcing</code></pre>
            <p>Now we need to do some research. First, we see that we’re running 6.1.76 Linux Kernel. If we explore the source code, we would see that <a href="https://elixir.bootlin.com/linux/v6.1.76/source/security/selinux/hooks.c#L107">inside the kernel, the SELinux configuration is stored in a singleton structure</a>, which <a href="https://elixir.bootlin.com/linux/v6.1.76/source/security/selinux/include/security.h#L92">is defined</a> as follows:</p>
            <pre><code>struct selinux_state {
#ifdef CONFIG_SECURITY_SELINUX_DISABLE
	bool disabled;
#endif
#ifdef CONFIG_SECURITY_SELINUX_DEVELOP
	bool enforcing;
#endif
	bool checkreqprot;
	bool initialized;
	bool policycap[__POLICYDB_CAP_MAX];

	struct page *status_page;
	struct mutex status_lock;

	struct selinux_avc *avc;
	struct selinux_policy __rcu *policy;
	struct mutex policy_mutex;
} __randomize_layout;</code></pre>
            <p>From the above, we can see that if the kernel configuration has <code>CONFIG_SECURITY_SELINUX_DEVELOP</code> enabled, the structure would have a boolean variable <code>enforcing</code>, which controls the enforcement status of SELinux at runtime. This is exactly what the above <code>$ sudo getenforce</code> command returns. We can double check that the Debian kernel indeed has the configuration option enabled:</p>
            <pre><code>ignat@dev:~$ grep CONFIG_SECURITY_SELINUX_DEVELOP /boot/config-`uname -r`
CONFIG_SECURITY_SELINUX_DEVELOP=y</code></pre>
            <p>Good! Now that we have a variable in the kernel, which is responsible for some security enforcement, we can try to attack it. One problem though is the <code>__randomize_layout</code> attribute: since <code>CONFIG_SECURITY_SELINUX_DISABLE</code> is actually not set for our Debian kernel, normally <code>enforcing</code> would be the first member of the struct. Thus if we know where the struct is, we immediately know the position of the <code>enforcing</code> flag. With <code>__randomize_layout</code>, during kernel compilation the compiler might place members at arbitrary positions within the struct, so it is harder to create generic exploits. But arbitrary struct randomization within the kernel <a href="https://elixir.bootlin.com/linux/v6.1.76/source/security/Kconfig.hardening#L301">may introduce performance impact</a>, so is often disabled and it is disabled for the Debian kernel:</p>
            <pre><code>ignat@dev:~$ grep RANDSTRUCT /boot/config-`uname -r`
CONFIG_RANDSTRUCT_NONE=y</code></pre>
            <p>We can also confirm the compiled position of the <code>enforcing</code> flag using the <a href="https://git.kernel.org/pub/scm/devel/pahole/pahole.git/">pahole tool</a> and either kernel debug symbols, if available, or (on modern kernels, if enabled) in-kernel <a href="https://www.kernel.org/doc/html/next/bpf/btf.html">BTF</a> information. We will use the latter:</p>
            <pre><code>ignat@dev:~$ pahole -C selinux_state /sys/kernel/btf/vmlinux
struct selinux_state {
	bool                       enforcing;            /*     0     1 */
	bool                       checkreqprot;         /*     1     1 */
	bool                       initialized;          /*     2     1 */
	bool                       policycap[8];         /*     3     8 */

	/* XXX 5 bytes hole, try to pack */

	struct page *              status_page;          /*    16     8 */
	struct mutex               status_lock;          /*    24    32 */
	struct selinux_avc *       avc;                  /*    56     8 */
	/* --- cacheline 1 boundary (64 bytes) --- */
	struct selinux_policy *    policy;               /*    64     8 */
	struct mutex               policy_mutex;         /*    72    32 */

	/* size: 104, cachelines: 2, members: 9 */
	/* sum members: 99, holes: 1, sum holes: 5 */
	/* last cacheline: 40 bytes */
};</code></pre>
            <p>So <code>enforcing</code> is indeed located at the start of the structure and we don’t even have to be a privileged user to confirm this.</p><p>Great! All we need is the runtime address of the <code>selinux_state</code> variable inside the kernel:(shell/bash)</p>
            <pre><code>ignat@dev:~$ sudo grep selinux_state /proc/kallsyms
ffffffffbc3bcae0 B selinux_state</code></pre>
            <p>With all the information, we can write an almost textbook simple kernel module to manipulate the SELinux state:</p><p>Mymod.c:</p>
            <pre><code>#include &lt;linux/module.h&gt;

static int __init mod_init(void)
{
	bool *selinux_enforce = (bool *)0xffffffffbc3bcae0;
	*selinux_enforce = false;
	return 0;
}

static void mod_fini(void)
{
}

module_init(mod_init);
module_exit(mod_fini);

MODULE_DESCRIPTION("A somewhat malicious module");
MODULE_AUTHOR("Ignat Korchagin &lt;ignat@cloudflare.com&gt;");
MODULE_LICENSE("GPL");</code></pre>
            <p>And the respective <code>Kbuild</code> file:</p>
            <pre><code>obj-m := mymod.o</code></pre>
            <p>With these two files we can build a full fledged kernel module according to <a href="https://docs.kernel.org/kbuild/modules.html">the official kernel docs</a>:</p>
            <pre><code>ignat@dev:~$ cd mymod/
ignat@dev:~/mymod$ ls
Kbuild  mymod.c
ignat@dev:~/mymod$ make -C /lib/modules/`uname -r`/build M=$PWD
make: Entering directory '/usr/src/linux-headers-6.1.0-18-cloud-amd64'
  CC [M]  /home/ignat/mymod/mymod.o
  MODPOST /home/ignat/mymod/Module.symvers
  CC [M]  /home/ignat/mymod/mymod.mod.o
  LD [M]  /home/ignat/mymod/mymod.ko
  BTF [M] /home/ignat/mymod/mymod.ko
Skipping BTF generation for /home/ignat/mymod/mymod.ko due to unavailability of vmlinux
make: Leaving directory '/usr/src/linux-headers-6.1.0-18-cloud-amd64'</code></pre>
            <p>If we try to load this module now, the system may not allow it due to the SELinux policy:</p>
            <pre><code>ignat@dev:~/mymod$ sudo insmod mymod.ko
insmod: ERROR: could not load module mymod.ko: Permission denied</code></pre>
            <p>We can workaround it by copying the module into the standard module path somewhere:</p>
            <pre><code>ignat@dev:~/mymod$ sudo cp mymod.ko /lib/modules/`uname -r`/kernel/crypto/</code></pre>
            <p>Now let’s try it out:</p>
            <pre><code>ignat@dev:~/mymod$ sudo getenforce
Enforcing
ignat@dev:~/mymod$ sudo insmod /lib/modules/`uname -r`/kernel/crypto/mymod.ko
ignat@dev:~/mymod$ sudo getenforce
Permissive</code></pre>
            <p>Not only did we disable the SELinux protection via a malicious kernel module, we did it quietly. Normal <code>sudo setenforce 0</code>, even if allowed, would go through the official <a href="https://elixir.bootlin.com/linux/v6.1.76/source/security/selinux/selinuxfs.c#L173">selinuxfs interface and would emit an audit message</a>. Our code manipulated the kernel memory directly, so no one was alerted. This illustrates why uncontrolled kernel module loading is very dangerous and that is why most security standards and commercial security monitoring products advocate for close monitoring of kernel module loading.</p><p>But we don’t need to monitor kernel modules at Cloudflare. Let’s repeat the exercise on a Cloudflare production kernel (module recompilation skipped for brevity):</p>
            <pre><code>ignat@dev:~/mymod$ uname -a
Linux dev 6.6.17-cloudflare-2024.2.9 #1 SMP PREEMPT_DYNAMIC Mon Sep 27 00:00:00 UTC 2010 x86_64 GNU/Linux
ignat@dev:~/mymod$ sudo insmod /lib/modules/`uname -r`/kernel/crypto/mymod.ko
insmod: ERROR: could not insert module /lib/modules/6.6.17-cloudflare-2024.2.9/kernel/crypto/mymod.ko: Key was rejected by service</code></pre>
            <p>We get a <code>Key was rejected by service</code> error when trying to load a module, and the kernel log will have the following message:</p>
            <pre><code>ignat@dev:~/mymod$ sudo dmesg | tail -n 1
[41515.037031] Loading of unsigned module is rejected</code></pre>
            <p>This is because the Cloudflare kernel <a href="https://elixir.bootlin.com/linux/v6.6.17/source/kernel/module/Kconfig#L211">requires all the kernel modules to have a valid signature</a>, so we don’t even have to worry about a malicious module being loaded at some point:</p>
            <pre><code>ignat@dev:~$ grep MODULE_SIG_FORCE /boot/config-`uname -r`
CONFIG_MODULE_SIG_FORCE=y</code></pre>
            <p>For completeness it is worth noting that the Debian stock kernel also supports module signatures, but does not enforce it:</p>
            <pre><code>ignat@dev:~$ grep MODULE_SIG /boot/config-6.1.0-18-cloud-amd64
CONFIG_MODULE_SIG_FORMAT=y
CONFIG_MODULE_SIG=y
# CONFIG_MODULE_SIG_FORCE is not set
…</code></pre>
            <p>The above configuration means that the kernel will validate a module signature, if available. But if not - the module will be loaded anyway with a warning message emitted and the <a href="https://docs.kernel.org/admin-guide/tainted-kernels.html">kernel will be tainted</a>.</p>
    <div>
      <h3>Key management for kernel module signing</h3>
      <a href="#key-management-for-kernel-module-signing">
        
      </a>
    </div>
    <p>Signed kernel modules are great, but it creates a key management problem: to sign a module we need a signing keypair that is trusted by the kernel. The public key of the keypair is usually directly embedded into the kernel binary, so the kernel can easily use it to verify module signatures. The private key of the pair needs to be protected and secure, because if it is leaked, anyone could compile and sign a potentially malicious kernel module which would be accepted by our kernel.</p><p>But what is the best way to eliminate the risk of losing something? Not to have it in the first place! Luckily the kernel build system <a href="https://elixir.bootlin.com/linux/v6.6.17/source/certs/Makefile#L36">will generate a random keypair</a> for module signing, if none is provided. At Cloudflare, we use that feature to sign all the kernel modules during the kernel compilation stage. When the compilation and signing is done though, instead of storing the key in a secure place, we just destroy the private key:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1EWN5Kh5NebZTW3kAzK9uM/0dfb98bc095710e5b317a128d3efb1c0/image1-19.png" />
            
            </figure><p>So with the above process:</p><ol><li><p>The kernel build system generated a random keypair, compiles the kernel and modules</p></li><li><p>The public key is embedded into the kernel image, the private key is used to sign all the modules</p></li><li><p>The private key is destroyed</p></li></ol><p>With this scheme not only do we not have to worry about module signing key management, we also use a different key for each kernel we release to production. So even if a particular build process is hijacked and the signing key is not destroyed and potentially leaked, the key will no longer be valid when a kernel update is released.</p><p>There are some flexibility downsides though, as we can’t “retrofit” a new kernel module for an already released kernel (for example, for <a href="https://www.cloudflare.com/en-gb/press-releases/2023/cloudflare-powers-hyper-local-ai-inference-with-nvidia/">a new piece of hardware we are adopting</a>). However, it is not a practical limitation for us as we release kernels often (roughly every week) to keep up with a steady stream of bug fixes and vulnerability patches in the Linux Kernel.</p>
    <div>
      <h2>KEXEC</h2>
      <a href="#kexec">
        
      </a>
    </div>
    <p><a href="https://en.wikipedia.org/wiki/Kexec">KEXEC</a> (or <code>kexec_load()</code>) is an interesting system call in Linux, which allows for one kernel to directly execute (or jump to) another kernel. The idea behind this is to switch/update/downgrade kernels faster without going through a full reboot cycle to minimize the potential system downtime. However, it was developed quite a while ago, when secure boot and system integrity was not quite a concern. Therefore its original design has security flaws and is known to be able to <a href="https://mjg59.dreamwidth.org/28746.html">bypass secure boot and potentially compromise system integrity</a>.</p><p>We can see the problems just based on the <a href="https://man7.org/linux/man-pages/man2/kexec_load.2.html">definition of the system call itself</a>:</p>
            <pre><code>struct kexec_segment {
	const void *buf;
	size_t bufsz;
	const void *mem;
	size_t memsz;
};
...
long kexec_load(unsigned long entry, unsigned long nr_segments, struct kexec_segment *segments, unsigned long flags);</code></pre>
            <p>So the kernel expects just a collection of buffers with code to execute. Back in those days there was not much desire to do a lot of data parsing inside the kernel, so the idea was to parse the to-be-executed kernel image in user space and provide the kernel with only the data it needs. Also, to switch kernels live, we need an intermediate program which would take over while the old kernel is shutting down and the new kernel has not yet been executed. In the kexec world this program is called <a href="https://git.kernel.org/pub/scm/utils/kernel/kexec/kexec-tools.git/tree/purgatory">purgatory</a>. Thus the problem is evident: we give the kernel a bunch of code and it will happily execute it at the highest privilege level. But instead of the original kernel or purgatory code, we can easily provide code similar to the one demonstrated earlier in this post, which disables SELinux (or does something else to the kernel).</p><p>At Cloudflare we have had <code>kexec_load()</code> disabled for some time now just because of this. The advantage of faster reboots with kexec comes with <a href="https://elixir.bootlin.com/linux/v6.6.17/source/kernel/Kconfig.kexec#L30">a (small) risk of improperly initialized hardware</a>, so it was not worth using it even without the security concerns. However, kexec does provide one useful feature — it is the foundation of the Linux kernel <a href="https://docs.kernel.org/admin-guide/kdump/kdump.html">crashdumping solution</a>. In a nutshell, if a kernel crashes in production (due to a bug or some other error), a backup kernel (previously loaded with kexec) can take over, collect and save the memory dump for further investigation. This allows to more effectively investigate kernel and other issues in production, so it is a powerful tool to have.</p><p>Luckily, since the <a href="https://mjg59.dreamwidth.org/28746.html">original problems with kexec were outlined</a>, Linux developed an alternative <a href="https://elixir.bootlin.com/linux/v6.6.17/source/kernel/Kconfig.kexec#L36">secure interface for kexec</a>: instead of buffers with code it expects file descriptors with the to-be-executed kernel image and initrd and does parsing inside the kernel. Thus, only a valid kernel image can be supplied. On top of this, we can <a href="https://elixir.bootlin.com/linux/v6.6.17/source/kernel/Kconfig.kexec#L48">configure</a> and <a href="https://elixir.bootlin.com/linux/v6.6.17/source/kernel/Kconfig.kexec#L62">require</a> kexec to ensure the provided images are properly signed, so only authorized code can be executed in the kexec scenario. A secure configuration for kexec looks something like this:</p>
            <pre><code>ignat@dev:~$ grep KEXEC /boot/config-`uname -r`
CONFIG_KEXEC_CORE=y
CONFIG_HAVE_IMA_KEXEC=y
# CONFIG_KEXEC is not set
CONFIG_KEXEC_FILE=y
CONFIG_KEXEC_SIG=y
CONFIG_KEXEC_SIG_FORCE=y
CONFIG_KEXEC_BZIMAGE_VERIFY_SIG=y
…</code></pre>
            <p>Above we ensure that the legacy <code>kexec_load()</code> system call is disabled by disabling <code>CONFIG_KEXEC</code>, but still can configure Linux Kernel crashdumping via the new <code>kexec_file_load()</code> system call via <code>CONFIG_KEXEC_FILE=y</code> with enforced signature checks (<code>CONFIG_KEXEC_SIG=y</code> and <code>CONFIG_KEXEC_SIG_FORCE=y</code>).</p><p>Note that stock Debian kernel has the legacy <code>kexec_load()</code> system call enabled and does not enforce signature checks for <code>kexec_file_load()</code> (similar to module signature checks):</p>
            <pre><code>ignat@dev:~$ grep KEXEC /boot/config-6.1.0-18-cloud-amd64
CONFIG_KEXEC=y
CONFIG_KEXEC_FILE=y
CONFIG_ARCH_HAS_KEXEC_PURGATORY=y
CONFIG_KEXEC_SIG=y
# CONFIG_KEXEC_SIG_FORCE is not set
CONFIG_KEXEC_BZIMAGE_VERIFY_SIG=y
…</code></pre>
            
    <div>
      <h2>Kernel Address Space Layout Randomization (KASLR)</h2>
      <a href="#kernel-address-space-layout-randomization-kaslr">
        
      </a>
    </div>
    <p>Even on the stock Debian kernel if you try to repeat the exercise we described in the “Secure boot” section of this post after a system reboot, you will likely see it would fail to disable SELinux now. This is because we hardcoded the kernel address of the <code>selinux_state</code> structure in our malicious kernel module, but the address changed now:</p>
            <pre><code>ignat@dev:~$ sudo grep selinux_state /proc/kallsyms
ffffffffb41bcae0 B selinux_state</code></pre>
            <p><a href="https://docs.kernel.org/security/self-protection.html#kernel-address-space-layout-randomization-kaslr">Kernel Address Space Layout Randomization (or KASLR)</a> is a simple concept: it slightly and randomly shifts the kernel code and data on each boot:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6e7sTU0q25lHmBG5b4Q8if/d19a762047547d6f14dff4f9ab8f2d81/Screenshot-2024-03-06-at-13.53.23-2.png" />
            
            </figure><p>This is to combat targeted exploitation (like the malicious module in this post) based on the knowledge of the location of internal kernel structures and code. It is especially useful for popular Linux distribution kernels, like the Debian one, because most users use the same binary and anyone can download the debug symbols and the System.map file with all the addresses of the kernel internals. Just to note: it will not prevent the module loading and doing harm, but it will likely not achieve the targeted effect of disabling SELinux. Instead, it will modify a random piece of kernel memory potentially causing the kernel to crash.</p><p>Both the Cloudflare kernel and the Debian one have this feature enabled:</p>
            <pre><code>ignat@dev:~$ grep RANDOMIZE_BASE /boot/config-`uname -r`
CONFIG_RANDOMIZE_BASE=y</code></pre>
            
    <div>
      <h3>Restricted kernel pointers</h3>
      <a href="#restricted-kernel-pointers">
        
      </a>
    </div>
    <p>While KASLR helps with targeted exploits, it is quite easy to bypass since everything is shifted by a single random offset as shown on the diagram above. Thus if the attacker knows at least one runtime kernel address, they can recover this offset by subtracting the runtime address from the compile time address of the same symbol (function or data structure) from the kernel’s System.map file. Once they know the offset, they can recover the addresses of all other symbols by adjusting them by this offset.</p><p>Therefore, modern kernels take precautions not to leak kernel addresses at least to unprivileged users. One of the main tunables for this is the <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#kptr-restrict">kptr_restrict sysctl</a>. It is a good idea to set it at least to <code>1</code> to not allow regular users to see kernel pointers:(shell/bash)</p>
            <pre><code>ignat@dev:~$ sudo sysctl -w kernel.kptr_restrict=1
kernel.kptr_restrict = 1
ignat@dev:~$ grep selinux_state /proc/kallsyms
0000000000000000 B selinux_state</code></pre>
            <p>Privileged users can still see the pointers:</p>
            <pre><code>ignat@dev:~$ sudo grep selinux_state /proc/kallsyms
ffffffffb41bcae0 B selinux_state</code></pre>
            <p>Similar to <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#kptr-restrict">kptr_restrict sysctl</a> there is also <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#dmesg-restrict">dmesg_restrict</a>, which if set, would prevent regular users from reading the kernel log (which may also leak kernel pointers via its messages). While you need to explicitly set <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#kptr-restrict">kptr_restrict sysctl</a> to a non-zero value on each boot (or use some system sysctl configuration utility, like <a href="https://www.freedesktop.org/software/systemd/man/latest/systemd-sysctl.service.html">this one</a>), you can configure <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#dmesg-restrict">dmesg_restrict</a> initial value via the <code>CONFIG_SECURITY_DMESG_RESTRICT</code> kernel configuration option. Both the Cloudflare kernel and the Debian one enforce <a href="https://www.kernel.org/doc/html/latest/admin-guide/sysctl/kernel.html#dmesg-restrict">dmesg_restrict</a> this way:</p>
            <pre><code>ignat@dev:~$ grep CONFIG_SECURITY_DMESG_RESTRICT /boot/config-`uname -r`
CONFIG_SECURITY_DMESG_RESTRICT=y</code></pre>
            <p>Worth noting that <code>/proc/kallsyms</code> and the kernel log are not the only sources of potential kernel pointer leaks. There is a lot of legacy in the Linux kernel and [new sources are continuously being found and patched]. That’s why it is very important to stay up to date with the latest kernel bugfix releases.</p>
    <div>
      <h2>Lockdown LSM</h2>
      <a href="#lockdown-lsm">
        
      </a>
    </div>
    <p><a href="https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html">Linux Security Modules (LSM)</a> is a hook-based framework for implementing security policies and Mandatory Access Control in the Linux Kernel. We have [covered our usage of another LSM module, BPF-LSM, previously].</p><p>BPF-LSM is a useful foundational piece for our kernel security, but in this post we want to mention another useful LSM module we use — <a href="https://man7.org/linux/man-pages/man7/kernel_lockdown.7.html">the Lockdown LSM</a>. Lockdown can be in three states (controlled by the <code>/sys/kernel/security/lockdown</code> special file):</p>
            <pre><code>ignat@dev:~$ cat /sys/kernel/security/lockdown
[none] integrity confidentiality</code></pre>
            <p><code>none</code> is the state where nothing is enforced and the module is effectively disabled. When Lockdown is in the <code>integrity</code> state, the kernel tries to prevent any operation, which may compromise its integrity. We already covered some examples of these in this post: loading unsigned modules and executing unsigned code via KEXEC. But there are other potential ways (which are mentioned in <a href="https://man7.org/linux/man-pages/man7/kernel_lockdown.7.html">the LSM’s man page</a>), all of which this LSM tries to block. <code>confidentiality</code> is the most restrictive mode, where Lockdown will also try to prevent any information leakage from the kernel. In practice this may be too restrictive for server workloads as it blocks all runtime debugging capabilities, like <code>perf</code> or eBPF.</p><p>Let’s see the Lockdown LSM in action. On a barebones Debian system the initial state is <code>none</code> meaning nothing is locked down:</p>
            <pre><code>ignat@dev:~$ uname -a
Linux dev 6.1.0-18-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.76-1 (2024-02-01) x86_64 GNU/Linux
ignat@dev:~$ cat /sys/kernel/security/lockdown
[none] integrity confidentiality</code></pre>
            <p>We can switch the system into the <code>integrity</code> mode:</p>
            <pre><code>ignat@dev:~$ echo integrity | sudo tee /sys/kernel/security/lockdown
integrity
ignat@dev:~$ cat /sys/kernel/security/lockdown
none [integrity] confidentiality</code></pre>
            <p>It is worth noting that we can only put the system into a more restrictive state, but not back. That is, once in <code>integrity</code> mode we can only switch to <code>confidentiality</code> mode, but not back to <code>none</code>:</p>
            <pre><code>ignat@dev:~$ echo none | sudo tee /sys/kernel/security/lockdown
none
tee: /sys/kernel/security/lockdown: Operation not permitted</code></pre>
            <p>Now we can see that even on a stock Debian kernel, which as we discovered above, does not enforce module signatures by default, we cannot load a potentially malicious unsigned kernel module anymore:</p>
            <pre><code>ignat@dev:~$ sudo insmod mymod/mymod.ko
insmod: ERROR: could not insert module mymod/mymod.ko: Operation not permitted</code></pre>
            <p>And the kernel log will helpfully point out that this is due to Lockdown LSM:</p>
            <pre><code>ignat@dev:~$ sudo dmesg | tail -n 1
[21728.820129] Lockdown: insmod: unsigned module loading is restricted; see man kernel_lockdown.7</code></pre>
            <p>As we can see, Lockdown LSM helps to tighten the security of a kernel, which otherwise may not have other enforcing bits enabled, like the stock Debian one.</p><p>If you compile your own kernel, you can go one step further and set <a href="https://elixir.bootlin.com/linux/v6.6.17/source/security/lockdown/Kconfig#L33">the initial state of the Lockdown LSM to be more restrictive than none from the start</a>. This is exactly what we did for the Cloudflare production kernel:</p>
            <pre><code>ignat@dev:~$ grep LOCK_DOWN /boot/config-6.6.17-cloudflare-2024.2.9
# CONFIG_LOCK_DOWN_KERNEL_FORCE_NONE is not set
CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY=y
# CONFIG_LOCK_DOWN_KERNEL_FORCE_CONFIDENTIALITY is not set</code></pre>
            
    <div>
      <h2>Conclusion</h2>
      <a href="#conclusion">
        
      </a>
    </div>
    <p>In this post we reviewed some useful Linux kernel security configuration options we use at Cloudflare. This is only a small subset, and there are many more available and even more are being constantly developed, reviewed, and improved by the Linux kernel community. We hope that this post will shed some light on these security features and that, if you haven’t already, you may consider enabling them in your Linux systems.</p>
    <div>
      <h2>Watch on Cloudflare TV</h2>
      <a href="#watch-on-cloudflare-tv">
        
      </a>
    </div>
    <div>
  
</div><p>Tune in for more news, announcements and thought-provoking discussions! Don't miss the full <a href="https://cloudflare.tv/shows/security-week">Security Week hub page</a>.</p> ]]></content:encoded>
            <category><![CDATA[Security Week]]></category>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Kernel]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <category><![CDATA[Security]]></category>
            <guid isPermaLink="false">3ySkqS53T1nhzX61XzJFEG</guid>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
        <item>
            <title><![CDATA[The Linux Kernel Key Retention Service and why you should use it in your next application]]></title>
            <link>https://blog.cloudflare.com/the-linux-kernel-key-retention-service-and-why-you-should-use-it-in-your-next-application/</link>
            <pubDate>Mon, 28 Nov 2022 14:57:20 GMT</pubDate>
            <description><![CDATA[ Many leaks happen because of software bugs and security vulnerabilities. In this post we will learn how the Linux kernel can help protect cryptographic keys from a whole class of potential security vulnerabilities: memory access violations. ]]></description>
            <content:encoded><![CDATA[ <p><i></i></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2LKKOrcwGlRDUpSWhMkxe4/406961181fbe307f99573e8fbc13a0b0/unnamed-5.png" />
            
            </figure><p>We want our digital data to be safe. We want to visit websites, send bank details, type passwords, sign documents online, login into remote computers, encrypt data before storing it in databases and be sure that nobody can tamper with it. Cryptography can provide a high degree of data security, but we need to protect cryptographic keys.</p><p>At the same time, we can’t have our key written somewhere securely and just access it occasionally. Quite the opposite, it’s involved in every request where we do crypto-operations. If a site supports TLS, then the private key is used to establish each connection.</p><p>Unfortunately cryptographic keys sometimes leak and when it happens, it is a big problem. Many leaks happen because of software bugs and security vulnerabilities. In this post we will learn how the Linux kernel can help protect cryptographic keys from a whole class of potential security vulnerabilities: memory access violations.</p>
    <div>
      <h3>Memory access violations</h3>
      <a href="#memory-access-violations">
        
      </a>
    </div>
    <p>According to the <a href="https://www.nsa.gov/Press-Room/News-Highlights/Article/Article/3215760/nsa-releases-guidance-on-how-to-protect-against-software-memory-safety-issues/">NSA</a>, around 70% of vulnerabilities in both Microsoft's and Google's code were related to memory safety issues. One of the consequences of incorrect memory accesses is leaking security data (including cryptographic keys). Cryptographic keys are just some (mostly random) data stored in memory, so they may be subject to memory leaks like any other in-memory data. The below example shows how a cryptographic key may accidentally leak via stack memory reuse:</p><p>broken.c</p>
            <pre><code>#include &lt;stdio.h&gt;
#include &lt;stdint.h&gt;

static void encrypt(void)
{
    uint8_t key[] = "hunter2";
    printf("encrypting with super secret key: %s\n", key);
}

static void log_completion(void)
{
    /* oh no, we forgot to init the msg */
    char msg[8];
    printf("not important, just fyi: %s\n", msg);
}

int main(void)
{
    encrypt();
    /* notify that we're done */
    log_completion();
    return 0;
}</code></pre>
            <p>Compile and run our program:</p>
            <pre><code>$ gcc -o broken broken.c
$ ./broken 
encrypting with super secret key: hunter2
not important, just fyi: hunter2</code></pre>
            <p>Oops, we printed the secret key in the “fyi” logger instead of the intended log message! There are two problems with the code above:</p><ul><li><p>we didn’t securely destroy the key in our pseudo-encryption function (by overwriting the key data with zeroes, for example), when we finished using it</p></li><li><p>our buggy logging function has access to any memory within our process</p></li></ul><p>And while we can probably easily fix the first problem with some additional code, the second problem is the inherent result of how software runs inside the operating system.</p><p>Each process is given a block of contiguous virtual memory by the operating system. It allows the kernel to share limited computer resources among several simultaneously running processes. This approach is called <a href="https://en.wikipedia.org/wiki/Virtual_memory">virtual memory management</a>. Inside the virtual memory a process has its own address space and doesn’t have access to the memory of other processes, but it can access any memory within its address space. In our example we are interested in a piece of process memory called the stack.</p><p>The stack consists of stack frames. A stack frame is dynamically allocated space for the currently running function. It contains the function’s local variables, arguments and return address. When compiling a function the compiler calculates how much memory needs to be allocated and requests a stack frame of this size. Once a function finishes execution the stack frame is marked as free and can be used again. A stack frame is a logical block, it doesn’t provide any boundary checks, it’s not erased, just marked as free. Additionally, the virtual memory is a contiguous block of addresses. Both of these statements give the possibility for malware/buggy code to access data from anywhere within virtual memory.</p><p>The stack of our program <code>broken.c</code> will look like:</p><img src="https://imagedelivery.net/52R3oh4H-57qkVChwuo3Ag/3526edee-ce7e-4f98-a2bf-ff1efd2fc800/public" /><p>At the beginning we have a stack frame of the main function. Further, the <code>main()</code> function calls <code>encrypt()</code> which will be placed on the stack immediately below the <code>main()</code> (the code stack grows downwards). Inside <code>encrypt()</code> the compiler requests 8 bytes for the <code>key</code> variable (7 bytes of data + C-null character). When <code>encrypt()</code> finishes execution, the same memory addresses are taken by <code>log_completion()</code>. Inside the <code>log_completion()</code> the compiler allocates eight bytes for the <code>msg</code> variable. Accidentally, it was put on the stack at the same place where our private key was stored before. The memory for <code>msg</code> was only allocated, but not initialized, the data from the previous function left as is.</p><p>Additionally, to the code bugs, programming languages provide unsafe functions known for the safe-memory vulnerabilities. For example, for C such functions are <code>printf()</code>, <code>strcpy()</code>, <code>gets()</code>. The function <code>printf()</code> doesn’t check how many arguments must be passed to replace all placeholders in the format string. The function arguments are placed on the stack above the function stack frame, <code>printf()</code> fetches arguments according to the numbers and type of placeholders, easily going off its arguments and accessing data from the stack frame of the previous function.</p><p>The NSA advises us to use safety-memory languages like Python, Go, Rust. But will it completely protect us?</p><p>The Python compiler will definitely check boundaries in many cases for you and notify with an error:</p>
            <pre><code>&gt;&gt;&gt; print("x: {}, y: {}, {}".format(1, 2))
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
IndexError: Replacement index 2 out of range for positional args tuple</code></pre>
            <p>However, this is a quote from one of 36 (for now) <a href="https://www.cvedetails.com/vulnerability-list/vendor_id-10210/opov-1/Python.html">vulnerabilities</a>:</p><blockquote><p><i>Python 2.7.14 is vulnerable to a Heap-Buffer-Overflow as well as a Heap-Use-After-Free.</i></p></blockquote><p>Golang has its own list of <a href="https://www.cvedetails.com/vulnerability-list/vendor_id-14185/opov-1/Golang.html">overflow vulnerabilities</a>, and has an <a href="https://pkg.go.dev/unsafe">unsafe package</a>. The name of the package speaks for itself, usual rules and checks don’t work inside this package.</p>
    <div>
      <h3>Heartbleed</h3>
      <a href="#heartbleed">
        
      </a>
    </div>
    <p>In 2014, the Heartbleed bug was discovered. The (at the time) most used cryptography library OpenSSL leaked private keys. We experienced it <a href="/answering-the-critical-question-can-you-get-private-ssl-keys-using-heartbleed/">too</a>.</p>
    <div>
      <h3>Mitigation</h3>
      <a href="#mitigation">
        
      </a>
    </div>
    <p>So memory bugs are a fact of life, and we can’t really fully protect ourselves from them. But, given the fact that cryptographic keys are much more valuable than the other data, can we do better protecting the keys at least?</p><p>As we already said, a memory address space is normally associated with a process. And two different processes don’t share memory by default, so are naturally isolated from each other. Therefore, a potential memory bug in one of the processes will not accidentally leak a cryptographic key from another process. The security of ssh-agent builds on this principle. There are always two processes involved: a client/requester and the <a href="https://linux.die.net/man/1/ssh-agent">agent</a>.</p><blockquote><p><i>The agent will never send a private key over its request channel. Instead, operations that require a private key will be performed by the agent, and the result will be returned to the requester. This way, private keys are not exposed to clients using the agent.</i></p></blockquote><p>A requester is usually a network-facing process and/or processing untrusted input. Therefore, the requester is much more likely to be susceptible to memory-related vulnerabilities but in this scheme it would never have access to cryptographic keys (because keys reside in a separate process address space) and, thus, can never leak them.</p><p>At Cloudflare, we employ the same principle in <a href="/heartbleed-revisited/">Keyless SSL</a>. Customer private keys are stored in an isolated environment and protected from Internet-facing connections.</p>
    <div>
      <h3>Linux Kernel Key Retention Service</h3>
      <a href="#linux-kernel-key-retention-service">
        
      </a>
    </div>
    <p>The client/requester and agent approach provides better protection for secrets or cryptographic keys, but it brings some drawbacks:</p><ul><li><p>we need to develop and maintain two different programs instead of one</p></li><li><p>we also need to design a well-defined-interface for communication between the two processes</p></li><li><p>we need to implement the communication support between two processes (Unix sockets, shared memory, etc.)</p></li><li><p>we might need to authenticate and support ACLs between the processes, as we don’t want any requester on our system to be able to use our cryptographic keys stored inside the agent</p></li><li><p>we need to ensure the agent process is up and running, when working with the client/requester process</p></li></ul><p>What if we replace the agent process with the Linux kernel itself?</p><ul><li><p>it is already running on our system (otherwise our software would not work)</p></li><li><p>it has a well-defined interface for communication (system calls)</p></li><li><p>it can enforce various ACLs on kernel objects</p></li><li><p>and it runs in a separate address space!</p></li></ul><p>Fortunately, the <a href="https://www.kernel.org/doc/html/v6.0/security/keys/core.html">Linux Kernel Key Retention Service</a> can perform all the functions of a typical agent process and probably even more!</p><p>Initially it was designed for kernel services like dm-crypt/ecryptfs, but later was opened to use by userspace programs. It gives us some advantages:</p><ul><li><p>the keys are stored outside the process address space</p></li><li><p>the well-defined-interface and the communication layer is implemented via syscalls</p></li><li><p>the keys are kernel objects and so have associated permissions and ACLs</p></li><li><p>the keys lifecycle can be implicitly bound to the process lifecycle</p></li></ul><p>The Linux Kernel Key Retention Service operates with two types of entities: keys and keyrings, where a keyring is a key of a special type. If we put it into analogy with files and directories, we can say a key is a file and a keyring is a directory. Moreover, they represent a key hierarchy similar to a filesystem tree hierarchy: keyrings reference keys and other keyrings, but only keys can hold the actual cryptographic material similar to files holding the actual data.</p><p>Keys have types. The type of key determines which operations can be performed over the keys. For example, keys of user and logon types can hold arbitrary blobs of data, but logon keys can never be read back into userspace, they are exclusively used by the in-kernel services.</p><p>For the purposes of using the kernel instead of an agent process the most interesting type of keys is the <a href="https://man7.org/linux/man-pages/man7/asymmetric.7.html">asymmetric type</a>. It can hold a private key inside the kernel and provides the ability for the allowed applications to either decrypt or sign some data with the key. Currently, only RSA keys are supported, but work is underway to add <a href="https://www.cloudflare.com/learning/dns/dnssec/ecdsa-and-dnssec/">ECDSA key support</a>.</p><p>While keys are responsible for safeguarding the cryptographic material inside the kernel, keyrings determine key lifetime and shared access. In its simplest form, when a particular keyring is destroyed, all the keys that are linked only to that keyring are securely destroyed as well. We can create custom keyrings manually, but probably one the most powerful features of the service are the “special keyrings”.</p><p>These keyrings are created implicitly by the kernel and their lifetime is bound to the lifetime of a different kernel object, like a process or a user. (Currently there are four categories of “implicit” <a href="https://man7.org/linux/man-pages/man7/keyrings.7.html">keyrings</a>), but for the purposes of this post we’re interested in two most widely used ones: process keyrings and user keyrings.</p><p>User keyring lifetime is bound to the existence of a particular user and this keyring is shared between all the processes of the same UID. Thus, one process, for example, can store a key in a user keyring and another process running as the same user can retrieve/use the key. When the UID is removed from the system, all the keys (and other keyrings) under the associated user keyring will be securely destroyed by the kernel.</p><p>Process keyrings are bound to some processes and may be of three types differing in semantics: process, thread and session. A process keyring is bound and private to a particular process. Thus, any code within the process can store/use keys in the keyring, but other processes (even with the same user id or child processes) cannot get access. And when the process dies, the keyring and the associated keys are securely destroyed. Besides the advantage of storing our secrets/keys in an isolated address space, the process keyring gives us the guarantee that the keys will be destroyed regardless of the reason for the process termination: even if our application crashed hard without being given an opportunity to execute any clean up code - our keys will still be securely destroyed by the kernel.</p><p>A thread keyring is similar to a process keyring, but it is private and bound to a particular thread. For example, we can build a multithreaded web server, which can serve TLS connections using multiple private keys, and we can be sure that connections/code in one thread can never use a private key, which is associated with another thread (for example, serving a different domain name).</p><p>A session keyring makes its keys available to the current process and all its children. It is destroyed when the topmost process terminates and child processes can store/access keys, while the topmost process exists. It is mostly useful in shell and interactive environments, when we employ the <a href="https://man7.org/linux/man-pages/man1/keyctl.1.html">keyctl tool</a> to access the Linux Kernel Key Retention Service, rather than using the kernel system call interface. In the shell, we generally can’t use the process keyring as every executed command creates a new process. Thus, if we add a key to the process keyring from the command line - that key will be immediately destroyed, because the “adding” process terminates, when the command finishes executing. Let’s actually confirm this with <code>[bpftrace](https://github.com/iovisor/bpftrace)</code>.</p><p>In one terminal we will trace the <code>[user_destroy](https://elixir.bootlin.com/linux/v5.19.17/source/security/keys/user_defined.c#L146)</code> function, which is responsible for deleting a user key:</p>
            <pre><code>$ sudo bpftrace -e 'kprobe:user_destroy { printf("destroying key %d\n", ((struct key *)arg0)-&gt;serial) }'
Att</code></pre>
            <p>And in another terminal let’s try to add a key to the process keyring:</p>
            <pre><code>$ keyctl add user mykey hunter2 @p
742524855</code></pre>
            <p>Going back to the first terminal we can immediately see:</p>
            <pre><code>…
Attaching 1 probe...
destroying key 742524855</code></pre>
            <p>And we can confirm the key is not available by trying to access it:</p>
            <pre><code>$ keyctl print 742524855
keyctl_read_alloc: Required key not available</code></pre>
            <p>So in the above example, the key “mykey” was added to the process keyring of the subshell executing <code>keyctl add user mykey hunter2 @p</code>. But since the subshell process terminated the moment the command was executed, both its process keyring and the added key were destroyed.</p><p>Instead, the session keyring allows our interactive commands to add keys to our current shell environment and subsequent commands to consume them. The keys will still be securely destroyed, when our main shell process terminates (likely, when we log out from the system).</p><p>So by selecting the appropriate keyring type we can ensure the keys will be securely destroyed, when not needed. Even if the application crashes! This is a very brief introduction, but it will allow you to play with our examples, for the whole context, please, reach the <a href="https://www.kernel.org/doc/html/v5.8/security/keys/core.html">official documentation</a>.</p>
    <div>
      <h3>Replacing the ssh-agent with the Linux Kernel Key Retention Service</h3>
      <a href="#replacing-the-ssh-agent-with-the-linux-kernel-key-retention-service">
        
      </a>
    </div>
    <p>We gave a long description of how we can replace two isolated processes with the Linux Kernel Retention Service. It’s time to put our words into code. We talked about ssh-agent as well, so it will be a good exercise to replace our private key stored in memory of the agent with an in-kernel one. We picked the most popular SSH implementation <a href="https://github.com/openssh/openssh-portable.git">OpenSSH</a> as our target.</p><p>Some minor changes need to be added to the code to add functionality to retrieve a key from the kernel:</p><p>openssh.patch</p>
            <pre><code>diff --git a/ssh-rsa.c b/ssh-rsa.c
index 6516ddc1..797739bb 100644
--- a/ssh-rsa.c
+++ b/ssh-rsa.c
@@ -26,6 +26,7 @@
 
 #include &lt;stdarg.h&gt;
 #include &lt;string.h&gt;
+#include &lt;stdbool.h&gt;
 
 #include "sshbuf.h"
 #include "compat.h"
@@ -63,6 +64,7 @@ ssh_rsa_cleanup(struct sshkey *k)
 {
 	RSA_free(k-&gt;rsa);
 	k-&gt;rsa = NULL;
+	k-&gt;serial = 0;
 }
 
 static int
@@ -220,9 +222,14 @@ ssh_rsa_deserialize_private(const char *ktype, struct sshbuf *b,
 	int r;
 	BIGNUM *rsa_n = NULL, *rsa_e = NULL, *rsa_d = NULL;
 	BIGNUM *rsa_iqmp = NULL, *rsa_p = NULL, *rsa_q = NULL;
+	bool is_keyring = (strncmp(ktype, "ssh-rsa-keyring", strlen("ssh-rsa-keyring")) == 0);
 
+	if (is_keyring) {
+		if ((r = ssh_rsa_deserialize_public(ktype, b, key)) != 0)
+			goto out;
+	}
 	/* Note: can't reuse ssh_rsa_deserialize_public: e, n vs. n, e */
-	if (!sshkey_is_cert(key)) {
+	else if (!sshkey_is_cert(key)) {
 		if ((r = sshbuf_get_bignum2(b, &amp;rsa_n)) != 0 ||
 		    (r = sshbuf_get_bignum2(b, &amp;rsa_e)) != 0)
 			goto out;
@@ -232,28 +239,46 @@ ssh_rsa_deserialize_private(const char *ktype, struct sshbuf *b,
 		}
 		rsa_n = rsa_e = NULL; /* transferred */
 	}
-	if ((r = sshbuf_get_bignum2(b, &amp;rsa_d)) != 0 ||
-	    (r = sshbuf_get_bignum2(b, &amp;rsa_iqmp)) != 0 ||
-	    (r = sshbuf_get_bignum2(b, &amp;rsa_p)) != 0 ||
-	    (r = sshbuf_get_bignum2(b, &amp;rsa_q)) != 0)
-		goto out;
-	if (!RSA_set0_key(key-&gt;rsa, NULL, NULL, rsa_d)) {
-		r = SSH_ERR_LIBCRYPTO_ERROR;
-		goto out;
-	}
-	rsa_d = NULL; /* transferred */
-	if (!RSA_set0_factors(key-&gt;rsa, rsa_p, rsa_q)) {
-		r = SSH_ERR_LIBCRYPTO_ERROR;
-		goto out;
-	}
-	rsa_p = rsa_q = NULL; /* transferred */
 	if ((r = sshkey_check_rsa_length(key, 0)) != 0)
 		goto out;
-	if ((r = ssh_rsa_complete_crt_parameters(key, rsa_iqmp)) != 0)
-		goto out;
-	if (RSA_blinding_on(key-&gt;rsa, NULL) != 1) {
-		r = SSH_ERR_LIBCRYPTO_ERROR;
-		goto out;
+
+	if (is_keyring) {
+		char *name;
+		size_t len;
+
+		if ((r = sshbuf_get_cstring(b, &amp;name, &amp;len)) != 0)
+			goto out;
+
+		key-&gt;serial = request_key("asymmetric", name, NULL, KEY_SPEC_PROCESS_KEYRING);
+		free(name);
+
+		if (key-&gt;serial == -1) {
+			key-&gt;serial = 0;
+			r = SSH_ERR_KEY_NOT_FOUND;
+			goto out;
+		}
+	} else {
+		if ((r = sshbuf_get_bignum2(b, &amp;rsa_d)) != 0 ||
+			(r = sshbuf_get_bignum2(b, &amp;rsa_iqmp)) != 0 ||
+			(r = sshbuf_get_bignum2(b, &amp;rsa_p)) != 0 ||
+			(r = sshbuf_get_bignum2(b, &amp;rsa_q)) != 0)
+			goto out;
+		if (!RSA_set0_key(key-&gt;rsa, NULL, NULL, rsa_d)) {
+			r = SSH_ERR_LIBCRYPTO_ERROR;
+			goto out;
+		}
+		rsa_d = NULL; /* transferred */
+		if (!RSA_set0_factors(key-&gt;rsa, rsa_p, rsa_q)) {
+			r = SSH_ERR_LIBCRYPTO_ERROR;
+			goto out;
+		}
+		rsa_p = rsa_q = NULL; /* transferred */
+		if ((r = ssh_rsa_complete_crt_parameters(key, rsa_iqmp)) != 0)
+			goto out;
+		if (RSA_blinding_on(key-&gt;rsa, NULL) != 1) {
+			r = SSH_ERR_LIBCRYPTO_ERROR;
+			goto out;
+		}
 	}
 	/* success */
 	r = 0;
@@ -333,6 +358,21 @@ rsa_hash_alg_nid(int type)
 	}
 }
 
+static const char *
+rsa_hash_alg_keyctl_info(int type)
+{
+	switch (type) {
+	case SSH_DIGEST_SHA1:
+		return "enc=pkcs1 hash=sha1";
+	case SSH_DIGEST_SHA256:
+		return "enc=pkcs1 hash=sha256";
+	case SSH_DIGEST_SHA512:
+		return "enc=pkcs1 hash=sha512";
+	default:
+		return NULL;
+	}
+}
+
 int
 ssh_rsa_complete_crt_parameters(struct sshkey *key, const BIGNUM *iqmp)
 {
@@ -433,7 +473,14 @@ ssh_rsa_sign(struct sshkey *key,
 		goto out;
 	}
 
-	if (RSA_sign(nid, digest, hlen, sig, &amp;len, key-&gt;rsa) != 1) {
+	if (key-&gt;serial &gt; 0) {
+		len = keyctl_pkey_sign(key-&gt;serial, rsa_hash_alg_keyctl_info(hash_alg), digest, hlen, sig, slen);
+		if ((long)len == -1) {
+			ret = SSH_ERR_LIBCRYPTO_ERROR;
+			goto out;
+		}
+	}
+	else if (RSA_sign(nid, digest, hlen, sig, &amp;len, key-&gt;rsa) != 1) {
 		ret = SSH_ERR_LIBCRYPTO_ERROR;
 		goto out;
 	}
@@ -705,6 +752,18 @@ const struct sshkey_impl sshkey_rsa_impl = {
 	/* .funcs = */		&amp;sshkey_rsa_funcs,
 };
 
+const struct sshkey_impl sshkey_rsa_keyring_impl = {
+	/* .name = */		"ssh-rsa-keyring",
+	/* .shortname = */	"RSA",
+	/* .sigalg = */		NULL,
+	/* .type = */		KEY_RSA,
+	/* .nid = */		0,
+	/* .cert = */		0,
+	/* .sigonly = */	0,
+	/* .keybits = */	0,
+	/* .funcs = */		&amp;sshkey_rsa_funcs,
+};
+
 const struct sshkey_impl sshkey_rsa_cert_impl = {
 	/* .name = */		"ssh-rsa-cert-v01@openssh.com",
 	/* .shortname = */	"RSA-CERT",
diff --git a/sshkey.c b/sshkey.c
index 43712253..3524ad37 100644
--- a/sshkey.c
+++ b/sshkey.c
@@ -115,6 +115,7 @@ extern const struct sshkey_impl sshkey_ecdsa_nistp521_cert_impl;
 #  endif /* OPENSSL_HAS_NISTP521 */
 # endif /* OPENSSL_HAS_ECC */
 extern const struct sshkey_impl sshkey_rsa_impl;
+extern const struct sshkey_impl sshkey_rsa_keyring_impl;
 extern const struct sshkey_impl sshkey_rsa_cert_impl;
 extern const struct sshkey_impl sshkey_rsa_sha256_impl;
 extern const struct sshkey_impl sshkey_rsa_sha256_cert_impl;
@@ -154,6 +155,7 @@ const struct sshkey_impl * const keyimpls[] = {
 	&amp;sshkey_dss_impl,
 	&amp;sshkey_dsa_cert_impl,
 	&amp;sshkey_rsa_impl,
+	&amp;sshkey_rsa_keyring_impl,
 	&amp;sshkey_rsa_cert_impl,
 	&amp;sshkey_rsa_sha256_impl,
 	&amp;sshkey_rsa_sha256_cert_impl,
diff --git a/sshkey.h b/sshkey.h
index 771c4bce..a7ae45f6 100644
--- a/sshkey.h
+++ b/sshkey.h
@@ -29,6 +29,7 @@
 #include &lt;sys/types.h&gt;
 
 #ifdef WITH_OPENSSL
+#include &lt;keyutils.h&gt;
 #include &lt;openssl/rsa.h&gt;
 #include &lt;openssl/dsa.h&gt;
 # ifdef OPENSSL_HAS_ECC
@@ -153,6 +154,7 @@ struct sshkey {
 	size_t	shielded_len;
 	u_char	*shield_prekey;
 	size_t	shield_prekey_len;
+	key_serial_t serial;
 };
 
 #define	ED25519_SK_SZ	crypto_sign_ed25519_SECRETKEYBYTES</code></pre>
            <p>We need to download and patch OpenSSH from the latest git as the above patch won’t work on the latest release (<code>V_9_1_P1</code> at the time of this writing):</p>
            <pre><code>$ git clone https://github.com/openssh/openssh-portable.git
…
$ cd openssl-portable
$ $ patch -p1 &lt; ../openssh.patch
patching file ssh-rsa.c
patching file sshkey.c
patching file sshkey.h</code></pre>
            <p>Now compile and build the patched OpenSSH</p>
            <pre><code>$ autoreconf
$ ./configure --with-libs=-lkeyutils --disable-pkcs11
…
$ make
…</code></pre>
            <p>Note that we instruct the build system to additionally link with <code>[libkeyutils](https://man7.org/linux/man-pages/man3/keyctl.3.html)</code>, which provides convenient wrappers to access the Linux Kernel Key Retention Service. Additionally, we had to disable PKCS11 support as the code has a function with the same name as in `libkeyutils`, so there is a naming conflict. There might be a better fix for this, but it is out of scope for this post.</p><p>Now that we have the patched OpenSSH - let’s test it. Firstly, we need to generate a new SSH RSA key that we will use to access the system. Because the Linux kernel only supports private keys in the PKCS8 format, we’ll use it from the start (instead of the default OpenSSH format):</p>
            <pre><code>$ ./ssh-keygen -b 4096 -m PKCS8
Generating public/private rsa key pair.
…</code></pre>
            <p>Normally, we would be using `ssh-add` to add this key to our ssh agent. In our case we need to use a replacement script, which would add the key to our current session keyring:</p><p>ssh-add-keyring.sh</p>
            <pre><code>#/bin/bash -e

in=$1
key_desc=$2
keyring=$3

in_pub=$in.pub
key=$(mktemp)
out="${in}_keyring"

function finish {
    rm -rf $key
}
trap finish EXIT

# https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.key
# null-terminanted openssh-key-v1
printf 'openssh-key-v1\0' &gt; $key
# cipher: none
echo '00000004' | xxd -r -p &gt;&gt; $key
echo -n 'none' &gt;&gt; $key
# kdf: none
echo '00000004' | xxd -r -p &gt;&gt; $key
echo -n 'none' &gt;&gt; $key
# no kdf options
echo '00000000' | xxd -r -p &gt;&gt; $key
# one key in the blob
echo '00000001' | xxd -r -p &gt;&gt; $key

# grab the hex public key without the (00000007 || ssh-rsa) preamble
pub_key=$(awk '{ print $2 }' $in_pub | base64 -d | xxd -s 11 -p | tr -d '\n')
# size of the following public key with the (0000000f || ssh-rsa-keyring) preamble
printf '%08x' $(( ${#pub_key} / 2 + 19 )) | xxd -r -p &gt;&gt; $key
# preamble for the public key
# ssh-rsa-keyring in prepended with length of the string
echo '0000000f' | xxd -r -p &gt;&gt; $key
echo -n 'ssh-rsa-keyring' &gt;&gt; $key
# the public key itself
echo $pub_key | xxd -r -p &gt;&gt; $key

# the private key is just a key description in the Linux keyring
# ssh will use it to actually find the corresponding key serial
# grab the comment from the public key
comment=$(awk '{ print $3 }' $in_pub)
# so the total size of the private key is
# two times the same 4 byte int +
# (0000000f || ssh-rsa-keyring) preamble +
# a copy of the public key (without preamble) +
# (size || key_desc) +
# (size || comment )
priv_sz=$(( 8 + 19 + ${#pub_key} / 2 + 4 + ${#key_desc} + 4 + ${#comment} ))
# we need to pad the size to 8 bytes
pad=$(( 8 - $(( priv_sz % 8 )) ))
# so, total private key size
printf '%08x' $(( $priv_sz + $pad )) | xxd -r -p &gt;&gt; $key
# repeated 4-byte int
echo '0102030401020304' | xxd -r -p &gt;&gt; $key
# preamble for the private key
echo '0000000f' | xxd -r -p &gt;&gt; $key
echo -n 'ssh-rsa-keyring' &gt;&gt; $key
# public key
echo $pub_key | xxd -r -p &gt;&gt; $key
# private key description in the keyring
printf '%08x' ${#key_desc} | xxd -r -p &gt;&gt; $key
echo -n $key_desc &gt;&gt; $key
# comment
printf '%08x' ${#comment} | xxd -r -p &gt;&gt; $key
echo -n $comment &gt;&gt; $key
# padding
for (( i = 1; i &lt;= $pad; i++ )); do
    echo 0$i | xxd -r -p &gt;&gt; $key
done

echo '-----BEGIN OPENSSH PRIVATE KEY-----' &gt; $out
base64 $key &gt;&gt; $out
echo '-----END OPENSSH PRIVATE KEY-----' &gt;&gt; $out
chmod 600 $out

# load the PKCS8 private key into the designated keyring
openssl pkcs8 -in $in -topk8 -outform DER -nocrypt | keyctl padd asymmetric $key_desc $keyring
</code></pre>
            <p>Depending on how our kernel was compiled, we might also need to load some kernel modules for asymmetric private key support:</p>
            <pre><code>$ sudo modprobe pkcs8_key_parser
$ ./ssh-add-keyring.sh ~/.ssh/id_rsa myssh @s
Enter pass phrase for ~/.ssh/id_rsa:
723263309</code></pre>
            <p>Finally, our private ssh key is added to the current session keyring with the name “myssh”. In addition, the <code>ssh-add-keyring.sh</code> will create a pseudo-private key file in <code>~/.ssh/id_rsa_keyring</code>, which needs to be passed to the main <code>ssh</code> process. It is a pseudo-private key, because it doesn’t have any sensitive cryptographic material. Instead, it only has the “myssh” identifier in a native OpenSSH format. If we use multiple SSH keys, we have to tell the main <code>ssh</code> process somehow which in-kernel key name should be requested from the system.</p><p>Before we start testing it, let’s make sure our SSH server (running locally) will accept the newly generated key as a valid authentication:</p>
            <pre><code>$ cat ~/.ssh/id_rsa.pub &gt;&gt; ~/.ssh/authorized_keys</code></pre>
            <p>Now we can try to SSH into the system:</p>
            <pre><code>$ SSH_AUTH_SOCK="" ./ssh -i ~/.ssh/id_rsa_keyring localhost
The authenticity of host 'localhost (::1)' can't be established.
ED25519 key fingerprint is SHA256:3zk7Z3i9qZZrSdHvBp2aUYtxHACmZNeLLEqsXltynAY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'localhost' (ED25519) to the list of known hosts.
Linux dev 5.15.79-cloudflare-2022.11.6 #1 SMP Mon Sep 27 00:00:00 UTC 2010 x86_64
…</code></pre>
            <p>It worked! Notice that we’re resetting the `SSH_AUTH_SOCK` environment variable to make sure we don’t use any keys from an ssh-agent running on the system. Still the login flow does not request any password for our private key, the key itself is resident of the kernel address space, and we reference it using its serial for signature operations.</p>
    <div>
      <h3>User or session keyring?</h3>
      <a href="#user-or-session-keyring">
        
      </a>
    </div>
    <p>In the example above, we set up our SSH private key into the session keyring. We can check if it is there:</p>
            <pre><code>$ keyctl show
Session Keyring
 577779279 --alswrv   1000  1000  keyring: _ses
 846694921 --alswrv   1000 65534   \_ keyring: _uid.1000
 723263309 --als--v   1000  1000   \_ asymmetric: myssh</code></pre>
            <p>We might have used user keyring as well. What is the difference? Currently, the “myssh” key lifetime is limited to the current login session. That is, if we log out and login again, the key will be gone, and we would have to run the <code>ssh-add-keyring.sh</code> script again. Similarly, if we log in to a second terminal, we won’t see this key:</p>
            <pre><code>$ keyctl show
Session Keyring
 333158329 --alswrv   1000  1000  keyring: _ses
 846694921 --alswrv   1000 65534   \_ keyring: _uid.1000</code></pre>
            <p>Notice that the serial number of the session keyring <code>_ses</code> in the second terminal is different. A new keyring was created and  “myssh” key along with the previous session keyring doesn’t exist anymore:</p>
            <pre><code>$ SSH_AUTH_SOCK="" ./ssh -i ~/.ssh/id_rsa_keyring localhost
Load key "/home/ignat/.ssh/id_rsa_keyring": key not found
…</code></pre>
            <p>If instead we tell <code>ssh-add-keyring.sh</code> to load the private key into the user keyring (replace <code>@s</code> with <code>@u</code> in the command line parameters), it will be available and accessible from both login sessions. In this case, during logout and re-login, the same key will be presented. Although, this has a security downside - any process running as our user id will be able to access and use the key.</p>
    <div>
      <h3>Summary</h3>
      <a href="#summary">
        
      </a>
    </div>
    <p>In this post we learned about one of the most common ways that data, including highly valuable cryptographic keys, can leak. We talked about some real examples, which impacted many users around the world, including Cloudflare. Finally, we learned how the Linux Kernel Retention Service can help us to protect our cryptographic keys and secrets.</p><p>We also introduced a working patch for OpenSSH to use this cool feature of the Linux kernel, so you can easily try it yourself. There are still many Linux Kernel Key Retention Service features left untold, which might be a topic for another blog post. Stay tuned!</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Kernel]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">1w40uuzCCDLItmJBNEkbkq</guid>
            <dc:creator>Oxana Kharitonova</dc:creator>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
        <item>
            <title><![CDATA[How to execute an object file: Part 3]]></title>
            <link>https://blog.cloudflare.com/how-to-execute-an-object-file-part-3/</link>
            <pubDate>Fri, 10 Sep 2021 12:58:20 GMT</pubDate>
            <description><![CDATA[ Continue learning how to import and execute code from an object file. In this part we will handle external library dependencies. ]]></description>
            <content:encoded><![CDATA[ <p></p>
    <div>
      <h2>Dealing with external libraries</h2>
      <a href="#dealing-with-external-libraries">
        
      </a>
    </div>
    <p>In the <a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-2/">part 2 of our series</a> we learned how to process relocations in object files in order to properly wire up internal dependencies in the code. In this post we will look into what happens if the code has external dependencies — that is, it tries to call functions from external libraries. As before, we will be building upon <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2021-03-obj-file/2">the code from part 2</a>. Let's add another function to our toy object file:</p><p><i>obj.c</i>:</p>
            <pre><code>#include &lt;stdio.h&gt;
 
...
 
void say_hello(void)
{
    puts("Hello, world!");
}</code></pre>
            <p>In the above scenario our <code>say_hello</code> function now depends on the <code>puts</code> <a href="https://man7.org/linux/man-pages/man3/puts.3.html">function from the C standard library</a>. To try it out we also need to modify our <code>loader</code> to import the new function and execute it:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void execute_funcs(void)
{
    /* pointers to imported functions */
    int (*add5)(int);
    int (*add10)(int);
    const char *(*get_hello)(void);
    int (*get_var)(void);
    void (*set_var)(int num);
    void (*say_hello)(void);
 
...
 
    say_hello = lookup_function("say_hello");
    if (!say_hello) {
        fputs("Failed to find say_hello function\n", stderr);
        exit(ENOENT);
    }
 
    puts("Executing say_hello...");
    say_hello();
}
...</code></pre>
            <p>Let's run it:</p>
            <pre><code>$ gcc -c obj.c
$ gcc -o loader loader.c
$ ./loader
No runtime base address for section</code></pre>
            <p>Seems something went wrong when the <code>loader</code> tried to process relocations, so let's check the relocations table:</p>
            <pre><code>$ readelf --relocs obj.o
 
Relocation section '.rela.text' at offset 0x3c8 contains 7 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000a00000004 R_X86_64_PLT32    0000000000000000 add5 - 4
00000000002d  000a00000004 R_X86_64_PLT32    0000000000000000 add5 - 4
00000000003a  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4
000000000046  000300000002 R_X86_64_PC32     0000000000000000 .data - 4
000000000058  000300000002 R_X86_64_PC32     0000000000000000 .data - 4
000000000066  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4
00000000006b  001100000004 R_X86_64_PLT32    0000000000000000 puts - 4
...</code></pre>
            <p>The compiler generated a relocation for the <code>puts</code> invocation. The relocation type is <code>R_X86_64_PLT32</code> and our <code>loader</code> already knows how to process these, so the problem is elsewhere. The above entry shows that the relocation references 17th entry (<code>0x11</code> in hex) in the symbol table, so let's check that:</p>
            <pre><code>$ readelf --symbols obj.o
 
Symbol table '.symtab' contains 18 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS obj.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    3 var
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
    10: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 add5
    11: 000000000000000f    36 FUNC    GLOBAL DEFAULT    1 add10
    12: 0000000000000033    13 FUNC    GLOBAL DEFAULT    1 get_hello
    13: 0000000000000040    12 FUNC    GLOBAL DEFAULT    1 get_var
    14: 000000000000004c    19 FUNC    GLOBAL DEFAULT    1 set_var
    15: 000000000000005f    19 FUNC    GLOBAL DEFAULT    1 say_hello
    16: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND _GLOBAL_OFFSET_TABLE_
    17: 0000000000000000     0 NOTYPE  GLOBAL DEFAULT  UND puts</code></pre>
            <p>Oh! The section index for the <code>puts</code> function is <code>UND</code> (essentially <code>0</code> in the code), which makes total sense: unlike previous symbols, <code>puts</code> is an external dependency, and it is not implemented in our <code>obj.o</code> file. Therefore, it can't be a part of any section within <code>obj.o</code>.So how do we resolve this relocation? We need to somehow point the code to jump to a <code>puts</code> implementation. Our <code>loader</code> actually already has access to the C library <code>puts</code> function (because it is written in C and we've used <code>puts</code> in the <code>loader</code> code itself already), but technically it doesn't have to be the C library <code>puts</code>, just some <code>puts</code> implementation. For completeness, let's implement our own custom <code>puts</code> function in the <code>loader</code>, which is just a decorator around the C library <code>puts</code>:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
/* external dependencies for obj.o */
static int my_puts(const char *s)
{
    puts("my_puts executed");
    return puts(s);
}
...</code></pre>
            <p>Now that we have a <code>puts</code> implementation (and thus its runtime address) we should just write logic in the <code>loader</code> to resolve the relocation by instructing the code to jump to the correct function. However, there is one complication: in <a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-2/">part 2 of our series</a>, when we processed relocations for constants and global variables, we learned we're mostly dealing with 32-bit relative relocations and that the code or data we're referencing needs to be no more than 2147483647 (<code>0x7fffffff</code> in hex) bytes away from the relocation itself. <code>R_X86_64_PLT32</code> is also a 32-bit relative relocation, so it has the same requirements, but unfortunately we can't reuse the trick from <a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-2/">part 2</a> as our <code>my_puts</code> function is part of the <code>loader</code> itself and we don't have control over where in the address space the operating system places the <code>loader</code> code.</p><p>Luckily, we don't have to come up with any new solutions and can just borrow the approach used in shared libraries.</p>
    <div>
      <h3>Exploring PLT/GOT</h3>
      <a href="#exploring-plt-got">
        
      </a>
    </div>
    <p>Real world ELF executables and shared libraries have the same problem: often executables have dependencies on shared libraries and shared libraries have dependencies on other shared libraries. And all of the different pieces of a complete runtime program may be mapped to random ranges in the process address space. When a shared library or an ELF executable is linked together, the linker enumerates all the external references and creates two or more additional sections (for a refresher on ELF sections check out the <a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-1/">part 1 of our series</a>) in the ELF file. The two mandatory ones are <a href="https://refspecs.linuxfoundation.org/ELF/zSeries/lzsabi0_zSeries/x2251.html">the Procedure Linkage Table (PLT) and the Global Offset Table (GOT)</a>.</p><p>We will not deep-dive into specifics of the standard PLT/GOT implementation as there are many other great resources online, but in a nutshell PLT/GOT is just a jumptable for external code. At the linking stage the linker resolves all external 32-bit relative relocations with respect to a locally generated PLT/GOT table. It can do that, because this table would become part of the final ELF file itself, so it will be "close" to the main code, when the file is mapped into memory at runtime. Later, at runtime <a href="https://man7.org/linux/man-pages/man8/ld.so.8.html">the dynamic loader</a> populates PLT/GOT tables for every loaded ELF file (both the executable and the shared libraries) with the runtime addresses of all the dependencies. Eventually, when the program code calls some external library function, the CPU "jumps" through the local PLT/GOT table to the final code:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7xHrWlui04cqRxeurzvRpd/2d4b41f80468fde55541c19933d924c1/image2-5.png" />
            
            </figure><p>Why do we need two ELF sections to implement one jumptable you may ask? Well, because real world PLT/GOT is a bit more complex than described above. Turns out resolving all external references at runtime may significantly slow down program startup time, so symbol resolution is implemented via a "lazy approach": a reference is resolved by <a href="https://man7.org/linux/man-pages/man8/ld.so.8.html">the dynamic loader</a> only when the code actually tries to call a particular function. If the main application code never calls a library function, that reference will never be resolved.</p>
    <div>
      <h3>Implementing a simplified PLT/GOT</h3>
      <a href="#implementing-a-simplified-plt-got">
        
      </a>
    </div>
    <p>For learning and demonstrative purposes though we will not be reimplementing a full-blown PLT/GOT with lazy resolution, but a simple jumptable, which resolves external references when the object file is loaded and parsed. First of all we need to know the size of the table: for ELF executables and shared libraries the linker will count the external references at link stage and create appropriately sized PLT and GOT sections. Because we are dealing with raw object files we would have to do another pass over the <code>.rela.text</code> section and count all the relocations, which point to an entry in the symbol table with undefined section index (or <code>0</code> in code). Let's add a function for this and store the number of external references in a global variable:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
/* number of external symbols in the symbol table */
static int num_ext_symbols = 0;
...
static void count_external_symbols(void)
{
    const Elf64_Shdr *rela_text_hdr = lookup_section(".rela.text");
    if (!rela_text_hdr) {
        fputs("Failed to find .rela.text\n", stderr);
        exit(ENOEXEC);
    }
 
    int num_relocations = rela_text_hdr-&gt;sh_size / rela_text_hdr-&gt;sh_entsize;
    const Elf64_Rela *relocations = (Elf64_Rela *)(obj.base + rela_text_hdr-&gt;sh_offset);
 
    for (int i = 0; i &lt; num_relocations; i++) {
        int symbol_idx = ELF64_R_SYM(relocations[i].r_info);
 
        /* if there is no section associated with a symbol, it is probably
         * an external reference */
        if (symbols[symbol_idx].st_shndx == SHN_UNDEF)
            num_ext_symbols++;
    }
}
...</code></pre>
            <p>This function is very similar to our <code>do_text_relocations</code> function. Only instead of actually performing relocations it just counts the number of external symbol references.</p><p>Next we need to decide the actual size in bytes for our jumptable. <code>num_ext_symbols</code> has the number of external symbol references in the object file, but how many bytes per symbol to allocate? To figure this out we need to design our jumptable format. As we established above, in its simple form our jumptable should be just a collection of unconditional CPU jump instructions — one for each external symbol. However, unfortunately modern x64 CPU architecture <a href="https://www.felixcloutier.com/x86/jmp">does not provide a jump instruction</a>, where an address pointer can be a direct operand. Instead, the jump address needs to be stored in memory somewhere "close" — that is within 32-bit offset — and the offset is the actual operand. So, for each external symbol we need to store the jump address (64 bits or 8 bytes on a 64-bit CPU system) and the actual jump instruction with an offset operand (<a href="https://www.felixcloutier.com/x86/jmp">6 bytes for x64 architecture</a>). We can represent an entry in our jumptable with the following C structure:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
struct ext_jump {
    /* address to jump to */
    uint8_t *addr;
    /* unconditional x64 JMP instruction */
    /* should always be {0xff, 0x25, 0xf2, 0xff, 0xff, 0xff} */
    /* so it would jump to an address stored at addr above */
    uint8_t instr[6];
};
 
struct ext_jump *jumptable;
...</code></pre>
            <p>We've also added a global variable to store the base address of the jumptable, which will be allocated later. Notice that with the above approach the actual jump instruction will always be constant for every external symbol. Since we allocate a dedicated entry for each external symbol with this structure, the <code>addr</code> member would always be at the same offset from the end of the jump instruction in <code>instr</code>: <code>-14</code> bytes or <code>0xfffffff2</code> in hex for a 32-bit operand. So <code>instr</code> will always be <code>{0xff, 0x25, 0xf2, 0xff, 0xff, 0xff}</code>: <code>0xff</code> and <code>0x25</code> is the encoding of the x64 jump instruction and its modifier and <code>0xfffffff2</code> is the operand offset in little-endian format.</p><p>Now that we have defined the entry format for our jumptable, we can allocate and populate it when parsing the object file. First of all, let's not forget to call our new <code>count_external_symbols</code> function from the <code>parse_obj</code> to populate <code>num_ext_symbols</code> (it has to be done before we allocate the jumptable):</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void parse_obj(void)
{
...
 
    count_external_symbols();
 
    /* allocate memory for `.text`, `.data` and `.rodata` copies rounding up each section to whole pages */
    text_runtime_base = mmap(NULL, page_align(text_hdr-&gt;sh_size)...
...
}</code></pre>
            <p>Next we need to allocate memory for the jumptable and store the pointer in the <code>jumptable</code> global variable for later use. Just a reminder that in order to resolve 32-bit relocations from the <code>.text</code> section to this table, it has to be "close" in memory to the main code. So we need to allocate it in the same <code>mmap</code> call as the rest of the object sections. Since we defined the table's entry format in <code>struct ext_jump</code> and have <code>num_ext_symbols</code>, the size of the table would simply be <code>sizeof(struct ext_jump) * num_ext_symbols</code>:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void parse_obj(void)
{
...
 
    count_external_symbols();
 
    /* allocate memory for `.text`, `.data` and `.rodata` copies and the jumptable for external symbols, rounding up each section to whole pages */
    text_runtime_base = mmap(NULL, page_align(text_hdr-&gt;sh_size) + \
                                   page_align(data_hdr-&gt;sh_size) + \
                                   page_align(rodata_hdr-&gt;sh_size) + \
                                   page_align(sizeof(struct ext_jump) * num_ext_symbols),
                                   PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (text_runtime_base == MAP_FAILED) {
        perror("Failed to allocate memory");
        exit(errno);
    }
 
...
    rodata_runtime_base = data_runtime_base + page_align(data_hdr-&gt;sh_size);
    /* jumptable will come after .rodata */
    jumptable = (struct ext_jump *)(rodata_runtime_base + page_align(rodata_hdr-&gt;sh_size));
 
...
}
...</code></pre>
            <p>Finally, because the CPU will actually be executing the jump instructions from our <code>instr</code> fields from the jumptable, we need to mark this memory readonly and executable (after <code>do_text_relocations</code> earlier in this function has completed):</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void parse_obj(void)
{
...
 
    do_text_relocations();
 
...
 
    /* make the jumptable readonly and executable */
    if (mprotect(jumptable, page_align(sizeof(struct ext_jump) * num_ext_symbols), PROT_READ | PROT_EXEC)) {
        perror("Failed to make the jumptable executable");
        exit(errno);
    }
}
...</code></pre>
            <p>At this stage we have our jumptable allocated and usable — all is left to do is to populate it properly. We’ll do this by improving the <code>do_text_relocations</code> implementation to handle the case of external symbols. The <code>No runtime base address for section</code> error from the beginning of this post is actually caused by this line in <code>do_text_relocations</code>:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void do_text_relocations(void)
{
...
    for (int i = 0; i &lt; num_relocations; i++) {
...
        /* symbol, with respect to which the relocation is performed */
        uint8_t *symbol_address = = section_runtime_base(&amp;sections[symbols[symbol_idx].st_shndx]) + symbols[symbol_idx].st_value;
...
}
...</code></pre>
            <p>Currently we try to determine the runtime symbol address for the relocation by looking up the symbol's section runtime address and adding the symbol's offset. But we have established above that external symbols do not have an associated section, so their handling needs to be a special case. Let's update the implementation to reflect this:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void do_text_relocations(void)
{
...
    for (int i = 0; i &lt; num_relocations; i++) {
...
        /* symbol, with respect to which the relocation is performed */
        uint8_t *symbol_address;
        
        /* if this is an external symbol */
        if (symbols[symbol_idx].st_shndx == SHN_UNDEF) {
            static int curr_jmp_idx = 0;
 
            /* get external symbol/function address by name */
            jumptable[curr_jmp_idx].addr = lookup_ext_function(strtab +  symbols[symbol_idx].st_name);
 
            /* x64 unconditional JMP with address stored at -14 bytes offset */
            /* will use the address stored in addr above */
            jumptable[curr_jmp_idx].instr[0] = 0xff;
            jumptable[curr_jmp_idx].instr[1] = 0x25;
            jumptable[curr_jmp_idx].instr[2] = 0xf2;
            jumptable[curr_jmp_idx].instr[3] = 0xff;
            jumptable[curr_jmp_idx].instr[4] = 0xff;
            jumptable[curr_jmp_idx].instr[5] = 0xff;
 
            /* resolve the relocation with respect to this unconditional JMP */
            symbol_address = (uint8_t *)(&amp;jumptable[curr_jmp_idx].instr);
 
            curr_jmp_idx++;
        } else {
            symbol_address = section_runtime_base(&amp;sections[symbols[symbol_idx].st_shndx]) + symbols[symbol_idx].st_value;
        }
...
}
...</code></pre>
            <p>If a relocation symbol does not have an associated section, we consider it external and call a helper function to lookup the symbol's runtime address by its name. We store this address in the next available jumptable entry, populate the x64 jump instruction with our fixed operand and store the address of the instruction in the <code>symbol_address</code> variable. Later, the existing code in <code>do_text_relocations</code> will resolve the <code>.text</code> relocation with respect to the address in <code>symbol_address</code> in the same way it does for local symbols in <a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-2/">part 2 of our series</a>.</p><p>The only missing bit here now is the implementation of the newly introduced <code>lookup_ext_function</code> helper. Real world loaders may have complicated logic on how to find and resolve symbols in memory at runtime. But for the purposes of this article we'll provide a simple naive implementation, which can only resolve the <code>puts</code> function:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void *lookup_ext_function(const char *name)
{
    size_t name_len = strlen(name);
 
    if (name_len == strlen("puts") &amp;&amp; !strcmp(name, "puts"))
        return my_puts;
 
    fprintf(stderr, "No address for function %s\n", name);
    exit(ENOENT);
}
...</code></pre>
            <p>Notice though that because we control the <code>loader</code> logic we are free to implement resolution as we please. In the above case we actually "divert" the object file to use our own "custom" <code>my_puts</code> function instead of the C library one. Let's recompile the <code>loader</code> and see if it works:</p>
            <pre><code>$ gcc -o loader loader.c
$ ./loader
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 52
Executing get_hello...
get_hello() = Hello, world!
Executing get_var...
get_var() = 5
Executing set_var(42)...
Executing get_var again...
get_var() = 42
Executing say_hello...
my_puts executed
Hello, world!</code></pre>
            <p>Hooray! We not only fixed our <code>loader</code> to handle external references in object files — we have also learned how to "hook" any such external function call and divert the code to a custom implementation, which might be useful in some cases, like malware research.</p><p>As in the previous posts, the complete source code from this post is <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2021-03-obj-file/3">available on GitHub</a>.</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Programming]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">3zPmdVaAOcTsffVeGWzsCW</guid>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
        <item>
            <title><![CDATA[How to execute an object file: Part 2]]></title>
            <link>https://blog.cloudflare.com/how-to-execute-an-object-file-part-2/</link>
            <pubDate>Fri, 02 Apr 2021 11:00:00 GMT</pubDate>
            <description><![CDATA[ Continue learning how to import and execute code from an object file. This time we will investigate ELF relocations. ]]></description>
            <content:encoded><![CDATA[ 
    <div>
      <h2>Handling relocations</h2>
      <a href="#handling-relocations">
        
      </a>
    </div>
    <p>In the <a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-1/">previous post</a>, we learned how to parse an object file and import and execute some functions from it. However, the functions in our toy object file were simple and self-contained: they computed their output solely based on their inputs and didn't have any external code or data dependencies. In this post we will build upon <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2021-03-obj-file/1">the code from part 1</a>, exploring additional steps needed to handle code with some dependencies.</p><p>As an example, we may notice that we can actually rewrite our <code>add10</code> function using our <code>add5</code> function:</p><p><i>obj.c</i>:</p>
            <pre><code>int add5(int num)
{
    return num + 5;
}
 
int add10(int num)
{
    num = add5(num);
    return add5(num);
}</code></pre>
            <p>Let's recompile the object file and try to use it as a library with our <code>loader</code> program:</p>
            <pre><code>$ gcc -c obj.c
$ ./loader
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 42</code></pre>
            <p>Whoa! Something is not right here. <code>add5</code> still produces the correct result, but <code>add10</code> does not . Depending on your environment and code composition, you may even see the <code>loader</code> program crashing instead of outputting incorrect results. To understand what happened, let's investigate the machine code generated by the compiler. We can do that by asking the <a href="https://man7.org/linux/man-pages/man1/objdump.1.html">objdump tool</a> to disassemble the <code>.text</code> section from our <code>obj.o</code>:</p>
            <pre><code>$ objdump --disassemble --section=.text obj.o
 
obj.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 &lt;add5&gt;:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	89 7d fc             	mov    %edi,-0x4(%rbp)
   7:	8b 45 fc             	mov    -0x4(%rbp),%eax
   a:	83 c0 05             	add    $0x5,%eax
   d:	5d                   	pop    %rbp
   e:	c3                   	retq
 
000000000000000f &lt;add10&gt;:
   f:	55                   	push   %rbp
  10:	48 89 e5             	mov    %rsp,%rbp
  13:	48 83 ec 08          	sub    $0x8,%rsp
  17:	89 7d fc             	mov    %edi,-0x4(%rbp)
  1a:	8b 45 fc             	mov    -0x4(%rbp),%eax
  1d:	89 c7                	mov    %eax,%edi
  1f:	e8 00 00 00 00       	callq  24 &lt;add10+0x15&gt;
  24:	89 45 fc             	mov    %eax,-0x4(%rbp)
  27:	8b 45 fc             	mov    -0x4(%rbp),%eax
  2a:	89 c7                	mov    %eax,%edi
  2c:	e8 00 00 00 00       	callq  31 &lt;add10+0x22&gt;
  31:	c9                   	leaveq
  32:	c3                   	retq</code></pre>
            <p>You don't have to understand the full output above. There are only two relevant lines here: <code>1f: e8 00 00 00 00</code> and <code>2c: e8 00 00 00 00</code>. These correspond to the two <code>add5</code> function invocations we have in the source code and <a href="https://man7.org/linux/man-pages/man1/objdump.1.html">objdump</a> even conveniently decodes the instruction for us as <code>callq</code>. By looking at descriptions of the <code>callq</code> instruction online (like <a href="https://www.felixcloutier.com/x86/call">this one</a>), we can further see we're dealing with a "near, relative call", because of the <code>0xe8</code> prefix:</p><blockquote><p>Call near, relative, displacement relative to next instruction.</p></blockquote><p>According to the <a href="https://www.felixcloutier.com/x86/call">description</a>, this variant of the <code>callq</code> instruction consists of 5 bytes: the <code>0xe8</code> prefix and a 4-byte (32 bit) argument. This is where "relative" comes from: the argument should contain the “distance” between the function we want to call and the current position — because the way how x86 works this distance is calculated from the next instruction and not our current <code>callq</code> instruction. <a href="https://man7.org/linux/man-pages/man1/objdump.1.html">objdump</a> conveniently outputs each machine instruction's offset in the output above, so we can easily calculate the needed argument. For example, for the first <code>callq</code> instruction (<code>1f: e8 00 00 00 00</code>) the next instruction is at offset <code>0x24</code>. We know we should be calling the <code>add5</code> function, which starts at offset <code>0x0</code> (beginning of our <code>.text</code> section). So the relative offset is <code>0x0 - 0x24 = -0x24</code>. Notice, we have a negative argument, because the <code>add5</code> function is located before our calling instruction, so we would be instructing the CPU to "jump backwards" from its current position. Lastly, we have to remember that negative numbers — at least on x86 systems — are presented by their <a href="https://en.wikipedia.org/wiki/Two%27s_complement">two's complements</a>, so a 4-byte (32 bit) representation of <code>-0x24</code> would be <code>0xffffffdc</code>. In the same way we can calculate the <code>callq</code> argument for the second <code>add5</code> call: <code>0x0 - 0x31 = -0x31</code>, two's complement - <code>0xffffffcf</code>:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7hr8krlEJ3GlvbDl2xV3Ww/4d838959fb33b65d3bc712c1b152262c/relative-calls.png" />
            
            </figure><p>It seems the compiler does not generate the right <code>callq</code> arguments for us. We've calculated the expected arguments to be <code>0xffffffdc</code> and <code>0xffffffcf</code>, but the compiler has just left <code>0x00000000</code> in both places. Let's check first if our expectations are correct by patching our loaded <code>.text</code> copy before trying to execute it:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void parse_obj(void)
{
...
    /* copy the contents of `.text` section from the ELF file */
    memcpy(text_runtime_base, obj.base + text_hdr-&gt;sh_offset, text_hdr-&gt;sh_size);
 
    /* the first add5 callq argument is located at offset 0x20 and should be 0xffffffdc:
     * 0x1f is the instruction offset + 1 byte instruction prefix
     */
    *((uint32_t *)(text_runtime_base + 0x1f + 1)) = 0xffffffdc;
 
    /* the second add5 callq argument is located at offset 0x2d and should be 0xffffffcf */
    *((uint32_t *)(text_runtime_base + 0x2c + 1)) = 0xffffffcf;
 
    /* make the `.text` copy readonly and executable */
    if (mprotect(text_runtime_base, page_align(text_hdr-&gt;sh_size), PROT_READ | PROT_EXEC)) {
...</code></pre>
            <p>And now let's test it out:</p>
            <pre><code>$ gcc -o loader loader.c 
$ ./loader 
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 52</code></pre>
            <p>Clearly our monkey-patching helped: <code>add10</code> executes fine now and produces the correct output. This means our expected <code>callq</code> arguments, which we calculated, are correct. So why did the compiler emit wrong <code>callq</code> arguments?</p>
    <div>
      <h3>Relocations</h3>
      <a href="#relocations">
        
      </a>
    </div>
    <p>The problem with our toy object file is that both functions are declared with external linkage — the default setting for all functions and global variables in C. And, although both functions are declared in the same file, the compiler is not sure where the <code>add5</code> code will end up in the target binary. So the compiler avoids making any assumptions and doesn’t calculate the relative offset argument of the <code>callq</code> instructions. Let's verify this by removing our monkey patching and declaring the <code>add5</code> function as <code>static</code>:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
    /* the first add5 callq argument is located at offset 0x20 and should be 0xffffffdc:
     * 0x1f is the instruction offset + 1 byte instruction prefix
     */
    /* *((uint32_t *)(text_runtime_base + 0x1f + 1)) = 0xffffffdc; */
 
    /* the second add5 callq argument is located at offset 0x2d and should be 0xffffffcf */
    /* *((uint32_t *)(text_runtime_base + 0x2c + 1)) = 0xffffffcf; */
 
...</code></pre>
            <p><i>obj.c</i>:</p>
            <pre><code>/* int add5(int num) */
static int add5(int num)
...</code></pre>
            <p>Recompiling and disassembling <code>obj.o</code> gives us the following:</p>
            <pre><code>$ gcc -c obj.c
$ objdump --disassemble --section=.text obj.o
 
obj.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 &lt;add5&gt;:
   0:	55                   	push   %rbp
   1:	48 89 e5             	mov    %rsp,%rbp
   4:	89 7d fc             	mov    %edi,-0x4(%rbp)
   7:	8b 45 fc             	mov    -0x4(%rbp),%eax
   a:	83 c0 05             	add    $0x5,%eax
   d:	5d                   	pop    %rbp
   e:	c3                   	retq
 
000000000000000f &lt;add10&gt;:
   f:	55                   	push   %rbp
  10:	48 89 e5             	mov    %rsp,%rbp
  13:	48 83 ec 08          	sub    $0x8,%rsp
  17:	89 7d fc             	mov    %edi,-0x4(%rbp)
  1a:	8b 45 fc             	mov    -0x4(%rbp),%eax
  1d:	89 c7                	mov    %eax,%edi
  1f:	e8 dc ff ff ff       	callq  0 &lt;add5&gt;
  24:	89 45 fc             	mov    %eax,-0x4(%rbp)
  27:	8b 45 fc             	mov    -0x4(%rbp),%eax
  2a:	89 c7                	mov    %eax,%edi
  2c:	e8 cf ff ff ff       	callq  0 &lt;add5&gt;
  31:	c9                   	leaveq
  32:	c3                   	retq</code></pre>
            <p>Because we re-declared the <code>add5</code> function with internal linkage, the compiler is more confident now and calculates <code>callq</code> arguments correctly (note that x86 systems are <a href="https://en.wikipedia.org/wiki/Endianness">little-endian</a>, so multibyte numbers like <code>0xffffffdc</code> will be represented with least significant byte first). We can double check this by recompiling and running our <code>loader</code> test tool:</p>
            <pre><code>$ gcc -o loader loader.c
$ ./loader
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 52</code></pre>
            <p>Even though the <code>add5</code> function is declared as <code>static</code>, we can still call it from the <code>loader</code> tool, basically ignoring the fact that it is an "internal" function now. Because of this, the <code>static</code> keyword should not be used as a security feature to hide APIs from potential malicious users.</p><p>But let's step back and revert our <code>add5</code> function in <code>obj.c</code> to the one with external linkage:</p><p><i>obj.c</i>:</p>
            <pre><code>int add5(int num)
...</code></pre>
            
            <pre><code>$ gcc -c obj.c
$ ./loader
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 42</code></pre>
            <p>As we have established above, the compiler did not compute proper <code>callq</code> arguments for us because it didn't have enough information. But later stages (namely the linker) will have that information, so instead the compiler leaves some clues on how to fix those arguments. These clues — or instructions for the later stages — are called <b>relocations</b>. We can inspect them with our friend, the <a href="https://man7.org/linux/man-pages/man1/readelf.1.html">readelf</a> utility. Let's examine <code>obj.o</code> sections table again:</p>
            <pre><code>$ readelf --sections obj.o
There are 12 section headers, starting at offset 0x2b0:
 
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       0000000000000033  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  000001f0
       0000000000000030  0000000000000018   I       9     1     8
  [ 3] .data             PROGBITS         0000000000000000  00000073
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .bss              NOBITS           0000000000000000  00000073
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .comment          PROGBITS         0000000000000000  00000073
       000000000000001d  0000000000000001  MS       0     0     1
  [ 6] .note.GNU-stack   PROGBITS         0000000000000000  00000090
       0000000000000000  0000000000000000           0     0     1
  [ 7] .eh_frame         PROGBITS         0000000000000000  00000090
       0000000000000058  0000000000000000   A       0     0     8
  [ 8] .rela.eh_frame    RELA             0000000000000000  00000220
       0000000000000030  0000000000000018   I       9     7     8
  [ 9] .symtab           SYMTAB           0000000000000000  000000e8
       00000000000000f0  0000000000000018          10     8     8
  [10] .strtab           STRTAB           0000000000000000  000001d8
       0000000000000012  0000000000000000           0     0     1
  [11] .shstrtab         STRTAB           0000000000000000  00000250
       0000000000000059  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)</code></pre>
            <p>We see that the compiler created a new section called <code>.rela.text</code>. By convention, a section with relocations for a section named <code>.foo</code> will be called <code>.rela.foo</code>, so we can see that the compiler created a section with relocations for the <code>.text</code> section. We can examine the relocations further:</p>
            <pre><code>$ readelf --relocs obj.o
 
Relocation section '.rela.text' at offset 0x1f0 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000800000004 R_X86_64_PLT32    0000000000000000 add5 - 4
00000000002d  000800000004 R_X86_64_PLT32    0000000000000000 add5 - 4
 
Relocation section '.rela.eh_frame' at offset 0x220 contains 2 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000200000002 R_X86_64_PC32     0000000000000000 .text + 0
000000000040  000200000002 R_X86_64_PC32     0000000000000000 .text + f</code></pre>
            <p>Let's ignore the relocations from the <code>.rela.eh_frame</code> section because they are out of scope of this post. Instead, let’s try to understand the relocations from the <code>.rela.text</code>:</p><ul><li><p><code>Offset</code> column tells us exactly where in the target section (<code>.text</code> in this case) the fix/adjustment is needed. Note that these offsets are exactly the same as in our self-calculated monkey-patching above.</p></li><li><p><code>Info</code> is a combined value: the upper 32 bits — only 16 bits are shown in the output above — represent the index of the symbol in the symbol table, with respect to which the relocation is performed. In our example it is <code>8</code> and if we run <code>readelf --symbols obj.o</code> we will see that it points to an entry corresponding to the <code>add5</code> function. The lower 32 bits (<code>4</code> in our case) is a relocation type (see <code>Type</code> below).</p></li><li><p><code>Type</code> describes the relocation type. This is a pseudo-column: <code>readelf</code> actually generates it from the lower 32 bits of the <code>Info</code> field. Different relocation types have different formulas we need to apply to perform the relocation.</p></li><li><p><code>Sym. Value</code> may mean different things depending on the relocation type, but most of the time it is the symbol offset with respect to which we perform the relocation. The offset is calculated from the beginning of that symbol’s section.</p></li><li><p><code>Addend</code> is a constant we may need to use in the relocation formula. Depending on the relocation type, <a href="https://man7.org/linux/man-pages/man1/readelf.1.html">readelf</a> actually adds the decoded symbol name to the output, so the column name is <code>Sym. Name + Addend</code> above but the actual field stores the addend only.</p></li></ul><p>In a nutshell, these entries tell us that we need to patch the <code>.text</code> section at offsets <code>0x20</code> and <code>0x2d</code>. To calculate what to put there, we need to apply the formula for the <code>R_X86_64_PLT32</code> relocation type. Searching online, we can find different ELF specifications — like <a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf">this one</a> — which will tell us how to implement the <code>R_X86_64_PLT32</code> relocation. The specification mentions that the result of this relocation is <code>word32</code> — which is what we expect because <code>callq</code> arguments are 32 bit in our case — and the formula we need to apply is <code>L + A - P</code>, where:</p><ul><li><p><code>L</code> is the address of the symbol, with respect to which the relocation is performed (<code>add5</code> in our case)</p></li><li><p><code>A</code> is the constant addend (<code>4</code> in our case)</p></li><li><p><code>P</code> is the address/offset, where we store the result of the relocation</p></li></ul><p>When the relocation formula references some symbol addresses or offsets, we should use the actual — runtime in our case — addresses in the calculations. For example, we will be using <code>text_runtime_base + 0x2d</code> as <code>P</code> for the second relocation and not just <code>0x2d</code>. So let's try to implement this relocation logic in our object loader:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
/* from https://elixir.bootlin.com/linux/v5.11.6/source/arch/x86/include/asm/elf.h#L51 */
#define R_X86_64_PLT32 4
 
...
 
static uint8_t *section_runtime_base(const Elf64_Shdr *section)
{
    const char *section_name = shstrtab + section-&gt;sh_name;
    size_t section_name_len = strlen(section_name);
 
    /* we only mmap .text section so far */
    if (strlen(".text") == section_name_len &amp;&amp; !strcmp(".text", section_name))
        return text_runtime_base;
 
    fprintf(stderr, "No runtime base address for section %s\n", section_name);
    exit(ENOENT);
}
 
static void do_text_relocations(void)
{
    /* we actually cheat here - the name .rela.text is a convention, but not a
     * rule: to figure out which section should be patched by these relocations
     * we would need to examine the rela_text_hdr, but we skip it for simplicity
     */
    const Elf64_Shdr *rela_text_hdr = lookup_section(".rela.text");
    if (!rela_text_hdr) {
        fputs("Failed to find .rela.text\n", stderr);
        exit(ENOEXEC);
    }
 
    int num_relocations = rela_text_hdr-&gt;sh_size / rela_text_hdr-&gt;sh_entsize;
    const Elf64_Rela *relocations = (Elf64_Rela *)(obj.base + rela_text_hdr-&gt;sh_offset);
 
    for (int i = 0; i &lt; num_relocations; i++) {
        int symbol_idx = ELF64_R_SYM(relocations[i].r_info);
        int type = ELF64_R_TYPE(relocations[i].r_info);
 
        /* where to patch .text */
        uint8_t *patch_offset = text_runtime_base + relocations[i].r_offset;
        /* symbol, with respect to which the relocation is performed */
        uint8_t *symbol_address = section_runtime_base(&amp;sections[symbols[symbol_idx].st_shndx]) + symbols[symbol_idx].st_value;
 
        switch (type)
        {
        case R_X86_64_PLT32:
            /* L + A - P, 32 bit output */
            *((uint32_t *)patch_offset) = symbol_address + relocations[i].r_addend - patch_offset;
            printf("Calculated relocation: 0x%08x\n", *((uint32_t *)patch_offset));
            break;
        }
    }
}
 
static void parse_obj(void)
{
...
 
    /* copy the contents of `.text` section from the ELF file */
    memcpy(text_runtime_base, obj.base + text_hdr-&gt;sh_offset, text_hdr-&gt;sh_size);
 
    do_text_relocations();
 
    /* make the `.text` copy readonly and executable */
    if (mprotect(text_runtime_base, page_align(text_hdr-&gt;sh_size), PROT_READ | PROT_EXEC)) {
 
...
}
 
...</code></pre>
            <p>We are now calling the <code>do_text_relocations</code> function before marking our <code>.text</code> copy executable. We have also added some debugging output to inspect the result of the relocation calculations. Let's try it out:</p>
            <pre><code>$ gcc -o loader loader.c 
$ ./loader 
Calculated relocation: 0xffffffdc
Calculated relocation: 0xffffffcf
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 52</code></pre>
            <p>Great! Our imported code works as expected now. By following the relocation hints left for us by the compiler, we've got the same results as in our monkey-patching calculations in the beginning of this post. Our relocation calculations also involved <code>text_runtime_base</code> address, which is not available at compile time. That's why the compiler could not calculate the <code>callq</code> arguments in the first place and had to emit the relocations instead.</p>
    <div>
      <h3>Handling constant data and global variables</h3>
      <a href="#handling-constant-data-and-global-variables">
        
      </a>
    </div>
    <p>So far, we have been dealing with object files containing only executable code with no state. That is, the imported functions could compute their output solely based on the inputs. Let's see what happens if we add some constant data and global variables dependencies to our imported code. First, we add some more functions to our <code>obj.o</code>:</p><p><i>obj.c</i>:</p>
            <pre><code>...
 
const char *get_hello(void)
{
    return "Hello, world!";
}
 
static int var = 5;
 
int get_var(void)
{
    return var;
}
 
void set_var(int num)
{
    var = num;
}</code></pre>
            <p><code>get_hello</code> returns a constant string and <code>get_var</code>/<code>set_var</code> get and set a global variable respectively. Next, let's recompile the <code>obj.o</code> and run our loader:</p>
            <pre><code>$ gcc -c obj.c
$ ./loader 
Calculated relocation: 0xffffffdc
Calculated relocation: 0xffffffcf
No runtime base address for section .rodata</code></pre>
            <p>Looks like our loader tried to process more relocations but could not find the runtime address for <code>.rodata</code> section. Previously, we didn't even have a <code>.rodata</code> section, but it was added now because our <code>obj.o</code> needs somewhere to store the constant string <code>Hello, world!:</code></p>
            <pre><code>$ readelf --sections obj.o
There are 13 section headers, starting at offset 0x478:
 
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       000000000000005f  0000000000000000  AX       0     0     1
  [ 2] .rela.text        RELA             0000000000000000  00000320
       0000000000000078  0000000000000018   I      10     1     8
  [ 3] .data             PROGBITS         0000000000000000  000000a0
       0000000000000004  0000000000000000  WA       0     0     4
  [ 4] .bss              NOBITS           0000000000000000  000000a4
       0000000000000000  0000000000000000  WA       0     0     1
  [ 5] .rodata           PROGBITS         0000000000000000  000000a4
       000000000000000d  0000000000000000   A       0     0     1
  [ 6] .comment          PROGBITS         0000000000000000  000000b1
       000000000000001d  0000000000000001  MS       0     0     1
  [ 7] .note.GNU-stack   PROGBITS         0000000000000000  000000ce
       0000000000000000  0000000000000000           0     0     1
  [ 8] .eh_frame         PROGBITS         0000000000000000  000000d0
       00000000000000b8  0000000000000000   A       0     0     8
  [ 9] .rela.eh_frame    RELA             0000000000000000  00000398
       0000000000000078  0000000000000018   I      10     8     8
  [10] .symtab           SYMTAB           0000000000000000  00000188
       0000000000000168  0000000000000018          11    10     8
  [11] .strtab           STRTAB           0000000000000000  000002f0
       000000000000002c  0000000000000000           0     0     1
  [12] .shstrtab         STRTAB           0000000000000000  00000410
       0000000000000061  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)</code></pre>
            <p>We also have more <code>.text</code> relocations:</p>
            <pre><code>$ readelf --relocs obj.o
 
Relocation section '.rela.text' at offset 0x320 contains 5 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
000000000020  000a00000004 R_X86_64_PLT32    0000000000000000 add5 - 4
00000000002d  000a00000004 R_X86_64_PLT32    0000000000000000 add5 - 4
00000000003a  000500000002 R_X86_64_PC32     0000000000000000 .rodata - 4
000000000046  000300000002 R_X86_64_PC32     0000000000000000 .data - 4
000000000058  000300000002 R_X86_64_PC32     0000000000000000 .data - 4
...</code></pre>
            <p>The compiler emitted three more <code>R_X86_64_PC32</code> relocations this time. They reference symbols with index <code>3</code> and <code>5</code>, so let's find out what they are:</p>
            <pre><code>$ readelf --symbols obj.o
 
Symbol table '.symtab' contains 15 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS obj.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     4 OBJECT  LOCAL  DEFAULT    3 var
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7
     8: 0000000000000000     0 SECTION LOCAL  DEFAULT    8
     9: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
    10: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 add5
    11: 000000000000000f    36 FUNC    GLOBAL DEFAULT    1 add10
    12: 0000000000000033    13 FUNC    GLOBAL DEFAULT    1 get_hello
    13: 0000000000000040    12 FUNC    GLOBAL DEFAULT    1 get_var
    14: 000000000000004c    19 FUNC    GLOBAL DEFAULT    1 set_var</code></pre>
            <p>Entries <code>3</code> and <code>5</code> don't have any names attached, but they reference something in sections with index <code>3</code> and <code>5</code> respectively. In the output of the section table above, we can see that the section with index <code>3</code> is <code>.data</code> and the section with index <code>5</code> is <code>.rodata</code>. For a refresher on the most common sections in an ELF file check out our <a href="https://blog.cloudflare.com/how-to-execute-an-object-file-part-1/">previous post</a>. To import our newly added code and make it work, we also need to map <code>.data</code> and <code>.rodata</code> sections in addition to the <code>.text</code> section and process these <code>R_X86_64_PC32</code> relocations.</p><p>There is one caveat though. If we check <a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf">the specification</a>, we'll see that <code>R_X86_64_PC32</code> relocation produces a 32-bit output similar to the <code>R_X86_64_PLT32</code> relocation. This means that the "distance" in memory between the patched position in <code>.text</code> and the referenced symbol has to be small enough to fit into a 32-bit value (1 bit for the positive/negative sign and 31 bits for the actual data, so less than 2147483647 bytes). Our <code>loader</code> program uses <a href="https://man7.org/linux/man-pages/man2/mmap.2.html">mmap system call</a> to allocate memory for the object section copies, but <a href="https://man7.org/linux/man-pages/man2/mmap.2.html">mmap</a> may allocate the mapping almost anywhere in the process address space. If we modify the <code>loader</code> program to call <a href="https://man7.org/linux/man-pages/man2/mmap.2.html">mmap</a> for each section separately, we may end up having <code>.rodata</code> or <code>.data</code> section mapped too far away from the <code>.text</code> section and will not be able to process the <code>R_X86_64_PC32</code> relocations. In other words, we need to ensure that <code>.data</code> and <code>.rodata</code> sections are located relatively close to the <code>.text</code> section at runtime:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4e8Qr6ZYKMwqrHQAgJhnHD/402256ee621e61484ca011ed8994b185/runtime-diff.png" />
            
            </figure><p>One way to achieve that would be to allocate the memory we need for all the sections with one <a href="https://man7.org/linux/man-pages/man2/mmap.2.html">mmap call</a>. Then, we’d break it in chunks and assign proper access permissions to each chunk. Let's modify our <code>loader</code> program to do just that:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
/* runtime base address of the imported code */
static uint8_t *text_runtime_base;
/* runtime base of the .data section */
static uint8_t *data_runtime_base;
/* runtime base of the .rodata section */
static uint8_t *rodata_runtime_base;
 
...
 
static void parse_obj(void)
{
...
 
    /* find the `.text` entry in the sections table */
    const Elf64_Shdr *text_hdr = lookup_section(".text");
    if (!text_hdr) {
        fputs("Failed to find .text\n", stderr);
        exit(ENOEXEC);
    }
 
    /* find the `.data` entry in the sections table */
    const Elf64_Shdr *data_hdr = lookup_section(".data");
    if (!data_hdr) {
        fputs("Failed to find .data\n", stderr);
        exit(ENOEXEC);
    }
 
    /* find the `.rodata` entry in the sections table */
    const Elf64_Shdr *rodata_hdr = lookup_section(".rodata");
    if (!rodata_hdr) {
        fputs("Failed to find .rodata\n", stderr);
        exit(ENOEXEC);
    }
 
    /* allocate memory for `.text`, `.data` and `.rodata` copies rounding up each section to whole pages */
    text_runtime_base = mmap(NULL, page_align(text_hdr-&gt;sh_size) + page_align(data_hdr-&gt;sh_size) + page_align(rodata_hdr-&gt;sh_size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (text_runtime_base == MAP_FAILED) {
        perror("Failed to allocate memory");
        exit(errno);
    }
 
    /* .data will come right after .text */
    data_runtime_base = text_runtime_base + page_align(text_hdr-&gt;sh_size);
    /* .rodata will come after .data */
    rodata_runtime_base = data_runtime_base + page_align(data_hdr-&gt;sh_size);
 
    /* copy the contents of `.text` section from the ELF file */
    memcpy(text_runtime_base, obj.base + text_hdr-&gt;sh_offset, text_hdr-&gt;sh_size);
    /* copy .data */
    memcpy(data_runtime_base, obj.base + data_hdr-&gt;sh_offset, data_hdr-&gt;sh_size);
    /* copy .rodata */
    memcpy(rodata_runtime_base, obj.base + rodata_hdr-&gt;sh_offset, rodata_hdr-&gt;sh_size);
 
    do_text_relocations();
 
    /* make the `.text` copy readonly and executable */
    if (mprotect(text_runtime_base, page_align(text_hdr-&gt;sh_size), PROT_READ | PROT_EXEC)) {
        perror("Failed to make .text executable");
        exit(errno);
    }
 
    /* we don't need to do anything with .data - it should remain read/write */
 
    /* make the `.rodata` copy readonly */
    if (mprotect(rodata_runtime_base, page_align(rodata_hdr-&gt;sh_size), PROT_READ)) {
        perror("Failed to make .rodata readonly");
        exit(errno);
    }
}
 
...</code></pre>
            <p>Now that we have runtime addresses of <code>.data</code> and <code>.rodata</code>, we can update the relocation runtime address lookup function:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static uint8_t *section_runtime_base(const Elf64_Shdr *section)
{
    const char *section_name = shstrtab + section-&gt;sh_name;
    size_t section_name_len = strlen(section_name);
 
    if (strlen(".text") == section_name_len &amp;&amp; !strcmp(".text", section_name))
        return text_runtime_base;
 
    if (strlen(".data") == section_name_len &amp;&amp; !strcmp(".data", section_name))
        return data_runtime_base;
 
    if (strlen(".rodata") == section_name_len &amp;&amp; !strcmp(".rodata", section_name))
        return rodata_runtime_base;
 
    fprintf(stderr, "No runtime base address for section %s\n", section_name);
    exit(ENOENT);
}</code></pre>
            <p>And finally we can import and execute our new functions:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
static void execute_funcs(void)
{
    /* pointers to imported functions */
    int (*add5)(int);
    int (*add10)(int);
    const char *(*get_hello)(void);
    int (*get_var)(void);
    void (*set_var)(int num);
 
...
 
    printf("add10(%d) = %d\n", 42, add10(42));
 
    get_hello = lookup_function("get_hello");
    if (!get_hello) {
        fputs("Failed to find get_hello function\n", stderr);
        exit(ENOENT);
    }
 
    puts("Executing get_hello...");
    printf("get_hello() = %s\n", get_hello());
 
    get_var = lookup_function("get_var");
    if (!get_var) {
        fputs("Failed to find get_var function\n", stderr);
        exit(ENOENT);
    }
 
    puts("Executing get_var...");
    printf("get_var() = %d\n", get_var());
 
    set_var = lookup_function("set_var");
    if (!set_var) {
        fputs("Failed to find set_var function\n", stderr);
        exit(ENOENT);
    }
 
    puts("Executing set_var(42)...");
    set_var(42);
 
    puts("Executing get_var again...");
    printf("get_var() = %d\n", get_var());
}
...</code></pre>
            <p>Let's try it out:</p>
            <pre><code>$ gcc -o loader loader.c 
$ ./loader 
Calculated relocation: 0xffffffdc
Calculated relocation: 0xffffffcf
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 52
Executing get_hello...
get_hello() = ]�UH��
Executing get_var...
get_var() = 1213580125
Executing set_var(42)...
Segmentation fault</code></pre>
            <p>Uh-oh! We forgot to implement the new <code>R_X86_64_PC32</code> relocation type. The <a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf">relocation formula</a> here is <code>S + A - P</code>. We already know about <code>A</code> and <code>P</code>. As for <code>S</code> (quoting from <a href="https://refspecs.linuxfoundation.org/elf/x86_64-abi-0.95.pdf">the spec</a>):</p><blockquote><p>“the value of the symbol whose index resides in the relocation entry"</p></blockquote><p>In our case, it is essentially the same as <code>L</code> for <code>R_X86_64_PLT32</code>. We can just reuse the implementation and remove the debug output in the process:</p><p><i>loader.c</i>:</p>
            <pre><code>...
 
/* from https://elixir.bootlin.com/linux/v5.11.6/source/arch/x86/include/asm/elf.h#L51 */
#define R_X86_64_PC32 2
#define R_X86_64_PLT32 4
 
...
 
static void do_text_relocations(void)
{
    /* we actually cheat here - the name .rela.text is a convention, but not a
     * rule: to figure out which section should be patched by these relocations
     * we would need to examine the rela_text_hdr, but we skip it for simplicity
     */
    const Elf64_Shdr *rela_text_hdr = lookup_section(".rela.text");
    if (!rela_text_hdr) {
        fputs("Failed to find .rela.text\n", stderr);
        exit(ENOEXEC);
    }
 
    int num_relocations = rela_text_hdr-&gt;sh_size / rela_text_hdr-&gt;sh_entsize;
    const Elf64_Rela *relocations = (Elf64_Rela *)(obj.base + rela_text_hdr-&gt;sh_offset);
 
    for (int i = 0; i &lt; num_relocations; i++) {
        int symbol_idx = ELF64_R_SYM(relocations[i].r_info);
        int type = ELF64_R_TYPE(relocations[i].r_info);
 
        /* where to patch .text */
        uint8_t *patch_offset = text_runtime_base + relocations[i].r_offset;
        /* symbol, with respect to which the relocation is performed */
        uint8_t *symbol_address = section_runtime_base(&amp;sections[symbols[symbol_idx].st_shndx]) + symbols[symbol_idx].st_value;
 
        switch (type)
        {
        case R_X86_64_PC32:
            /* S + A - P, 32 bit output, S == L here */
        case R_X86_64_PLT32:
            /* L + A - P, 32 bit output */
            *((uint32_t *)patch_offset) = symbol_address + relocations[i].r_addend - patch_offset;
            break;
        }
    }
}
 
...</code></pre>
            <p>Now we should be done. Another try:</p>
            <pre><code>$ gcc -o loader loader.c 
$ ./loader 
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 52
Executing get_hello...
get_hello() = Hello, world!
Executing get_var...
get_var() = 5
Executing set_var(42)...
Executing get_var again...
get_var() = 42</code></pre>
            <p>This time we can successfully import functions that reference static constant data and global variables. We can even manipulate the object file’s internal state through the defined accessor interface. As before, the complete source code for this post is <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2021-03-obj-file/2">available on GitHub</a>.</p><p>In the next post, we will look into importing and executing object code with references to external libraries. Stay tuned!</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Programming]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">6S16duVAPX4oHU5pN43RlW</guid>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
        <item>
            <title><![CDATA[How to execute an object file: Part 1]]></title>
            <link>https://blog.cloudflare.com/how-to-execute-an-object-file-part-1/</link>
            <pubDate>Tue, 02 Mar 2021 12:00:00 GMT</pubDate>
            <description><![CDATA[ Ever wondered if it is possible to execute an object file without linking? Or use any object file as a library? Follow along to learn how to decompose an object file and import code from it along the way. ]]></description>
            <content:encoded><![CDATA[ <p></p>
    <div>
      <h2>Calling a simple function without linking</h2>
      <a href="#calling-a-simple-function-without-linking">
        
      </a>
    </div>
    <p>When we write software using a high-level compiled programming language, there are usually a number of steps involved in transforming our source code into the final executable binary:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3RNZmg4aDxjJOAjAmYxFk9/c1279a03f8962b5bb74f812b66a4a448/build.png" />
            
            </figure><p>First, our source files are compiled by a <i>compiler</i> translating the high-level programming language into machine code. The output of the compiler is a number of <i>object</i> files. If the project contains multiple source files, we usually get as many object files. The next step is the <i>linker</i>: since the code in different object files may reference each other, the linker is responsible for assembling all these object files into one big program and binding these references together. The output of the linker is usually our target executable, so only one file.</p><p>However, at this point, our executable might still be incomplete. These days, most executables on Linux are dynamically linked: the executable itself does not have all the code it needs to run a program. Instead it expects to "borrow" part of the code at runtime from <a href="https://en.wikipedia.org/wiki/Library_(computing)#Shared_libraries">shared libraries</a> for some of its functionality:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4qgviYFjrBDkGj5OckqJFi/a94b46caad8bfcda8eb96ad7eba84d67/runtime.png" />
            
            </figure><p>This process is called <i>runtime linking</i>: when our executable is being started, the operating system will invoke the <i>dynamic loader</i>, which should find all the needed libraries, copy/map their code into our target process address space, and resolve all the dependencies our code has on them.</p><p>One interesting thing to note about this overall process is that we get the executable machine code directly from step 1 (compiling the source code), but if any of the later steps fail, we still can't execute our program. So, in this series of blog posts we will investigate if it is possible to execute machine code directly from object files skipping all the later steps.</p>
    <div>
      <h4>Why would we want to execute an object file?</h4>
      <a href="#why-would-we-want-to-execute-an-object-file">
        
      </a>
    </div>
    <p>There may be many reasons. Perhaps we're writing an open-source replacement for a proprietary Linux driver or an application, and want to compare if the behaviour of some code is the same. Or we have a piece of a rare, obscure program and we can't link to it, because it was compiled with a rare, obscure compiler. Maybe we have a source file, but cannot create a full featured executable, because of the missing build time or runtime dependencies. Malware analysis, code from a different operating system etc - all these scenarios may put us in a position, where either linking is not possible or the runtime environment is not suitable.</p>
    <div>
      <h3>A simple toy object file</h3>
      <a href="#a-simple-toy-object-file">
        
      </a>
    </div>
    <p>For the purposes of this article, let's create a simple toy object file, so we can use it in our experiments:</p><p><i>obj.c</i>:</p>
            <pre><code>int add5(int num)
{
    return num + 5;
}

int add10(int num)
{
    return num + 10;
}</code></pre>
            <p>Our source file contains only 2 functions, <code>add5</code> and <code>add10</code>, which adds 5 or 10 respectively to the only input parameter. It's a small but fully functional piece of code, and we can easily compile it into an object file:</p>
            <pre><code>$ gcc -c obj.c 
$ ls
obj.c  obj.o</code></pre>
            
    <div>
      <h3>Loading an object file into the process memory</h3>
      <a href="#loading-an-object-file-into-the-process-memory">
        
      </a>
    </div>
    <p>Now we will try to import the <code>add5</code> and <code>add10</code> functions from the object file and execute them. When we talk about executing an object file, we mean using an object file as some sort of a library. As we learned above, when we have an executable that utilises external shared libraries, the <i>dynamic loader</i> loads these libraries into the process address space for us. With object files, however, we have to do this manually, because ultimately we can't execute machine code that doesn't reside in the operating system's RAM. So, to execute object files we still need some kind of a wrapper program:</p><p><i>loader.c</i>:</p>
            <pre><code>#include &lt;stdio.h&gt;
#include &lt;stdint.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;

static void load_obj(void)
{
    /* load obj.o into memory */
}

static void parse_obj(void)
{
    /* parse an object file and find add5 and add10 functions */
}

static void execute_funcs(void)
{
    /* execute add5 and add10 with some inputs */
}

int main(void)
{
    load_obj();
    parse_obj();
    execute_funcs();

    return 0;
}</code></pre>
            <p>Above is a self-contained object loader program with some functions as placeholders. We will be implementing these functions (and adding more) in the course of this post.</p><p>First, as we established already, we need to load our object file into the process address space. We could just read the whole file into a buffer, but that would not be very efficient. Real-world object files might be big, but as we will see later, we don't need all of the object's file contents. So it is better to <a href="https://man7.org/linux/man-pages/man2/mmap.2.html"><code>mmap</code></a> the file instead: this way the operating system will lazily read the parts from the file we need at the time we need them. Let's implement the <code>load_obj</code> function:</p><p><i>loader.c</i>:</p>
            <pre><code>...
/* for open(2), fstat(2) */
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;

/* for close(2), fstat(2) */
#include &lt;unistd.h&gt;

/* for mmap(2) */
#include &lt;sys/mman.h&gt;

/* parsing ELF files */
#include &lt;elf.h&gt;

/* for errno */
#include &lt;errno.h&gt;

typedef union {
    const Elf64_Ehdr *hdr;
    const uint8_t *base;
} objhdr;

/* obj.o memory address */
static objhdr obj;

static void load_obj(void)
{
    struct stat sb;

    int fd = open("obj.o", O_RDONLY);
    if (fd &lt;= 0) {
        perror("Cannot open obj.o");
        exit(errno);
    }

    /* we need obj.o size for mmap(2) */
    if (fstat(fd, &amp;sb)) {
        perror("Failed to get obj.o info");
        exit(errno);
    }

    /* mmap obj.o into memory */
    obj.base = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    if (obj.base == MAP_FAILED) {
        perror("Maping obj.o failed");
        exit(errno);
    }
    close(fd);
}
...</code></pre>
            <p>If we don't encounter any errors, after <code>load_obj</code> executes we should get the memory address, which points to the beginning of our <code>obj.o</code> in the <code>obj</code> global variable. It is worth noting we have created a special union type for the <code>obj</code> variable: we will be parsing <code>obj.o</code> later (and peeking ahead - object files are actually <a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">ELF files</a>), so will be referring to the address both as <code>Elf64_Ehdr</code> (ELF header structure in C) and a byte pointer (parsing ELF files involves calculations of byte offsets from the beginning of the file).</p>
    <div>
      <h3>A peek inside an object file</h3>
      <a href="#a-peek-inside-an-object-file">
        
      </a>
    </div>
    <p>To use some code from an object file, we need to find it first. As I've leaked above, object files are actually <a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">ELF files</a> (the same format as Linux executables and shared libraries) and luckily they’re easy to parse on Linux with the help of the standard <code>elf.h</code> header, which includes many useful definitions related to the ELF file structure. But we actually need to know what we’re looking for, so a high-level understanding of an ELF file is needed.</p>
    <div>
      <h4>ELF segments and sections</h4>
      <a href="#elf-segments-and-sections">
        
      </a>
    </div>
    <p>Segments (also known as program headers) and sections are probably the main parts of an ELF file and usually a starting point of any ELF tutorial. However, there is often some confusion between the two. Different sections contain different types of ELF data: executable code (which we are most interested in in this post), constant data, global variables etc. Segments, on the other hand, do not contain any data themselves - they just describe to the operating system how to properly load sections into RAM for the executable to work correctly. Some tutorials say "a segment may include 0 or more sections", which is not entirely accurate: segments do not contain sections, rather they just indicate to the OS where in memory a particular section should be loaded and what is the access pattern for this memory (read, write or execute):</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/7E14iCMhz5ADpvhMnfyswT/8f97b1cf69b5788744e31b1a5853c8db/segments-sections.png" />
            
            </figure><p>Furthermore, object files do not contain any segments at all: an object file is not meant to be directly loaded by the OS. Instead, it is assumed it will be linked with some other code, so ELF segments are usually generated by the linker, not the compiler. We can check this by using the <a href="https://man7.org/linux/man-pages/man1/readelf.1.html">readelf command</a>:</p>
            <pre><code>$ readelf --segments obj.o

There are no program headers in this file.</code></pre>
            
    <div>
      <h4>Object file sections</h4>
      <a href="#object-file-sections">
        
      </a>
    </div>
    <p>The same <a href="https://man7.org/linux/man-pages/man1/readelf.1.html">readelf command</a> can be used to get all the sections from our object file:</p>
            <pre><code>$ readelf --sections obj.o
There are 11 section headers, starting at offset 0x268:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .text             PROGBITS         0000000000000000  00000040
       000000000000001e  0000000000000000  AX       0     0     1
  [ 2] .data             PROGBITS         0000000000000000  0000005e
       0000000000000000  0000000000000000  WA       0     0     1
  [ 3] .bss              NOBITS           0000000000000000  0000005e
       0000000000000000  0000000000000000  WA       0     0     1
  [ 4] .comment          PROGBITS         0000000000000000  0000005e
       000000000000001d  0000000000000001  MS       0     0     1
  [ 5] .note.GNU-stack   PROGBITS         0000000000000000  0000007b
       0000000000000000  0000000000000000           0     0     1
  [ 6] .eh_frame         PROGBITS         0000000000000000  00000080
       0000000000000058  0000000000000000   A       0     0     8
  [ 7] .rela.eh_frame    RELA             0000000000000000  000001e0
       0000000000000030  0000000000000018   I       8     6     8
  [ 8] .symtab           SYMTAB           0000000000000000  000000d8
       00000000000000f0  0000000000000018           9     8     8
  [ 9] .strtab           STRTAB           0000000000000000  000001c8
       0000000000000012  0000000000000000           0     0     1
  [10] .shstrtab         STRTAB           0000000000000000  00000210
       0000000000000054  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  l (large), p (processor specific)</code></pre>
            <p>There are different tutorials online describing the most popular ELF sections in detail. Another great reference is the <a href="https://man7.org/linux/man-pages/man5/elf.5.html">Linux manpages project</a>. It is handy because it describes both sections’ purpose as well as C structure definitions from <code>elf.h</code>, which makes it a one-stop shop for parsing ELF files. However, for completeness, below is a short description of the most popular sections one may encounter in an ELF file:</p><ul><li><p><code>.text</code>: this section contains the executable code (the actual machine code, which was created by the compiler from our source code). This section is the primary area of interest for this post as it should contain the <code>add5</code> and <code>add10</code> functions we want to use.</p></li><li><p><code>.data</code> and <code>.bss</code>: these sections contain global and static local variables. The difference is: <code>.data</code> has variables with an initial value (defined like <code>int foo = 5;</code>) and <code>.bss</code> just reserves space for variables with no initial value (defined like <code>int bar;</code>).</p></li><li><p><code>.rodata</code>: this section contains constant data (mostly strings or byte arrays). For example, if we use a string literal in the code (for example, for <code>printf</code> or some error message), it will be stored here. Note, that <code>.rodata</code> is missing from the output above as we didn't use any string literals or constant byte arrays in <code>obj.c</code>.</p></li><li><p><code>.symtab</code>: this section contains information about the symbols in the object file: functions, global variables, constants etc. It may also contain information about external symbols the object file needs, like needed functions from the external libraries.</p></li><li><p><code>.strtab</code> and <code>.shstrtab</code>: contain packed strings for the ELF file. Note, that these are not the strings we may define in our source code (those go to the <code>.rodata</code> section). These are the strings describing the names of other ELF structures, like symbols from <code>.symtab</code> or even section names from the table above. ELF binary format aims to make its structures compact and of a fixed size, so all strings are stored in one place and the respective data structures just reference them as an offset in either <code>.shstrtab</code> or <code>.strtab</code> sections instead of storing the full string locally.</p></li></ul>
    <div>
      <h4>The <code>.symtab</code> section</h4>
      <a href="#the-symtab-section">
        
      </a>
    </div>
    <p>At this point, we know that the code we want to import and execute is located in the <code>obj.o</code>'s <code>.text</code> section. But we have two functions, <code>add5</code> and <code>add10</code>, remember? At this level the <code>.text</code> section is just a byte blob - how do we know where each of these functions is located? This is where the <code>.symtab</code> (the "symbol table") comes in handy. It is so important that it has its own dedicated parameter in <a href="https://man7.org/linux/man-pages/man1/readelf.1.html">readelf</a>:</p>
            <pre><code>$ readelf --symbols obj.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS obj.c
     2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1
     3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2
     4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3
     5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5
     6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6
     7: 0000000000000000     0 SECTION LOCAL  DEFAULT    4
     8: 0000000000000000    15 FUNC    GLOBAL DEFAULT    1 add5
     9: 000000000000000f    15 FUNC    GLOBAL DEFAULT    1 add10</code></pre>
            <p>Let's ignore the other entries for now and just focus on the last two lines, because they conveniently have <code>add5</code> and <code>add10</code> as their symbol names. And indeed, this is the info about our functions. Apart from the names, the symbol table provides us with some additional metadata:</p><ul><li><p>The <code>Ndx</code> column tells us the index of the section, where the symbol is located. We can cross-check it with the section table above and confirm that indeed these functions are located in <code>.text</code> (section with the index <code>1</code>).</p></li><li><p><code>Type</code> being set to <code>FUNC</code> confirms that these are indeed functions.</p></li><li><p><code>Size</code> tells us the size of each function, but this information is not very useful in our context. The same goes for <code>Bind</code> and <code>Vis</code>.</p></li><li><p>Probably the most useful piece of information is <code>Value</code>. The name is misleading, because it is actually an offset from the start of the containing section in this context. That is, the <code>add5</code> function starts just from the beginning of <code>.text</code> and <code>add10</code> is located from 15th byte and onwards.</p></li></ul><p>So now we have all the pieces on how to parse an ELF file and find the functions we need.</p>
    <div>
      <h3>Finding and executing a function from an object file</h3>
      <a href="#finding-and-executing-a-function-from-an-object-file">
        
      </a>
    </div>
    <p>Given what we have learned so far, let's define a plan on how to proceed to import and execute a function from an object file:</p><ol><li><p>Find the ELF sections table and <code>.shstrtab</code> section (we need <code>.shstrtab</code> later to lookup sections in the section table by name).</p></li><li><p>Find the <code>.symtab</code> and <code>.strtab</code> sections (we need <code>.strtab</code> to lookup symbols by name in <code>.symtab</code>).</p></li><li><p>Find the <code>.text</code> section and copy it into RAM with executable permissions.</p></li><li><p>Find <code>add5</code> and <code>add10</code> function offsets from the <code>.symtab</code>.</p></li><li><p>Execute <code>add5</code> and <code>add10</code> functions.</p></li></ol><p>Let's start by adding some more global variables and implementing the <code>parse_obj</code> function:</p><p><i>loader.c</i>:</p>
            <pre><code>...

/* sections table */
static const Elf64_Shdr *sections;
static const char *shstrtab = NULL;

/* symbols table */
static const Elf64_Sym *symbols;
/* number of entries in the symbols table */
static int num_symbols;
static const char *strtab = NULL;

...

static void parse_obj(void)
{
    /* the sections table offset is encoded in the ELF header */
    sections = (const Elf64_Shdr *)(obj.base + obj.hdr-&gt;e_shoff);
    /* the index of `.shstrtab` in the sections table is encoded in the ELF header
     * so we can find it without actually using a name lookup
     */
    shstrtab = (const char *)(obj.base + sections[obj.hdr-&gt;e_shstrndx].sh_offset);

...
}

...</code></pre>
            <p>Now that we have references to both the sections table and the <code>.shstrtab</code> section, we can lookup other sections by their name. Let's create a helper function for that:</p><p><i>loader.c</i>:</p>
            <pre><code>...

static const Elf64_Shdr *lookup_section(const char *name)
{
    size_t name_len = strlen(name);

    /* number of entries in the sections table is encoded in the ELF header */
    for (Elf64_Half i = 0; i &lt; obj.hdr-&gt;e_shnum; i++) {
        /* sections table entry does not contain the string name of the section
         * instead, the `sh_name` parameter is an offset in the `.shstrtab`
         * section, which points to a string name
         */
        const char *section_name = shstrtab + sections[i].sh_name;
        size_t section_name_len = strlen(section_name);

        if (name_len == section_name_len &amp;&amp; !strcmp(name, section_name)) {
            /* we ignore sections with 0 size */
            if (sections[i].sh_size)
                return sections + i;
        }
    }

    return NULL;
}

...</code></pre>
            <p>Using our new helper function, we can now find the <code>.symtab</code> and <code>.strtab</code> sections:</p><p><i>loader.c</i>:</p>
            <pre><code>...

static void parse_obj(void)
{
...

    /* find the `.symtab` entry in the sections table */
    const Elf64_Shdr *symtab_hdr = lookup_section(".symtab");
    if (!symtab_hdr) {
        fputs("Failed to find .symtab\n", stderr);
        exit(ENOEXEC);
    }

    /* the symbols table */
    symbols = (const Elf64_Sym *)(obj.base + symtab_hdr-&gt;sh_offset);
    /* number of entries in the symbols table = table size / entry size */
    num_symbols = symtab_hdr-&gt;sh_size / symtab_hdr-&gt;sh_entsize;

    const Elf64_Shdr *strtab_hdr = lookup_section(".strtab");
    if (!strtab_hdr) {
        fputs("Failed to find .strtab\n", stderr);
        exit(ENOEXEC);
    }

    strtab = (const char *)(obj.base + strtab_hdr-&gt;sh_offset);
    
...
}

...</code></pre>
            <p>Next, let's focus on the <code>.text</code> section. We noted earlier in our plan that it is not enough to just locate the <code>.text</code> section in the object file, like we did with other sections. We would need to copy it over to a different location in RAM with executable permissions. There are several reasons for that, but these are the main ones:</p><ul><li><p>Many CPU architectures either don't allow execution of the machine code, which is <a href="https://en.wikipedia.org/wiki/Page_(computer_memory)">unaligned in memory</a> (4 kilobytes for x86 systems), or they execute it with a performance penalty. However, the <code>.text</code> section in an ELF file is not guaranteed to be positioned at a page aligned offset, because the on-disk version of the ELF file aims to be compact rather than convenient.</p></li><li><p>We may need to modify some bytes in the <code>.text</code> section to perform relocations (we don't need to do it in this case, but will be dealing with relocations in future posts). If, for example, we forget to use the <code>MAP_PRIVATE</code> flag, when mapping the ELF file, our modifications may propagate to the underlying file and corrupt it.</p></li><li><p>Finally, different sections, which are needed at runtime, like <code>.text</code>, <code>.data</code>, <code>.bss</code> and <code>.rodata</code>, require different memory permission bits: the <code>.text</code> section memory needs to be both readable and executable, but not writable (it is considered a bad security practice to have memory both writable and executable). The <code>.data</code> and <code>.bss</code> sections need to be readable and writable to support global variables, but not executable. The <code>.rodata</code> section should be readonly, because its purpose is to hold constant data. To support this, each section must be allocated on a page boundary as we can only set memory permission bits on whole pages and not custom ranges. Therefore, we need to create new, page aligned memory ranges for these sections and copy the data there.</p></li></ul><p>To create a page aligned copy of the <code>.text</code> section, first we actually need to know the page size. Many programs usually just hardcode the page size to 4096 (4 kilobytes), but we shouldn't rely on that. While it's accurate for most x86 systems, other CPU architectures, like arm64, might have a different page size. So hard coding a page size may make our program non-portable. Let's find the page size and store it in another global variable:</p><p><i>loader.c</i>:</p>
            <pre><code>...

static uint64_t page_size;

static inline uint64_t page_align(uint64_t n)
{
    return (n + (page_size - 1)) &amp; ~(page_size - 1);
}

...

static void parse_obj(void)
{
...

    /* get system page size */
    page_size = sysconf(_SC_PAGESIZE);

...
}

...</code></pre>
            <p>Notice, we have also added a convenience function <code>page_align</code>, which will round up the passed in number to the next page aligned boundary. Next, back to the <code>.text</code> section. As a reminder, we need to:</p><ol><li><p>Find the <code>.text</code> section metadata in the sections table.</p></li><li><p>Allocate a chunk of memory to hold the <code>.text</code> section copy.</p></li><li><p>Actually copy the <code>.text</code> section to the newly allocated memory.</p></li><li><p>Make the <code>.text</code> section executable, so we can later call functions from it.</p></li></ol><p>Here is the implementation of the above steps:</p><p><i>loader.c</i>:</p>
            <pre><code>...

/* runtime base address of the imported code */
static uint8_t *text_runtime_base;

...

static void parse_obj(void)
{
...

    /* find the `.text` entry in the sections table */
    const Elf64_Shdr *text_hdr = lookup_section(".text");
    if (!text_hdr) {
        fputs("Failed to find .text\n", stderr);
        exit(ENOEXEC);
    }

    /* allocate memory for `.text` copy rounding it up to whole pages */
    text_runtime_base = mmap(NULL, page_align(text_hdr-&gt;sh_size), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (text_runtime_base == MAP_FAILED) {
        perror("Failed to allocate memory for .text");
        exit(errno);
    }

    /* copy the contents of `.text` section from the ELF file */
    memcpy(text_runtime_base, obj.base + text_hdr-&gt;sh_offset, text_hdr-&gt;sh_size);

    /* make the `.text` copy readonly and executable */
    if (mprotect(text_runtime_base, page_align(text_hdr-&gt;sh_size), PROT_READ | PROT_EXEC)) {
        perror("Failed to make .text executable");
        exit(errno);
    }
}

...</code></pre>
            <p>Now we have all the pieces we need to locate the address of a function. Let's write a helper for it:</p><p><i>loader.c</i>:</p>
            <pre><code>...

static void *lookup_function(const char *name)
{
    size_t name_len = strlen(name);

    /* loop through all the symbols in the symbol table */
    for (int i = 0; i &lt; num_symbols; i++) {
        /* consider only function symbols */
        if (ELF64_ST_TYPE(symbols[i].st_info) == STT_FUNC) {
            /* symbol table entry does not contain the string name of the symbol
             * instead, the `st_name` parameter is an offset in the `.strtab`
             * section, which points to a string name
             */
            const char *function_name = strtab + symbols[i].st_name;
            size_t function_name_len = strlen(function_name);

            if (name_len == function_name_len &amp;&amp; !strcmp(name, function_name)) {
                /* st_value is an offset in bytes of the function from the
                 * beginning of the `.text` section
                 */
                return text_runtime_base + symbols[i].st_value;
            }
        }
    }

    return NULL;
}

...</code></pre>
            <p>And finally we can implement the <code>execute_funcs</code> function to import and execute code from an object file:</p><p><i>loader.c</i>:</p>
            <pre><code>...

static void execute_funcs(void)
{
    /* pointers to imported add5 and add10 functions */
    int (*add5)(int);
    int (*add10)(int);

    add5 = lookup_function("add5");
    if (!add5) {
        fputs("Failed to find add5 function\n", stderr);
        exit(ENOENT);
    }

    puts("Executing add5...");
    printf("add5(%d) = %d\n", 42, add5(42));

    add10 = lookup_function("add10");
    if (!add10) {
        fputs("Failed to find add10 function\n", stderr);
        exit(ENOENT);
    }

    puts("Executing add10...");
    printf("add10(%d) = %d\n", 42, add10(42));
}

...</code></pre>
            <p>Let's compile our loader and make sure it works as expected:</p>
            <pre><code>$ gcc -o loader loader.c 
$ ./loader 
Executing add5...
add5(42) = 47
Executing add10...
add10(42) = 52</code></pre>
            <p>Voila! We have successfully imported code from <code>obj.o</code> and executed it. Of course, the example above is simplified: the code in the object file is self-contained, does not reference any global variables or constants, and does not have any external dependencies. In future posts we will look into more complex code and how to handle such cases.</p>
    <div>
      <h4>Security considerations</h4>
      <a href="#security-considerations">
        
      </a>
    </div>
    <p>Processing external inputs, like parsing an ELF file from the disk above, should be handled with care. The code from <i>loader.c</i> omits a lot of bounds checking and additional ELF integrity checks, when parsing the object file. The code is simplified for the purposes of this post, but most likely not production ready, as it can probably be exploited by specifically crafted malicious inputs. Use it only for educational purposes!</p><p>The complete source code from this post can be found <a href="https://github.com/cloudflare/cloudflare-blog/tree/master/2021-03-obj-file/1">here</a>.</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Programming]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">73aVJfyo4dMAfJku0Q859K</guid>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
        <item>
            <title><![CDATA[Sandboxing in Linux with zero lines of code]]></title>
            <link>https://blog.cloudflare.com/sandboxing-in-linux-with-zero-lines-of-code/</link>
            <pubDate>Wed, 08 Jul 2020 11:00:00 GMT</pubDate>
            <description><![CDATA[ In this post we will review Linux seccomp and learn how to sandbox any (even a proprietary) application without writing a single line of code. ]]></description>
            <content:encoded><![CDATA[ <p>Modern Linux operating systems provide many tools to run code more securely. There are <a href="https://www.man7.org/linux/man-pages/man7/namespaces.7.html">namespaces</a> (the basic building blocks for containers), <a href="https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html">Linux Security Modules</a>, <a href="https://wiki.gentoo.org/wiki/Integrity_Measurement_Architecture">Integrity Measurement Architecture</a> etc.</p><p>In this post we will review <a href="https://www.kernel.org/doc/html/latest/userspace-api/seccomp_filter.html">Linux seccomp</a> and learn how to sandbox any (even a proprietary) application without writing a single line of code.</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4I6LYA0Qq7KBBc5vj9LDV0/eb6f886ee659efc0b3abd38b39024e2f/linux-sandbox-1.jpg" />
            
            </figure><p><a href="https://www.deviantart.com/qubodup/art/Tux-Flat-SVG-607655623">Tux by Iwan Gabovitch, GPL</a><a href="https://pixabay.com/vectors/sandpit-sandbox-container-sand-35536/">Sandbox, Simplified Pixabay License</a></p>
    <div>
      <h2>Linux system calls</h2>
      <a href="#linux-system-calls">
        
      </a>
    </div>
    <p>System calls (syscalls) is a well-defined interface between <a href="https://en.wikipedia.org/wiki/User_space">userspace applications</a> and the <a href="https://en.wikipedia.org/wiki/Kernel_(operating_system)">operating system (OS) kernel</a>. On modern operating systems most applications provide only application-specific logic as code. Applications do not, and most of the time cannot, directly access low-level hardware or networking, when they need to store data or send something over the wire. Instead they use system calls to ask the OS kernel to do specific hardware and networking tasks on their behalf:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2t6kOXlTPfyGwZ5IEPq07g/03b56292c1136c5fb3f7694e3e44fcdd/image2-2.png" />
            
            </figure><p>Apart from providing a generic high level way for applications to interact with the low level hardware, the system call architecture allows the OS kernel to manage available resources between applications as well as enforce policies, like application permissions, networking access control lists etc.</p>
    <div>
      <h2>Linux seccomp</h2>
      <a href="#linux-seccomp">
        
      </a>
    </div>
    <p>Linux seccomp is <a href="https://www.man7.org/linux/man-pages/man2/seccomp.2.html">yet another syscall</a> on Linux, but it is a bit special, because it influences how the OS kernel will behave when the application uses other system calls. By default, the OS kernel has almost no insight into userspace application logic, so it provides all the possible services it can. But not all applications require all services. Consider an application which converts image formats: it needs the ability to read and write data from disk, but in its simplest form probably does not need any network access. Using seccomp an application can declare its intentions in advance to the Linux kernel. For this particular case it can notify the kernel that it will be using the <a href="https://www.man7.org/linux/man-pages/man2/read.2.html">read</a> and <a href="https://www.man7.org/linux/man-pages/man2/write.2.html">write</a> system calls, but never the <a href="https://www.man7.org/linux/man-pages/man2/send.2.html">send</a> and <a href="https://www.man7.org/linux/man-pages/man2/recv.2.html">recv</a> system calls (because its intent is to work with local files and never with the network). It’s like establishing a contract between the application and the OS kernel:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6X06iaPGGtyi9O1geWqZq8/8bb6c0006df4e30c5d5de2ccd31ecf5f/image1-4.png" />
            
            </figure><p>But what happens if the application later breaks the contract and tries to use one of the system calls it promised not to use? The kernel will “penalise” the application, usually by immediately terminating it. Linux seccomp also <a href="https://www.man7.org/linux/man-pages/man2/seccomp.2.html">allows less restrictive actions</a> for the kernel to take:</p><ul><li><p>instead of terminating the whole application, the kernel can be requested to terminate only the thread, which issued the prohibited system call</p></li><li><p>the kernel may just send a <code>SIGSYS</code> <a href="https://man7.org/linux/man-pages/man7/signal.7.html">signal</a> to the calling thread</p></li><li><p>the seccomp policy can specify an error code, which the kernel will then return to the calling application instead of executing the prohibited system call</p></li><li><p>if the violating process is under <a href="https://man7.org/linux/man-pages/man2/ptrace.2.html">ptrace</a> (for example executing under a debugger), the kernel can notify the tracer (the debugger) that a prohibited system call is about to happen and let the debugger decide what to do</p></li><li><p>the kernel may be instructed to allow and execute the system call, but log the attempt: this is useful, when we want to verify that our seccomp policy is not too tight without the risk of terminating the application and potentially creating an outage</p></li></ul><p>Although there is a lot of flexibility in defining the potential penalty for the application, from a security perspective it is usually best to stick with the complete application termination upon seccomp policy violation. The reason for that will be described later in the examples in the post.</p><p>So why would the application take the risk of being abruptly terminated and declare its intentions beforehand, if it can just be “silent” and the OS kernel will allow it to use any system call by default? Of course, for a normal behaving application it makes no sense, but it turns out this feature is quite effective to protect from rogue applications and <a href="https://en.wikipedia.org/wiki/Arbitrary_code_execution">arbitrary code execution</a> exploits.</p><p>Imagine our image format converter is written in some unsafe language and <a href="https://imagetragick.com/">an attacker was able to take control of the application by making it process some malformed image</a>. What the attacker might do is to try to steal some sensitive information from the machine running our converter and send it to themselves via the network. By default, the OS kernel will most likely allow it and a data leak will happen. But if our image converter “confined” (or sandboxed) itself beforehand to only read and write local data the kernel will terminate the application when the latter tries to leak the data over the network thus preventing the leak and locking out the attacker from our system!</p>
    <div>
      <h2>Integrating seccomp into the application</h2>
      <a href="#integrating-seccomp-into-the-application">
        
      </a>
    </div>
    <p>To see how seccomp can be used in practice, let’s consider a toy example program</p><p><i>myos.c:</i></p>
            <pre><code>#include &lt;stdio.h&gt;
#include &lt;sys/utsname.h&gt;

int main(void)
{
    struct utsname name;

    if (uname(&amp;name)) {
        perror("uname failed: ");
        return 1;
    }

    printf("My OS is %s!\n", name.sysname);
    return 0;
}</code></pre>
            <p></p><p>This is a simplified version of the <a href="https://www.man7.org/linux/man-pages/man1/uname.1.html">uname command line tool</a>, which just prints your operating system name. Like its full-featured counterpart, it uses the <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">uname system call</a> to actually get the name of the current operating system from the kernel. Let’s see it action:</p>
            <pre><code>$ gcc -o myos myos.c
$ ./myos
My OS is Linux!</code></pre>
            <p></p><p>Great! We’re on Linux, so can further experiment with <a href="https://www.man7.org/linux/man-pages/man2/seccomp.2.html">seccomp</a> (it is a Linux-only feature). Notice that we’re properly handling the error code after invoking the <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">uname system call</a>. However, according to the <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">man page</a> it can only fail, when the passed in buffer pointer is invalid. And in this case the set error number will be “EINVAL”, which translates to invalid parameter. In our case, the “struct utsname” structure is being allocated on the stack, so our pointer will always be valid. In other words, in normal circumstances the <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">uname system call</a> should never fail in this particular program.</p><p>To illustrate seccomp capabilities we will add a “sandbox” function to our program before the main logic</p><p><i>myos_raw_seccomp.c:</i></p>
            <pre><code>#include &lt;linux/seccomp.h&gt;
#include &lt;linux/filter.h&gt;
#include &lt;linux/audit.h&gt;
#include &lt;sys/ptrace.h&gt;
#include &lt;sys/prctl.h&gt;

#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stddef.h&gt;
#include &lt;sys/utsname.h&gt;
#include &lt;errno.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/syscall.h&gt;

static void sandbox(void)
{
    struct sock_filter filter[] = {
        /* seccomp(2) says we should always check the arch */
        /* as syscalls may have different numbers on different architectures */
        /* see https://fedora.juszkiewicz.com.pl/syscalls.html */
        /* for simplicity we only allow x86_64 */
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, arch))),
        /* if not x86_64, tell the kernel to kill the process */
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, AUDIT_ARCH_X86_64, 0, 4),
        /* get the actual syscall number */
        BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
        /* if "uname", tell the kernel to return EPERM, otherwise just allow */
        BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SYS_uname, 0, 1),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ERRNO | (EPERM &amp; SECCOMP_RET_DATA)),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
        BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
    };

    struct sock_fprog prog = {
        .len = (unsigned short) (sizeof(filter) / sizeof(filter[0])),
        .filter = filter,
    };

    /* see seccomp(2) on why this is needed */
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("PR_SET_NO_NEW_PRIVS failed");
        exit(1);
    };

    /* glibc does not have a wrapper for seccomp(2) */
    /* invoke it via the generic syscall wrapper */
    if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &amp;prog)) {
        perror("seccomp failed");
        exit(1);
    };
}

int main(void)
{
    struct utsname name;

    sandbox();

    if (uname(&amp;name)) {
        perror("uname failed");
        return 1;
    }

    printf("My OS is %s!\n", name.sysname);
    return 0;
}</code></pre>
            <p></p><p>To sandbox itself the application defines a <a href="https://www.kernel.org/doc/Documentation/networking/filter.txt">BPF program</a>, which implements the desired sandboxing policy. Then the application passes this program to the kernel via the <a href="https://www.man7.org/linux/man-pages/man2/seccomp.2.html">seccomp</a> system call. The kernel does some validation checks to ensure the BPF program is OK and then runs this program on every system call the application makes. The results of the execution of the program is used by the kernel to determine if the current call complies with the desired policy. In other words the BPF program is the “contract” between the application and the kernel.</p><p>In our toy example above, the BPF program simply checks which system call is about to be invoked. If the application is trying to use the <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">uname system call</a> we tell the kernel to just return a EPERM (which stands for “operation not permitted”) error code. We also tell the kernel to allow any other system call. Let’s see if it works now:</p>
            <pre><code>$ gcc -o myos myos_raw_seccomp.c
$ ./myos
uname failed: Operation not permitted</code></pre>
            <p><code></code></p><p><code>uname</code> failed now with the EPERM error code and EPERM is not even described as a potential failure code in the <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">uname manpage</a>! So we know now that this happened because we “told” the kernel to prohibit us using the uname syscall and to return EPERM instead. We can double check this by replacing EPERM with some other error code, which is totally inappropriate for this context, for example ENETDOWN (“network is down”). Why would we need the network to be up to just get the currently executing OS? Yet, recompiling and rerunning the program we get:</p>
            <pre><code>$ gcc -o myos myos_raw_seccomp.c
$ ./myos
uname failed: Network is down</code></pre>
            <p></p><p>We can also verify the other part of our “contract” works as expected. We told the kernel to allow any other system call, remember? In our program, when uname fails, we convert the error code to a human readable message and print it on the screen with the <a href="https://www.man7.org/linux/man-pages/man3/perror.3.html">perror</a> function. To print on the screen <a href="https://www.man7.org/linux/man-pages/man3/perror.3.html">perror</a> uses the <a href="https://www.man7.org/linux/man-pages/man2/write.2.html">write system call</a> under the hood and since we can actually see the printed error message, we know that the kernel allowed our program to make the <a href="https://www.man7.org/linux/man-pages/man2/write.2.html">write system call</a> in the first place.</p>
    <div>
      <h3>seccomp with libseccomp</h3>
      <a href="#seccomp-with-libseccomp">
        
      </a>
    </div>
    <p>While it is possible to use seccomp directly, as in the examples above, BPF programs are cumbersome to write by hand and hard to debug, review and update later. That’s why it is usually a good idea to use a more high-level library, which abstracts away most of the low-level details. Luckily <a href="https://github.com/seccomp/libseccomp">such a library exists</a>: it is called libseccomp and is even recommended by the <a href="https://www.man7.org/linux/man-pages/man2/seccomp.2.html">seccomp man page</a>.</p><p>Let’s rewrite our program’s <code>sandbox()</code> function to use this library instead:</p><p><i>myos_libseccomp.c:</i></p>
            <pre><code>#define _GNU_SOURCE
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;sys/utsname.h&gt;
#include &lt;seccomp.h&gt;
#include &lt;err.h&gt;

static void sandbox(void)
{
    /* allow all syscalls by default */
    scmp_filter_ctx seccomp_ctx = seccomp_init(SCMP_ACT_ALLOW);
    if (!seccomp_ctx)
        err(1, "seccomp_init failed");

    /* kill the process, if it tries to use "uname" syscall */
    if (seccomp_rule_add_exact(seccomp_ctx, SCMP_ACT_KILL, seccomp_syscall_resolve_name("uname"), 0)) {
        perror("seccomp_rule_add_exact failed");
        exit(1);
    }

    /* apply the composed filter */
    if (seccomp_load(seccomp_ctx)) {
        perror("seccomp_load failed");
        exit(1);
    }

    /* release allocated context */
    seccomp_release(seccomp_ctx);
}

int main(void)
{
    struct utsname name;

    sandbox();

    if (uname(&amp;name)) {
        perror("uname failed: ");
        return 1;
    }

    printf("My OS is %s!\n", name.sysname);
    return 0;
}</code></pre>
            <p></p><p>Our <code>sandbox()</code> function not only became shorter and much more readable, but also provided the ability to reference syscalls in our rules by names and not internal numbers as well as not having to deal with other quirks, like setting <code>PR_SET_NO_NEW_PRIVS</code> bit and dealing with system architectures.</p><p>It is worth noting we have modified our seccomp policy a bit. In the raw seccomp example above we instructed the kernel to return an error code when the application tries to execute a prohibited syscall. This is good for demonstration purposes, but in most cases a stricter action is required. Just returning an error code and allowing the application to continue gives the potentially malicious code a chance to bypass the policy. There are many syscalls in Linux and some of them do the same or similar things. For example, we might want to prohibit the application to read data from disk, so we deny the <a href="https://www.man7.org/linux/man-pages/man2/read.2.html">read</a> syscall in our policy and tell the kernel to return an error code instead. However, if the application does get exploited, the exploit code/logic might look like below:</p>
            <pre><code>…
if (-1 == read(fd, buf, count)) {
    /* hm… read failed, but what about pread? */
    if (-1 == pread(fd, buf, count, offset) {
        /* what about readv? */ ...
    }
    /* bypassed the prohibited read(2) syscall */
}
…</code></pre>
            <p></p><p>Wait what?! There is more than one read system call? Yes, there are <a href="https://www.man7.org/linux/man-pages/man2/read.2.html">read</a>, <a href="https://man7.org/linux/man-pages/man2/pread.2.html">pread</a>, <a href="https://man7.org/linux/man-pages/man2/readv.2.html">readv</a> as well as more obscure ones, like <a href="https://blog.cloudflare.com/io_submit-the-epoll-alternative-youve-never-heard-about/">io_submit</a> and <code>io_uring_enter</code>. Of course, it is our fault for providing incomplete seccomp policy, which does not block all possible read syscalls. But if at least we had instructed the kernel to terminate the process immediately upon violation of the first plain <code>read</code>, the malicious code above would not have the chance to be clever and try other options.</p><p>Given the above in the libseccomp example we have a stricter policy now, which tells the kernel to terminate the process upon the policy violation. Let’s see if it works:</p>
            <pre><code>$ gcc -o myos myos_libseccomp.c -lseccomp
$ ./myos
Bad system call</code></pre>
            <p></p><p>Notice that we need to link against <a href="https://github.com/seccomp/libseccomp">libseccomp</a> when compiling the application. Also, when we run the application, we don’t see the <code>uname failed: Operation not permitted</code> error output anymore, because we don’t give the application the ability to even print a failure message. Instead, we see a <code>Bad system call</code> message from the shell, which tells us that the application was terminated with a <code>SIGSYS</code> <a href="https://man7.org/linux/man-pages/man7/signal.7.html">signal</a>. Great!</p>
    <div>
      <h2>zero code seccomp</h2>
      <a href="#zero-code-seccomp">
        
      </a>
    </div>
    <p>The previous examples worked fine, but both of them have one disadvantage: we actually needed to modify the source code to embed our desired seccomp policy into the application. This is because <a href="https://www.man7.org/linux/man-pages/man2/seccomp.2.html">seccomp syscall</a> affects the calling process and its children, but there is no interface to inject the policy from “outside”. It is expected that developers will sandbox their code themselves as part of the application logic, but in practice this rarely happens. When developers are starting a new project, most of the time the focus is on primary functionality and security features are usually either postponed or omitted altogether. Also, most real-world software is usually written using some high-level programming language and/or a framework, where the developers do not deal with the system calls directly and probably are even unaware which system calls are being used by their code.</p><p>On the other hand we have system operators, sysadmins, SRE and other folks, who run the above code in production. They are more incentivized to keep production systems secure, thus would probably want to sandbox the services as much as possible. But most of the time they don’t have access to the source code. So there are mismatched expectations: developers have the ability to sandbox their code, but are usually not incentivized to do so and operators have the incentive to sandbox the code, but don’t have the ability.</p><p>This is where “zero code seccomp” might help, where an external operator can inject the desired sandbox policy into any process without needing to modify any source code. <a href="https://www.freedesktop.org/wiki/Software/systemd/">Systemd</a> is one of the popular implementations of a “zero code seccomp” approach. Systemd-managed services can have a <a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter="><code>SystemCallFilter=</code></a> directive defined in their <a href="https://www.freedesktop.org/software/systemd/man/systemd.service.html">unit files</a> listing all the system calls the managed service is allowed to make. As an example, let’s go back to our toy application without any sandboxing code embedded:</p>
            <pre><code>$ gcc -o myos myos.c
$ ./myos
My OS is Linux!</code></pre>
            <p></p><p>Now we can run the same code with systemd, but prohibit the application for using <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">uname</a> without changing or recompiling any code (we’re using <a href="https://www.freedesktop.org/software/systemd/man/systemd-run.html">systemd-run</a> to create an ephemeral systemd service unit for us):</p>
            <pre><code>$ systemd-run --user --pty --same-dir --wait --collect --service-type=exec --property="SystemCallFilter=~uname" ./myos
Running as unit: run-u0.service
Press ^] three times within 1s to disconnect TTY.
Finished with result: signal
Main processes terminated with: code=killed/status=SYS
Service runtime: 6ms</code></pre>
            <p></p><p>We don’t see the normal <code>My OS is Linux!</code> output anymore and systemd conveniently tells us that the managed process was terminated with a <code>SIGSYS</code> signal. We can even go further and use another directive <a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallErrorNumber="><code>SystemCallErrorNumber=</code></a> to configure our seccomp policy not to terminate the application, but return an error code instead as in our first seccomp raw example:</p>
            <pre><code>$ systemd-run --user --pty --same-dir --wait --collect --service-type=exec --property="SystemCallFilter=~uname" --property="SystemCallErrorNumber=ENETDOWN" ./myos
Running as unit: run-u2.service
Press ^] three times within 1s to disconnect TTY.
uname failed: Network is down
Finished with result: exit-code
Main processes terminated with: code=exited/status=1
Service runtime: 6ms</code></pre>
            <p></p>
    <div>
      <h3>systemd small print</h3>
      <a href="#systemd-small-print">
        
      </a>
    </div>
    <p>Great! We can now inject almost any seccomp policy into any process without the need to write any code or recompile the application. However, there is an interesting statement in the <a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=">systemd documentation</a>:</p><blockquote><p>...Note that the <code>execve, exit, exit_group, getrlimit, rt_sigreturn, sigreturn</code> system calls and the system calls for querying time and sleeping are implicitly whitelisted and do not need to be listed explicitly...</p></blockquote><p>Some system calls are implicitly allowed and we don’t have to list them. This is mostly related to the way how systemd manages processes and injects the seccomp policy. We established earlier that seccomp policy applies to the current process and its children. So, to inject the policy, systemd <a href="https://www.man7.org/linux/man-pages/man2/fork.2.html">forks</a> itself, calls <a href="https://www.man7.org/linux/man-pages/man2/seccomp.2.html">seccomp</a> in the forked process and then <a href="https://www.man7.org/linux/man-pages/man2/execve.2.html">execs</a> the forked process into the target application. That’s why always allowing the <a href="https://www.man7.org/linux/man-pages/man2/execve.2.html">execve</a> system call is necessary in the first place, because otherwise systemd cannot do its job as a service manager.</p><p>But what if we want to explicitly prohibit some of these system calls? If we continue with the <a href="https://www.man7.org/linux/man-pages/man2/execve.2.html">execve</a> as an example, that can actually be a dangerous system call most applications would want to prohibit. Seccomp is an effective tool to protect the code from arbitrary code execution exploits, remember? If a malicious actor takes over our code, most likely the first thing they will try is to get a shell (or replace our code with any other application which is easier to control) by directing our code to call <a href="https://www.man7.org/linux/man-pages/man2/execve.2.html">execve</a> with the desired binary. So, if our code does not need <a href="https://www.man7.org/linux/man-pages/man2/execve.2.html">execve</a> for its main functionality, it would be a good idea to prohibit it. Unfortunately, it is not possible with the systemd <a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter="><code>SystemCallFilter=</code></a> approach...</p>
    <div>
      <h2>Introducing Cloudflare sandbox</h2>
      <a href="#introducing-cloudflare-sandbox">
        
      </a>
    </div>
    <p>We really liked the “zero code seccomp” approach with systemd <a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter="><code>SystemCallFilter=</code></a> directive, but were not satisfied with its limitations. We decided to take it one step further and make it possible to prohibit any system call in any process externally without touching its source code, so came up with the <a href="https://github.com/cloudflare/sandbox">Cloudflare sandbox</a>. It’s a simple standalone toolkit consisting of a shared library and an executable. The shared library is supposed to be used with dynamically linked applications and the executable is for statically linked applications.</p>
    <div>
      <h3>sandboxing dynamically linked executables</h3>
      <a href="#sandboxing-dynamically-linked-executables">
        
      </a>
    </div>
    <p>For dynamically linked executables it is possible to inject custom code into the process by utilizing the <a href="https://www.man7.org/linux/man-pages/man8/ld.so.8.html"><code>LD_PRELOAD</code></a> environment variable. The <code>libsandbox.so</code> shared library from our toolkit also contains a so-called <a href="https://gcc.gnu.org/onlinedocs/gccint/Initialization.html">initialization routine</a>, which should be executed before the main logic. This is how we make the target application sandbox itself:</p><ul><li><p><a href="https://www.man7.org/linux/man-pages/man8/ld.so.8.html"><code>LD_PRELOAD</code></a> tells the dynamic loader to load our <code>libsandbox.so</code> as part of the application, when it starts</p></li><li><p>the runtime executes the <a href="https://gcc.gnu.org/onlinedocs/gccint/Initialization.html">initialization routine</a> from the <code>libsandbox.so</code> before most of the main logic</p></li><li><p>our initialization routine configures the sandbox policy described in special environment variables</p></li><li><p>by the time the main application logic begin executing, the target process has the configured seccomp policy enforced</p></li></ul><p>Let’s see how it works with our <code>myos</code> toy tool. First, we need to make sure it is actually a dynamically linked application:</p>
            <pre><code>$ ldd ./myos
	linux-vdso.so.1 (0x00007ffd8e1e3000)
	libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f339ddfb000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f339dfcf000)</code></pre>
            <p></p><p>Yes, it is . Now, let’s prohibit it from using the <a href="https://www.man7.org/linux/man-pages/man2/uname.2.html">uname</a> system call with our toolkit:</p>
            <pre><code>$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libsandbox.so SECCOMP_SYSCALL_DENY=uname ./myos
adding uname to the process seccomp filter
Bad system call</code></pre>
            <p></p><p>Yet again, we’ve managed to inject our desired seccomp policy into the <code>myos</code> application without modifying or recompiling it. The advantage of this approach is that it doesn’t have the shortcomings of the systemd’s <a href="https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter="><code>SystemCallFilter=</code></a> and we can block any system call (luckily <a href="https://www.gnu.org/software/bash/">Bash</a> is a dynamically linked application as well):</p>
            <pre><code>$ /bin/bash -c 'echo I will try to execve something...; exec /usr/bin/echo Doing arbitrary code execution!!!'
I will try to execve something...
Doing arbitrary code execution!!!
$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libsandbox.so SECCOMP_SYSCALL_DENY=execve /bin/bash -c 'echo I will try to execve something...; exec /usr/bin/echo Doing arbitrary code execution!!!'
adding execve to the process seccomp filter
I will try to execve something...
Bad system call</code></pre>
            <p></p><p>The only problem here is that we may accidentally forget to <code>LD_PRELOAD</code> our <code>libsandbox.so</code> library and potentially run unprotected. Also, as described in the <a href="https://www.man7.org/linux/man-pages/man8/ld.so.8.html">man page</a>, <code>LD_PRELOAD</code> has some limitations. We can overcome all these problems by making <code>libsandbox.so</code> a permanent part of our target application:</p>
            <pre><code>$ patchelf --add-needed /usr/lib/x86_64-linux-gnu/libsandbox.so ./myos
$ ldd ./myos
	linux-vdso.so.1 (0x00007fff835ae000)
	/usr/lib/x86_64-linux-gnu/libsandbox.so (0x00007fc4f55f2000)
	libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc4f5425000)
	/lib64/ld-linux-x86-64.so.2 (0x00007fc4f5647000)</code></pre>
            <p></p><p>Again, we didn’t need access to the source code here, but patched the compiled binary instead. Now we can just configure our seccomp policy as before without the need of <code>LD_PRELOAD</code>:</p>
            <pre><code>$ ./myos
My OS is Linux!
$ SECCOMP_SYSCALL_DENY=uname ./myos
adding uname to the process seccomp filter
Bad system call</code></pre>
            <p></p>
    <div>
      <h3>sandboxing statically linked executables</h3>
      <a href="#sandboxing-statically-linked-executables">
        
      </a>
    </div>
    <p>The above method is quite convenient and easy, but it doesn’t work for statically linked executables:</p>
            <pre><code>$ gcc -static -o myos myos.c
$ ldd ./myos
	not a dynamic executable
$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libsandbox.so SECCOMP_SYSCALL_DENY=uname ./myos
My OS is Linux!</code></pre>
            <p></p><p>This is because there is no <a href="https://www.man7.org/linux/man-pages/man8/ld.so.8.html">dynamic loader</a> involved in starting a statically linked executable, so <code>LD_PRELOAD</code> has no effect. For this case our toolkit contains a special application launcher, which will inject the seccomp rules similarly to the way systemd does it:</p>
            <pre><code>$ sandboxify ./myos
My OS is Linux!
$ SECCOMP_SYSCALL_DENY=uname sandboxify ./myos
adding uname to the process seccomp filter</code></pre>
            <p></p><p>Note that we don’t see the <code>Bad system call</code> shell message anymore, because our target executable is being started by the launcher instead of the shell directly. Unlike systemd however, we can use this launcher to block dangerous system calls, like <a href="https://www.man7.org/linux/man-pages/man2/execve.2.html">execve</a>, as well:</p>
            <pre><code>$ sandboxify /bin/bash -c 'echo I will try to execve something...; exec /usr/bin/echo Doing arbitrary code execution!!!'
I will try to execve something...
Doing arbitrary code execution!!!
SECCOMP_SYSCALL_DENY=execve sandboxify /bin/bash -c 'echo I will try to execve something...; exec /usr/bin/echo Doing arbitrary code execution!!!'
adding execve to the process seccomp filter
I will try to execve something...</code></pre>
            <p></p>
    <div>
      <h3>sandboxify vs libsandbox.so</h3>
      <a href="#sandboxify-vs-libsandbox-so">
        
      </a>
    </div>
    <p>From the examples above you may notice that it is possible to use <code>sandboxify</code> with dynamically linked executables as well, so why even bother with <code>libsandbox.so</code>? The difference becomes visible, when we start using not the “denylist” policy as in most examples in this post, but rather the preferred “allowlist” policy, where we explicitly allow only the system calls we need, but prohibit everything else.</p><p>Let’s convert our toy application back into the dynamically-linked one and try to come up with the minimal list of allowed system calls it needs to function properly:</p>
            <pre><code>$ gcc -o myos myos.c
$ ldd ./myos
	linux-vdso.so.1 (0x00007ffe027f6000)
	libc.so.6 =&gt; /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4f1410a000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f4f142de000)
$ LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libsandbox.so SECCOMP_SYSCALL_ALLOW=exit_group:fstat:uname:write ./myos
adding exit_group to the process seccomp filter
adding fstat to the process seccomp filter
adding uname to the process seccomp filter
adding write to the process seccomp filter
My OS is Linux</code></pre>
            <p></p><p>So we need to allow 4 system calls: <code>exit_group:fstat:uname:write</code>. This is the tightest “sandbox”, which still doesn’t break the application. If we remove any system call from this list, the application will terminate with the <code>Bad system call</code> message (try it yourself!).</p><p>If we use the same allowlist, but with the <code>sandboxify</code> launcher, things do not work anymore:</p>
            <pre><code>$ SECCOMP_SYSCALL_ALLOW=exit_group:fstat:uname:write sandboxify ./myos
adding exit_group to the process seccomp filter
adding fstat to the process seccomp filter
adding uname to the process seccomp filter
adding write to the process seccomp filter</code></pre>
            <p></p><p>The reason is <code>sandboxify</code> and <code>libsandbox.so</code> inject seccomp rules at different stages of the process lifecycle. Consider the following very high level diagram of a process startup:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1gnqXj5b3tLBdG5pG9b9yH/ff6fb1ab546047ed8fabb610bee2c1df/image3-2.png" />
            
            </figure><p>
In a nutshell, every process has two runtime stages: “runtime init” and the “main logic”. The main logic is basically the code, which is located in the program <code>main()</code> function and other code put there by the application developers. But the process usually needs to do some work before the code from the <code>main()</code> function is able to execute - we call this work the “runtime init” on the diagram above. Developers do not write this code directly, but most of the time this code is automatically generated by the compiler toolchain, which is used to compile the source code.</p><p>To do its job, the “runtime init” stage uses a lot of different system calls, but most of them are not needed later at the “main logic” stage. If we’re using the “allowlist” approach for our sandboxing, it does not make sense to allow these system calls for the whole duration of the program, if they are only used once on program init. This is where the difference between <code>libsandbox.so</code> and <code>sandboxify</code> comes from: <code>libsandbox.so</code> enforces the seccomp rules usually after the “runtime init” stage has already executed, so we don’t have to allow most system calls from that stage. <code>sandboxify</code> on the other hand enforces the policy before the “runtime init” stage, so we have to allow all the system calls from both stages, which usually results in a bigger allowlist, thus wider attack surface.</p><p>Going back to our toy <code>myos</code> example, here is the minimal list of all the system calls we need to allow to make the application work under our sandbox:</p>
            <pre><code>$ SECCOMP_SYSCALL_ALLOW=access:arch_prctl:brk:close:exit_group:fstat:mmap:mprotect:munmap:openat:read:uname:write sandboxify ./myos
adding access to the process seccomp filter
adding arch_prctl to the process seccomp filter
adding brk to the process seccomp filter
adding close to the process seccomp filter
adding exit_group to the process seccomp filter
adding fstat to the process seccomp filter
adding mmap to the process seccomp filter
adding mprotect to the process seccomp filter
adding munmap to the process seccomp filter
adding openat to the process seccomp filter
adding read to the process seccomp filter
adding uname to the process seccomp filter
adding write to the process seccomp filter
My OS is Linux!</code></pre>
            <p></p><p>It is 13 syscalls vs 4 syscalls, if we’re using the <code>libsandbox.so</code> approach!</p>
    <div>
      <h2>Conclusions</h2>
      <a href="#conclusions">
        
      </a>
    </div>
    <p>In this post we discussed how to easily sandbox applications on Linux without the need to write any additional code. We introduced the <a href="https://github.com/cloudflare/sandbox">Cloudflare sandbox toolkit</a> and discussed the different approaches we take at sandboxing dynamically linked applications vs statically linked applications.</p><p>Having safer code online helps to build a Better Internet and we would be happy if you find our <a href="https://github.com/cloudflare/sandbox">sandbox toolkit</a> useful. Looking forward to the feedback, improvements and other contributions!</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">62KzlmtEZD97kzQlvdKaNN</guid>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
        <item>
            <title><![CDATA[Speeding up Linux disk encryption]]></title>
            <link>https://blog.cloudflare.com/speeding-up-linux-disk-encryption/</link>
            <pubDate>Wed, 25 Mar 2020 12:00:00 GMT</pubDate>
            <description><![CDATA[ Encrypting data at rest is vital for Cloudflare with more than 200 data centres across the world. In this post, we will investigate the performance of disk encryption on Linux and explain how we made it at least two times faster for ourselves and our customers! ]]></description>
            <content:encoded><![CDATA[ <p>Data encryption at rest is a must-have for any modern Internet company. Many companies, however, don't encrypt their disks, because they fear the potential performance penalty caused by encryption overhead.</p><p>Encrypting data at rest is vital for Cloudflare with <a href="https://www.cloudflare.com/network/">more than 200 data centres across the world</a>. In this post, we will investigate the performance of disk encryption on Linux and explain how we made it at least two times faster for ourselves and our customers!</p>
    <div>
      <h3>Encrypting data at rest</h3>
      <a href="#encrypting-data-at-rest">
        
      </a>
    </div>
    <p>When it comes to encrypting data at rest there are several ways it can be implemented on a modern operating system (OS). Available techniques are tightly coupled with a <a href="https://en.wikibooks.org/wiki/The_Linux_Kernel/Storage">typical OS storage stack</a>. A simplified version of the storage stack and encryption solutions can be found on the diagram below:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/1IM596LYKyiKGoM8dQv0a2/459b3d6be1edf9328914470ed9eee925/storage-stack.png" />
            
            </figure><p>On the top of the stack are applications, which read and write data in files (or streams). The file system in the OS kernel keeps track of which blocks of the underlying block device belong to which files and translates these file reads and writes into block reads and writes, however the hardware specifics of the underlying storage device is abstracted away from the filesystem. Finally, the block subsystem actually passes the block reads and writes to the underlying hardware using appropriate device drivers.</p><p>The concept of the storage stack is actually similar to the <a href="https://www.cloudflare.com/learning/ddos/glossary/open-systems-interconnection-model-osi/">well-known network OSI model</a>, where each layer has a more high-level view of the information and the implementation details of the lower layers are abstracted away from the upper layers. And, similar to the OSI model, one can apply encryption at different layers (think about <a href="https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/">TLS</a> vs <a href="https://en.wikipedia.org/wiki/IPsec">IPsec</a> or <a href="https://www.cloudflare.com/learning/access-management/what-is-a-vpn/">a VPN</a>).</p><p>For data at rest we can apply encryption either at the block layers (either in hardware or in software) or at the file level (either directly in applications or in the filesystem).</p>
    <div>
      <h4>Block vs file encryption</h4>
      <a href="#block-vs-file-encryption">
        
      </a>
    </div>
    <p>Generally, the higher in the stack we apply encryption, the more flexibility we have. With application level encryption the application maintainers can apply any encryption code they please to any particular data they need. The downside of this approach is they actually have to implement it themselves and encryption in general is not very developer-friendly: one has to know the ins and outs of a specific cryptographic algorithm, properly generate keys, nonces, IVs etc. Additionally, application level encryption does not leverage OS-level caching and <a href="https://en.wikipedia.org/wiki/Page_cache">Linux page cache</a> in particular: each time the application needs to use the data, it has to either decrypt it again, wasting CPU cycles, or implement its own decrypted “cache”, which introduces more complexity to the code.</p><p>File system level encryption makes data encryption transparent to applications, because the file system itself encrypts the data before passing it to the block subsystem, so files are encrypted regardless if the application has crypto support or not. Also, file systems can be configured to encrypt only a particular directory or have different keys for different files. This flexibility, however, comes at a cost of a more complex configuration. File system encryption is also considered less secure than block device encryption as only the contents of the files are encrypted. Files also have associated metadata, like file size, the number of files, the directory tree layout etc., which are still visible to a potential adversary.</p><p>Encryption down at the block layer (often referred to as <a href="https://en.wikipedia.org/wiki/Disk_encryption">disk encryption</a> or full disk encryption) also makes data encryption transparent to applications and even whole file systems. Unlike file system level encryption it encrypts all data on the disk including file metadata and even free space. It is less flexible though - one can only encrypt the whole disk with a single key, so there is no per-directory, per-file or per-user configuration. From the crypto perspective, not all cryptographic algorithms can be used as the block layer doesn't have a high-level overview of the data anymore, so it needs to process each block independently. Most <a href="https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Common_modes">common algorithms require some sort of block chaining</a> to be secure, so are not applicable to disk encryption. Instead, <a href="https://en.wikipedia.org/wiki/Disk_encryption_theory#Block_cipher-based_modes">special modes were developed</a> just for this specific use-case.</p><p>So which layer to choose? As always, it depends... Application and file system level encryption are usually the preferred choice for client systems because of the flexibility. For example, each user on a multi-user desktop may want to encrypt their home directory with a key they own and leave some shared directories unencrypted. On the contrary, on server systems, managed by SaaS/PaaS/IaaS companies (including Cloudflare) the preferred choice is configuration simplicity and security - with full disk encryption enabled any data from any application is automatically encrypted with no exceptions or overrides. We believe that all data needs to be protected without sorting it into "important" vs "not important" buckets, so the selective flexibility the upper layers provide is not needed.</p>
    <div>
      <h4>Hardware vs software disk encryption</h4>
      <a href="#hardware-vs-software-disk-encryption">
        
      </a>
    </div>
    <p>When encrypting data at the block layer it is possible to do it directly in the storage hardware, if the hardware <a href="https://en.wikipedia.org/wiki/Hardware-based_full_disk_encryption">supports it</a>. Doing so usually gives better read/write performance and consumes less resources from the host. However, since most hardware firmware is proprietary, it does not receive as much attention and review from the security community. In the past this led to <a href="https://www.us-cert.gov/ncas/current-activity/2018/11/06/Self-Encrypting-Solid-State-Drive-Vulnerabilities">flaws in some implementations of hardware disk encryption</a>, which render the whole security model useless. Microsoft, for example, <a href="https://support.microsoft.com/en-us/help/4516071/windows-10-update-kb4516071">started to prefer software-based disk encryption</a> since then.</p><p>We didn't want to put our data and our customers' data to the risk of using potentially insecure solutions and we <a href="/helping-to-build-cloudflare-part-4/">strongly believe in open-source</a>. That's why we rely only on software disk encryption in the Linux kernel, which is open and has been audited by many security professionals across the world.</p>
    <div>
      <h3>Linux disk encryption performance</h3>
      <a href="#linux-disk-encryption-performance">
        
      </a>
    </div>
    <p>We aim not only to save bandwidth costs for our customers, but to deliver content to Internet users as fast as possible.</p><p>At one point we noticed that our disks were not as fast as we would like them to be. Some profiling as well as a quick A/B test pointed to Linux disk encryption. Because not encrypting the data (even if it is supposed-to-be a public Internet cache) is not a sustainable option, we decided to take a closer look into Linux disk encryption performance.</p>
    <div>
      <h4>Device mapper and dm-crypt</h4>
      <a href="#device-mapper-and-dm-crypt">
        
      </a>
    </div>
    <p>Linux implements transparent disk encryption via a <a href="https://en.wikipedia.org/wiki/Dm-crypt">dm-crypt module</a> and <code>dm-crypt</code> itself is part of <a href="https://en.wikipedia.org/wiki/Device_mapper">device mapper</a> kernel framework. In a nutshell, the device mapper allows pre/post-process IO requests as they travel between the file system and the underlying block device.</p><p><code>dm-crypt</code> in particular encrypts "write" IO requests before sending them further down the stack to the actual block device and decrypts "read" IO requests before sending them up to the file system driver. Simple and easy! Or is it?</p>
    <div>
      <h4>Benchmarking setup</h4>
      <a href="#benchmarking-setup">
        
      </a>
    </div>
    <p>For the record, the numbers in this post were obtained by running specified commands on an idle <a href="/a-tour-inside-cloudflares-g9-servers/">Cloudflare G9 server</a> out of production. However, the setup should be easily reproducible on any modern x86 laptop.</p><p>Generally, benchmarking anything around a storage stack is hard because of the noise introduced by the storage hardware itself. Not all disks are created equal, so for the purpose of this post we will use the fastest disks available out there - that is no disks.</p><p>Instead Linux has an option to emulate a disk directly in <a href="https://en.wikipedia.org/wiki/Random-access_memory">RAM</a>. Since RAM is much faster than any persistent storage, it should introduce little bias in our results.</p><p>The following command creates a 4GB ramdisk:</p>
            <pre><code>$ sudo modprobe brd rd_nr=1 rd_size=4194304
$ ls /dev/ram0</code></pre>
            <p>Now we can set up a <code>dm-crypt</code> instance on top of it thus enabling encryption for the disk. First, we need to generate the disk encryption key, "format" the disk and specify a password to unlock the newly generated key.</p>
            <pre><code>$ fallocate -l 2M crypthdr.img
$ sudo cryptsetup luksFormat /dev/ram0 --header crypthdr.img

WARNING!
========
This will overwrite data on crypthdr.img irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase:
Verify passphrase:</code></pre>
            <p>Those who are familiar with <code>LUKS/dm-crypt</code> might have noticed we used a <a href="http://man7.org/linux/man-pages/man8/cryptsetup.8.html">LUKS detached header</a> here. Normally, LUKS stores the password-encrypted disk encryption key on the same disk as the data, but since we want to compare read/write performance between encrypted and unencrypted devices, we might accidentally overwrite the encrypted key during our benchmarking later. Keeping the encrypted key in a separate file avoids this problem for the purposes of this post.</p><p>Now, we can actually "unlock" the encrypted device for our testing:</p>
            <pre><code>$ sudo cryptsetup open --header crypthdr.img /dev/ram0 encrypted-ram0
Enter passphrase for /dev/ram0:
$ ls /dev/mapper/encrypted-ram0
/dev/mapper/encrypted-ram0</code></pre>
            <p>At this point we can now compare the performance of encrypted vs unencrypted ramdisk: if we read/write data to <code>/dev/ram0</code>, it will be stored in <a href="https://en.wikipedia.org/wiki/Plaintext">plaintext</a>. Likewise, if we read/write data to <code>/dev/mapper/encrypted-ram0</code>, it will be decrypted/encrypted on the way by <code>dm-crypt</code> and stored in <a href="https://en.wikipedia.org/wiki/Ciphertext">ciphertext</a>.</p><p>It's worth noting that we're not creating any file system on top of our block devices to avoid biasing results with a file system overhead.</p>
    <div>
      <h4>Measuring throughput</h4>
      <a href="#measuring-throughput">
        
      </a>
    </div>
    <p>When it comes to storage testing/benchmarking <a href="https://fio.readthedocs.io/en/latest/fio_doc.html">Flexible I/O tester</a> is the usual go-to solution. Let's simulate simple sequential read/write load with 4K block size on the ramdisk without encryption:</p>
            <pre><code>$ sudo fio --filename=/dev/ram0 --readwrite=readwrite --bs=4k --direct=1 --loops=1000000 --name=plain
plain: (g=0): rw=rw, bs=4K-4K/4K-4K/4K-4K, ioengine=psync, iodepth=1
fio-2.16
Starting 1 process
...
Run status group 0 (all jobs):
   READ: io=21013MB, aggrb=1126.5MB/s, minb=1126.5MB/s, maxb=1126.5MB/s, mint=18655msec, maxt=18655msec
  WRITE: io=21023MB, aggrb=1126.1MB/s, minb=1126.1MB/s, maxb=1126.1MB/s, mint=18655msec, maxt=18655msec

Disk stats (read/write):
  ram0: ios=0/0, merge=0/0, ticks=0/0, in_queue=0, util=0.00%</code></pre>
            <p>The above command will run for a long time, so we just stop it after a while. As we can see from the stats, we're able to read and write roughly with the same throughput around <code>1126 MB/s</code>. Let's repeat the test with the encrypted ramdisk:</p>
            <pre><code>$ sudo fio --filename=/dev/mapper/encrypted-ram0 --readwrite=readwrite --bs=4k --direct=1 --loops=1000000 --name=crypt
crypt: (g=0): rw=rw, bs=4K-4K/4K-4K/4K-4K, ioengine=psync, iodepth=1
fio-2.16
Starting 1 process
...
Run status group 0 (all jobs):
   READ: io=1693.7MB, aggrb=150874KB/s, minb=150874KB/s, maxb=150874KB/s, mint=11491msec, maxt=11491msec
  WRITE: io=1696.4MB, aggrb=151170KB/s, minb=151170KB/s, maxb=151170KB/s, mint=11491msec, maxt=11491msec</code></pre>
            <p>Whoa, that's a drop! We only get <code>~147 MB/s</code> now, which is more than 7 times slower! And this is on a totally idle machine!</p>
    <div>
      <h4>Maybe, crypto is just slow</h4>
      <a href="#maybe-crypto-is-just-slow">
        
      </a>
    </div>
    <p>The first thing we considered is to ensure we use the fastest crypto. <code>cryptsetup</code> allows us to benchmark all the available crypto implementations on the system to select the best one:</p>
            <pre><code>$ sudo cryptsetup benchmark
# Tests are approximate using memory only (no storage IO).
PBKDF2-sha1      1340890 iterations per second for 256-bit key
PBKDF2-sha256    1539759 iterations per second for 256-bit key
PBKDF2-sha512    1205259 iterations per second for 256-bit key
PBKDF2-ripemd160  967321 iterations per second for 256-bit key
PBKDF2-whirlpool  720175 iterations per second for 256-bit key
#  Algorithm | Key |  Encryption |  Decryption
     aes-cbc   128b   969.7 MiB/s  3110.0 MiB/s
 serpent-cbc   128b           N/A           N/A
 twofish-cbc   128b           N/A           N/A
     aes-cbc   256b   756.1 MiB/s  2474.7 MiB/s
 serpent-cbc   256b           N/A           N/A
 twofish-cbc   256b           N/A           N/A
     aes-xts   256b  1823.1 MiB/s  1900.3 MiB/s
 serpent-xts   256b           N/A           N/A
 twofish-xts   256b           N/A           N/A
     aes-xts   512b  1724.4 MiB/s  1765.8 MiB/s
 serpent-xts   512b           N/A           N/A
 twofish-xts   512b           N/A           N/A</code></pre>
            <p>It seems <code>aes-xts</code> with a 256-bit data encryption key is the fastest here. But which one are we actually using for our encrypted ramdisk?</p>
            <pre><code>$ sudo dmsetup table /dev/mapper/encrypted-ram0
0 8388608 crypt aes-xts-plain64 0000000000000000000000000000000000000000000000000000000000000000 0 1:0 0</code></pre>
            <p>We do use <code>aes-xts</code> with a 256-bit data encryption key (count all the zeroes conveniently masked by <code>dmsetup</code> tool - if you want to see the actual bytes, add the <code>--showkeys</code> option to the above command). The numbers do not add up however: <code>cryptsetup benchmark</code> tells us above not to rely on the results, as "Tests are approximate using memory only (no storage IO)", but that is exactly how we've set up our experiment using the ramdisk. In a somewhat worse case (assuming we're reading all the data and then encrypting/decrypting it sequentially with no parallelism) doing <a href="https://en.wikipedia.org/wiki/Back-of-the-envelope_calculation">back-of-the-envelope calculation</a> we should be getting around <code>(1126 * 1823) / (1126 + 1823) =~696 MB/s</code>, which is still quite far from the actual <code>147 * 2 = 294 MB/s</code> (total for reads and writes).</p>
    <div>
      <h4>dm-crypt performance flags</h4>
      <a href="#dm-crypt-performance-flags">
        
      </a>
    </div>
    <p>While reading the <a href="http://man7.org/linux/man-pages/man8/cryptsetup.8.html">cryptsetup man page</a> we noticed that it has two options prefixed with <code>--perf-</code>, which are probably related to performance tuning. The first one is <code>--perf-same_cpu_crypt</code> with a rather cryptic description:</p>
            <pre><code>Perform encryption using the same cpu that IO was submitted on.  The default is to use an unbound workqueue so that encryption work is automatically balanced between available CPUs.  This option is only relevant for open action.</code></pre>
            <p>So we enable the option</p>
            <pre><code>$ sudo cryptsetup close encrypted-ram0
$ sudo cryptsetup open --header crypthdr.img --perf-same_cpu_crypt /dev/ram0 encrypted-ram0</code></pre>
            <p>Note: according to the <a href="http://man7.org/linux/man-pages/man8/cryptsetup.8.html">latest man page</a> there is also a <code>cryptsetup refresh</code> command, which can be used to enable these options live without having to "close" and "re-open" the encrypted device. Our <code>cryptsetup</code> however didn't support it yet.</p><p>Verifying if the option has been really enabled:</p>
            <pre><code>$ sudo dmsetup table encrypted-ram0
0 8388608 crypt aes-xts-plain64 0000000000000000000000000000000000000000000000000000000000000000 0 1:0 0 1 same_cpu_crypt</code></pre>
            <p>Yes, we can now see <code>same_cpu_crypt</code> in the output, which is what we wanted. Let's rerun the benchmark:</p>
            <pre><code>$ sudo fio --filename=/dev/mapper/encrypted-ram0 --readwrite=readwrite --bs=4k --direct=1 --loops=1000000 --name=crypt
crypt: (g=0): rw=rw, bs=4K-4K/4K-4K/4K-4K, ioengine=psync, iodepth=1
fio-2.16
Starting 1 process
...
Run status group 0 (all jobs):
   READ: io=1596.6MB, aggrb=139811KB/s, minb=139811KB/s, maxb=139811KB/s, mint=11693msec, maxt=11693msec
  WRITE: io=1600.9MB, aggrb=140192KB/s, minb=140192KB/s, maxb=140192KB/s, mint=11693msec, maxt=11693msec</code></pre>
            <p>Hmm, now it is <code>~136 MB/s</code> which is slightly worse than before, so no good. What about the second option <code>--perf-submit_from_crypt_cpus</code>:</p>
            <pre><code>Disable offloading writes to a separate thread after encryption.  There are some situations where offloading write bios from the encryption threads to a single thread degrades performance significantly.  The default is to offload write bios to the same thread.  This option is only relevant for open action.</code></pre>
            <p>Maybe, we are in the "some situation" here, so let's try it out:</p>
            <pre><code>$ sudo cryptsetup close encrypted-ram0
$ sudo cryptsetup open --header crypthdr.img --perf-submit_from_crypt_cpus /dev/ram0 encrypted-ram0
Enter passphrase for /dev/ram0:
$ sudo dmsetup table encrypted-ram0
0 8388608 crypt aes-xts-plain64 0000000000000000000000000000000000000000000000000000000000000000 0 1:0 0 1 submit_from_crypt_cpus</code></pre>
            <p>And now the benchmark:</p>
            <pre><code>$ sudo fio --filename=/dev/mapper/encrypted-ram0 --readwrite=readwrite --bs=4k --direct=1 --loops=1000000 --name=crypt
crypt: (g=0): rw=rw, bs=4K-4K/4K-4K/4K-4K, ioengine=psync, iodepth=1
fio-2.16
Starting 1 process
...
Run status group 0 (all jobs):
   READ: io=2066.6MB, aggrb=169835KB/s, minb=169835KB/s, maxb=169835KB/s, mint=12457msec, maxt=12457msec
  WRITE: io=2067.7MB, aggrb=169965KB/s, minb=169965KB/s, maxb=169965KB/s, mint=12457msec, maxt=12457msec</code></pre>
            <p><code>~166 MB/s</code>, which is a bit better, but still not good...</p>
    <div>
      <h4>Asking the community</h4>
      <a href="#asking-the-community">
        
      </a>
    </div>
    <p>Being desperate we decided to seek support from the Internet and <a href="https://www.spinics.net/lists/dm-crypt/msg07516.html">posted our findings to the <code>dm-crypt</code> mailing list</a>, but the response we got was not very encouraging:</p><blockquote><p>If the numbers disturb you, then this is from lack of understanding on your side. You are probably unaware that encryption is a heavy-weight operation...</p></blockquote><p>We decided to make a scientific research on this topic by typing "is encryption expensive" into Google Search and one of the top results, which actually contains meaningful measurements, is... <a href="/how-expensive-is-crypto-anyway/">our own post about cost of encryption</a>, but in the context of <a href="https://www.cloudflare.com/learning/ssl/transport-layer-security-tls/">TLS</a>! This is a fascinating read on its own, but the gist is: modern crypto on modern hardware is very cheap even at Cloudflare scale (doing millions of encrypted HTTP requests per second). In fact, it is so cheap that Cloudflare was the first provider to offer <a href="https://www.cloudflare.com/application-services/products/ssl/">free SSL/TLS for everyone</a>.</p>
    <div>
      <h4>Digging into the source code</h4>
      <a href="#digging-into-the-source-code">
        
      </a>
    </div>
    <p>When trying to use the custom <code>dm-crypt</code> options described above we were curious why they exist in the first place and what is that "offloading" all about. Originally we expected <code>dm-crypt</code> to be a simple "proxy", which just encrypts/decrypts data as it flows through the stack. Turns out <code>dm-crypt</code> does more than just encrypting memory buffers and a (simplified) IO traverse path diagram is presented below:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4UenGQJyEiOzxwISPhhGR1/bdeae8ae7c9d331f4ca4d1cc75bbfb15/dm-crypt.png" />
            
            </figure><p>When the file system issues a write request, <code>dm-crypt</code> does not process it immediately - instead it puts it into a <a href="https://www.kernel.org/doc/html/v4.19/core-api/workqueue.html">workqueue</a> <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L3124">named "kcryptd"</a>. In a nutshell, a kernel workqueue just schedules some work (encryption in this case) to be performed at some later time, when it is more convenient. When "the time" comes, <code>dm-crypt</code> <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L1940">sends the request</a> to <a href="https://www.kernel.org/doc/html/v4.19/crypto/index.html">Linux Crypto API</a> for actual encryption. However, modern Linux Crypto API <a href="https://www.kernel.org/doc/html/v4.19/crypto/api-skcipher.html#symmetric-key-cipher-api">is asynchronous</a> as well, so depending on which particular implementation your system will use, most likely it will not be processed immediately, but queued again for "later time". When Linux Crypto API will finally <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L1980">do the encryption</a>, <code>dm-crypt</code> may try to <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L1909-L1910">sort pending write requests by putting each request</a> into a <a href="https://en.wikipedia.org/wiki/Red%E2%80%93black_tree">red-black tree</a>. Then a <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L1819">separate kernel thread</a> again at "some time later" actually takes all IO requests in the tree and <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L1864">sends them down the stack</a>.</p><p>Now for read requests: this time we need to get the encrypted data first from the hardware, but <code>dm-crypt</code> does not just ask for the driver for the data, but queues the request into a different <a href="https://www.kernel.org/doc/html/v4.19/core-api/workqueue.html">workqueue</a> <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L3122">named "kcryptd_io"</a>. At some point later, when we actually have the encrypted data, we <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L1742">schedule it for decryption</a> using the now familiar "kcryptd" workqueue. "kcryptd" <a href="https://github.com/torvalds/linux/blob/0d81a3f29c0afb18ba2b1275dcccf21e0dd4da38/drivers/md/dm-crypt.c#L1970">will send the request</a> to Linux Crypto API, which may decrypt the data asynchronously as well.</p><p>To be fair the request does not always traverse all these queues, but the important part here is that write requests may be queued up to 4 times in <code>dm-crypt</code> and read requests up to 3 times. At this point we were wondering if all this extra queueing can cause any performance issues. For example, there is a <a href="https://www.usenix.org/conference/srecon19asia/presentation/plenz">nice presentation from Google</a> about the relationship between queueing and tail latency. One key takeaway from the presentation is:</p><blockquote><p>A significant amount of tail latency is due to queueing effects</p></blockquote><p>So, why are all these queues there and can we remove them?</p>
    <div>
      <h4>Git archeology</h4>
      <a href="#git-archeology">
        
      </a>
    </div>
    <p>No-one writes more complex code just for fun, especially for the OS kernel. So all these queues must have been put there for a reason. Luckily, the Linux kernel source is managed by <a href="https://en.wikipedia.org/wiki/Git">git</a>, so we can try to retrace the changes and the decisions around them.</p><p>The "kcryptd" workqueue was in the source <a href="https://github.com/torvalds/linux/blob/1da177e4c3f41524e886b7f1b8a0c1fc7321cac2/drivers/md/dm-crypt.c">since the beginning of the available history</a> with the following comment:</p><blockquote><p>Needed because it would be very unwise to do decryption in an interrupt context, so bios returning from read requests get queued here.</p></blockquote><p>So it was for reads only, but even then - why do we care if it is interrupt context or not, if Linux Crypto API will likely use a dedicated thread/queue for encryption anyway? Well, back in 2005 Crypto API <a href="https://github.com/torvalds/linux/blob/1da177e4c3f41524e886b7f1b8a0c1fc7321cac2/Documentation/crypto/api-intro.txt">was not asynchronous</a>, so this made perfect sense.</p><p>In 2006 <code>dm-crypt</code> <a href="https://github.com/torvalds/linux/commit/23541d2d288cdb54f417ba1001dacc7f3ea10a97">started to use</a> the "kcryptd" workqueue not only for encryption, but for submitting IO requests:</p><blockquote><p>This patch is designed to help dm-crypt comply with the new constraints imposed by the following patch in -mm: md-dm-reduce-stack-usage-with-stacked-block-devices.patch</p></blockquote><p>It seems the goal here was not to add more concurrency, but rather reduce kernel stack usage, which makes sense again as the kernel has a common stack across all the code, so it is a quite limited resource. It is worth noting, however, that the <a href="https://github.com/torvalds/linux/commit/6538b8ea886e472f4431db8ca1d60478f838d14b">Linux kernel stack has been expanded</a> in 2014 for x86 platforms, so this might not be a problem anymore.</p><p>A <a href="https://github.com/torvalds/linux/commit/cabf08e4d3d1181d7c408edae97fb4d1c31518af">first version of "kcryptd_io" workqueue was added</a> in 2007 with the intent to avoid:</p><blockquote><p>starvation caused by many requests waiting for memory allocation...</p></blockquote><p>The request processing was bottlenecking on a single workqueue here, so the solution was to add another one. Makes sense.</p><p>We are definitely not the first ones experiencing performance degradation because of extensive queueing: in 2011 a change was introduced to <a href="https://github.com/torvalds/linux/commit/20c82538e4f5ede51bc2b4795bc6e5cae772796d">conditionally revert some of the queueing for read requests</a>:</p><blockquote><p>If there is enough memory, code can directly submit bio instead queuing this operation in a separate thread.</p></blockquote><p>Unfortunately, at that time Linux kernel commit messages were not as verbose as today, so there is no performance data available.</p><p>In 2015 <a href="https://github.com/torvalds/linux/commit/dc2676210c425ee8e5cb1bec5bc84d004ddf4179">dm-crypt started to sort writes</a> in a separate "dmcrypt_write" thread before sending them down the stack:</p><blockquote><p>On a multiprocessor machine, encryption requests finish in a different order than they were submitted. Consequently, write requests would be submitted in a different order and it could cause severe performance degradation.</p></blockquote><p>It does make sense as sequential disk access used to be much faster than the random one and <code>dm-crypt</code> was breaking the pattern. But this mostly applies to <a href="https://en.wikipedia.org/wiki/Hard_disk_drive">spinning disks</a>, which were still dominant in 2015. It may not be as important with modern fast <a href="https://en.wikipedia.org/wiki/Solid-state_drive">SSDs (including NVME SSDs)</a>.</p><p>Another part of the commit message is worth mentioning:</p><blockquote><p>...in particular it enables IO schedulers like CFQ to sort more effectively...</p></blockquote><p>It mentions the performance benefits for the <a href="https://www.kernel.org/doc/Documentation/block/cfq-iosched.txt">CFQ IO scheduler</a>, but Linux schedulers have improved since then to the point that <a href="https://github.com/torvalds/linux/commit/f382fb0bcef4c37dc049e9f6963e3baf204d815c">CFQ scheduler has been removed</a> from the kernel in 2018.</p><p>The same patchset <a href="https://github.com/torvalds/linux/commit/b3c5fd3052492f1b8d060799d4f18be5a5438">replaces the sorting list with a red-black tree</a>:</p><blockquote><p>In theory the sorting should be performed by the underlying disk scheduler, however, in practice the disk scheduler only accepts and sorts a finite number of requests. To allow the sorting of all requests, dm-crypt needs to implement its own sorting.</p><p>The overhead associated with rbtree-based sorting is considered negligible so it is not used conditionally.</p></blockquote><p>All that make sense, but it would be nice to have some backing data.</p><p>Interestingly, in the same patchset we see <a href="https://github.com/torvalds/linux/commit/0f5d8e6ee758f7023e4353cca75d785b2d4f6abe">the introduction of our familiar "submit_from_crypt_cpus" option</a>:</p><blockquote><p>There are some situations where offloading write bios from the encryption threads to a single thread degrades performance significantly</p></blockquote><p>Overall, we can see that every change was reasonable and needed, however things have changed since then:</p><ul><li><p>hardware became faster and smarter</p></li><li><p>Linux resource allocation was revisited</p></li><li><p>coupled Linux subsystems were rearchitected</p></li></ul><p>And many of the design choices above may not be applicable to modern Linux.</p>
    <div>
      <h3>The "clean-up"</h3>
      <a href="#the-clean-up">
        
      </a>
    </div>
    <p>Based on the research above we decided to try to remove all the extra queueing and asynchronous behaviour and revert <code>dm-crypt</code> to its original purpose: simply encrypt/decrypt IO requests as they pass through. But for the sake of stability and further benchmarking we ended up not removing the actual code, but rather adding yet another <code>dm-crypt</code> option, which bypasses all the queues/threads, if enabled. The flag allows us to switch between the current and new behaviour at runtime under full production load, so we can easily revert our changes should we see any side-effects. The resulting patch can be found on the <a href="https://github.com/cloudflare/linux/blob/12a61de6dd06408f4f3c27f8019beb66366e98e3/patches/0023-Add-DM_CRYPT_FORCE_INLINE-flag-to-dm-crypt-target.patch">Cloudflare GitHub Linux repository</a>.</p>
    <div>
      <h4>Synchronous Linux Crypto API</h4>
      <a href="#synchronous-linux-crypto-api">
        
      </a>
    </div>
    <p>From the diagram above we remember that not all queueing is implemented in <code>dm-crypt</code>. Modern Linux Crypto API may also be asynchronous and for the sake of this experiment we want to eliminate queues there as well. What does "may be" mean, though? The OS may contain different implementations of the same algorithm (for example, <a href="https://en.wikipedia.org/wiki/AES_instruction_set">hardware-accelerated AES-NI on x86 platforms</a> and generic C-code AES implementations). By default the system chooses the "best" one based on <a href="https://www.kernel.org/doc/html/v4.19/crypto/architecture.html#crypto-api-cipher-references-and-priority">the configured algorithm priority</a>. <code>dm-crypt</code> allows overriding this behaviour and <a href="https://gitlab.com/cryptsetup/cryptsetup/-/wikis/DMCrypt#mapping-table-for-crypt-target">request a particular cipher implementation</a> using the <code>capi:</code> prefix. However, there is one problem. Let us actually check the available AES-XTS (this is our disk encryption cipher, remember?) implementations on our system:</p>
            <pre><code>$ grep -A 11 'xts(aes)' /proc/crypto
name         : xts(aes)
driver       : xts(ecb(aes-generic))
module       : kernel
priority     : 100
refcnt       : 7
selftest     : passed
internal     : no
type         : skcipher
async        : no
blocksize    : 16
min keysize  : 32
max keysize  : 64
--
name         : __xts(aes)
driver       : cryptd(__xts-aes-aesni)
module       : cryptd
priority     : 451
refcnt       : 1
selftest     : passed
internal     : yes
type         : skcipher
async        : yes
blocksize    : 16
min keysize  : 32
max keysize  : 64
--
name         : xts(aes)
driver       : xts-aes-aesni
module       : aesni_intel
priority     : 401
refcnt       : 1
selftest     : passed
internal     : no
type         : skcipher
async        : yes
blocksize    : 16
min keysize  : 32
max keysize  : 64
--
name         : __xts(aes)
driver       : __xts-aes-aesni
module       : aesni_intel
priority     : 401
refcnt       : 7
selftest     : passed
internal     : yes
type         : skcipher
async        : no
blocksize    : 16
min keysize  : 32
max keysize  : 64</code></pre>
            <p>We want to explicitly select a synchronous cipher from the above list to avoid queueing effects in threads, but the only two supported are <code>xts(ecb(aes-generic))</code> (the generic C implementation) and <code>__xts-aes-aesni</code> (the <a href="https://en.wikipedia.org/wiki/AES_instruction_set">x86 hardware-accelerated implementation</a>). We definitely want the latter as it is much faster (we're aiming for performance here), but it is suspiciously marked as internal (see <code>internal: yes</code>). If we <a href="https://github.com/torvalds/linux/blob/fb33c6510d5595144d585aa194d377cf74d31911/include/linux/crypto.h#L91">check the source code</a>:</p><blockquote><p>Mark a cipher as a service implementation only usable by another cipher and never by a normal user of the kernel crypto API</p></blockquote><p>So this cipher is meant to be used only by other wrapper code in the Crypto API and not outside it. In practice this means, that the caller of the Crypto API needs to explicitly specify this flag, when requesting a particular cipher implementation, but <code>dm-crypt</code> does not do it, because by design it is not part of the Linux Crypto API, rather an "external" user. We already patch the <code>dm-crypt</code> module, so we could as well just add the relevant flag. However, there is another problem with <a href="https://en.wikipedia.org/wiki/AES_instruction_set">AES-NI</a> in particular: <a href="https://en.wikipedia.org/wiki/X87">x86 FPU</a>. "Floating point" you say? Why do we need floating point math to do symmetric encryption which should only be about bit shifts and XOR operations? We don't need the math, but AES-NI instructions use some of the CPU registers, which are dedicated to the FPU. Unfortunately the Linux kernel <a href="https://github.com/torvalds/linux/blob/fb33c6510d5595144d585aa194d377cf74d31911/arch/x86/kernel/fpu/core.c#L77">does not always preserve these registers in interrupt context</a> for performance reasons (saving/restoring FPU is expensive). But <code>dm-crypt</code> may execute code in interrupt context, so we risk corrupting some other process data and we go back to "it would be very unwise to do decryption in an interrupt context" statement in the original code.</p><p>Our solution to address the above was to create another somewhat <a href="https://github.com/cloudflare/linux/blob/master/patches/0024-Add-xtsproxy-Crypto-API-module.patch">"smart" Crypto API module</a>. This module is synchronous and does not roll its own crypto, but is just a "router" of encryption requests:</p><ul><li><p>if we can use the FPU (and thus AES-NI) in the current execution context, we just forward the encryption request to the faster, "internal" <code>__xts-aes-aesni</code> implementation (and we can use it here, because now we are part of the Crypto API)</p></li><li><p>otherwise, we just forward the encryption request to the slower, generic C-based <code>xts(ecb(aes-generic))</code> implementation</p></li></ul>
    <div>
      <h4>Using the whole lot</h4>
      <a href="#using-the-whole-lot">
        
      </a>
    </div>
    <p>Let's walk through the process of using it all together. The first step is to <a href="https://github.com/cloudflare/linux/blob/master/patches/">grab the patches</a> and recompile the kernel (or just compile <code>dm-crypt</code> and our <code>xtsproxy</code> modules).</p><p>Next, let's restart our IO workload in a separate terminal, so we can make sure we can reconfigure the kernel at runtime under load:</p>
            <pre><code>$ sudo fio --filename=/dev/mapper/encrypted-ram0 --readwrite=readwrite --bs=4k --direct=1 --loops=1000000 --name=crypt
crypt: (g=0): rw=rw, bs=4K-4K/4K-4K/4K-4K, ioengine=psync, iodepth=1
fio-2.16
Starting 1 process
...</code></pre>
            <p>In the main terminal make sure our new Crypto API module is loaded and available:</p>
            <pre><code>$ sudo modprobe xtsproxy
$ grep -A 11 'xtsproxy' /proc/crypto
driver       : xts-aes-xtsproxy
module       : xtsproxy
priority     : 0
refcnt       : 0
selftest     : passed
internal     : no
type         : skcipher
async        : no
blocksize    : 16
min keysize  : 32
max keysize  : 64
ivsize       : 16
chunksize    : 16</code></pre>
            <p>Reconfigure the encrypted disk to use our newly loaded module and enable our patched <code>dm-crypt</code> flag (we have to use low-level <code>dmsetup</code> tool as <code>cryptsetup</code> obviously is not aware of our modifications):</p>
            <pre><code>$ sudo dmsetup table encrypted-ram0 --showkeys | sed 's/aes-xts-plain64/capi:xts-aes-xtsproxy-plain64/' | sed 's/$/ 1 force_inline/' | sudo dmsetup reload encrypted-ram0</code></pre>
            <p>We just "loaded" the new configuration, but for it to take effect, we need to suspend/resume the encrypted device:</p>
            <pre><code>$ sudo dmsetup suspend encrypted-ram0 &amp;&amp; sudo dmsetup resume encrypted-ram0</code></pre>
            <p>And now observe the result. We may go back to the other terminal running the <code>fio</code> job and look at the output, but to make things nicer, here's a snapshot of the observed read/write throughput in <a href="https://grafana.com/">Grafana</a>:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/gU4JU2MqSRwllckZS17O0/eb5ce828cbd3e9a84bfa12411def3455/read-throughput-annotated.png" />
            
            </figure><p></p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6jxXWkIyojMU4AfFA3BVGm/223478d25385f5dbb18385074fa9d60e/write-throughput-annotated.png" />
            
            </figure><p>Wow, we have more than doubled the throughput! With the total throughput of <code>~640 MB/s</code> we're now much closer to the expected <code>~696 MB/s</code> from above. What about the IO latency? (The <code>await</code> statistic from the <a href="http://man7.org/linux/man-pages/man1/iostat.1.html">iostat reporting tool</a>):</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/3gewP8Uml8nprMoLHFTeCR/5f559ee898a438446b8cbccf962290c9/await-annotated.png" />
            
            </figure><p>The latency has been cut in half as well!</p>
    <div>
      <h4>To production</h4>
      <a href="#to-production">
        
      </a>
    </div>
    <p>So far we have been using a synthetic setup with some parts of the full production stack missing, like file systems, real hardware and most importantly, production workload. To ensure we’re not optimising imaginary things, here is a snapshot of the production impact these changes bring to the caching part of our stack:</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/6efmOs555BLNsL8xsxw7YP/7c43e23c6c93013511e06fd191cab393/prod.png" />
            
            </figure><p>This graph represents a three-way comparison of the worst-case response times (99th percentile) for a <a href="/how-we-scaled-nginx-and-saved-the-world-54-years-every-day/">cache hit in one of our servers</a>. The green line is from a server with unencrypted disks, which we will use as baseline. The red line is from a server with encrypted disks with the default Linux disk encryption implementation and the blue line is from a server with encrypted disks and our optimisations enabled. As we can see the default Linux disk encryption implementation has a significant impact on our cache latency in worst case scenarios, whereas the patched implementation is indistinguishable from not using encryption at all. In other words the improved encryption implementation does not have any impact at all on our cache response speed, so we basically get it for free! That’s a win!</p>
    <div>
      <h3>We're just getting started</h3>
      <a href="#were-just-getting-started">
        
      </a>
    </div>
    <p>This post shows how an architecture review can double the performance of a system. Also we <a href="/how-expensive-is-crypto-anyway/">reconfirmed that modern cryptography is not expensive</a> and there is usually no excuse not to protect your data.</p><p>We are going to submit this work for inclusion in the main kernel source tree, but most likely not in its current form. Although the results look encouraging we have to remember that Linux is a highly portable operating system: it runs on powerful servers as well as small resource constrained IoT devices and on <a href="/arm-takes-wing/">many other CPU architectures</a> as well. The current version of the patches just optimises disk encryption for a particular workload on a particular architecture, but Linux needs a solution which runs smoothly everywhere.</p><p>That said, if you think your case is similar and you want to take advantage of the performance improvements now, you may <a href="https://github.com/cloudflare/linux/blob/master/patches/">grab the patches</a> and hopefully provide feedback. The runtime flag makes it easy to toggle the functionality on the fly and a simple A/B test may be performed to see if it benefits any particular case or setup. These patches have been running across our <a href="https://www.cloudflare.com/network/">wide network of more than 200 data centres</a> on five generations of hardware, so can be reasonably considered stable. Enjoy both performance and security from Cloudflare for all!</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/2QGDkwHsSsWOJBpiZYunWH/81406719438a6797a82752855ee57cdd/perf-sec.png" />
            
            </figure>
    <div>
      <h3>Update (October 11, 2020)</h3>
      <a href="#update-october-11-2020">
        
      </a>
    </div>
    <p>The main patch from this blog (in a slightly updated form) has been <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/drivers/md/dm-crypt.c?id=39d42fa96ba1b7d2544db3f8ed5da8fb0d5cb877">merged</a> into mainline Linux kernel and is available since version 5.9 and onwards. The main difference is the mainline version exposes two flags instead of one, which provide the ability to bypass dm-crypt workqueues for reads and writes independently. For details, see <a href="https://www.kernel.org/doc/html/latest/admin-guide/device-mapper/dm-crypt.html">the official dm-crypt documentation</a>.</p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Kernel]]></category>
            <category><![CDATA[Performance]]></category>
            <category><![CDATA[Security]]></category>
            <category><![CDATA[Cryptography]]></category>
            <guid isPermaLink="false">2jUxkv277c7kroisM2311O</guid>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
        <item>
            <title><![CDATA[Using Go as a scripting language in Linux]]></title>
            <link>https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/</link>
            <pubDate>Tue, 20 Feb 2018 19:49:17 GMT</pubDate>
            <description><![CDATA[ At Cloudflare we like Go. We use it in many in-house software projects as well as parts of bigger pipeline systems. But can we take Go to the next level and use it as a scripting language for our favourite operating system, Linux? ]]></description>
            <content:encoded><![CDATA[ <p>At Cloudflare we like Go. We use it in many <a href="/what-weve-been-doing-with-go/">in-house software projects</a> as well as parts of <a href="/meet-gatebot-a-bot-that-allows-us-to-sleep/">bigger pipeline systems</a>. But can we take Go to the next level and use it as a scripting language for our favourite operating system, Linux?</p>
            <figure>
            
            <img src="https://cf-assets.www.cloudflare.com/zkvhlag99gkb/4xPPT6bpUh23aWgb47ugak/64a8b0f1ad6d51a000034829b8d26fd4/gopher-tux-1.png" />
            
            </figure><p><a href="https://golang.org/doc/gopher/gophercolor.png">gopher image</a> <a href="https://creativecommons.org/licenses/by/3.0/">CC BY 3.0</a> <a href="http://reneefrench.blogspot.com/">Renee French</a><a href="https://pixabay.com/en/linux-penguin-tux-2025536/">Tux image</a> <a href="https://creativecommons.org/publicdomain/zero/1.0/deed.en">CC0 BY</a> <a href="https://pixabay.com/en/users/OpenClipart-Vectors-30363/">OpenClipart-Vectors</a></p>
    <div>
      <h3>Why consider Go as a scripting language</h3>
      <a href="#why-consider-go-as-a-scripting-language">
        
      </a>
    </div>
    <p>Short answer: why not? Go is relatively easy to learn, not too verbose and there is a huge ecosystem of libraries which can be reused to avoid writing all the code from scratch. Some other potential advantages it might bring:</p><ul><li><p>Go-based build system for your Go project: <code>go build</code> command is mostly suitable for small, self-contained projects. More complex projects usually adopt some build system/set of scripts. Why not have these scripts written in Go then as well?</p></li><li><p>Easy non-privileged package management out of the box: if you want to use a third-party library in your script, you can simply <code>go get</code> it. And because the code will be installed in your <code>GOPATH</code>, getting a third-party library does not require administrative privileges on the system (unlike some other scripting languages). This is especially useful in large corporate environments.</p></li><li><p>Quick code prototyping on early project stages: when you're writing the first iteration of the code, it usually takes a lot of edits even to make it compile and you have to waste a lot of keystrokes on <i>"edit-&gt;build-&gt;check"</i> cycle. Instead you can skip the "build" part and just immediately execute your source file.</p></li><li><p>Strongly-typed scripting language: if you make a small typo somewhere in the middle of the script, most scripts will execute everything up to that point and fail on the typo itself. This might leave your system in an inconsistent state. With strongly-typed languages many typos can be caught at compile time, so the buggy script will not run in the first place.</p></li></ul>
    <div>
      <h3>Current state of Go scripting</h3>
      <a href="#current-state-of-go-scripting">
        
      </a>
    </div>
    <p>At first glance Go scripts seem easy to implement with Unix support of <a href="https://en.wikipedia.org/wiki/Shebang_(Unix)">shebang lines</a> for scripts. A shebang line is the first line of the script, which starts with <code>#!</code> and specifies the script interpreter to be used to execute the script (for example, <code>#!/bin/bash</code> or <code>#!/usr/bin/env python</code>), so the system knows exactly how to execute the script regardless of the programming language used. And Go already supports interpreter-like invocation for <code>.go</code> files with <code>go run</code> command, so it should be just a matter of adding a proper shebang line, something like <code>#!/usr/bin/env go run</code>, to any <code>.go</code> file, setting the executable bit and we're good to go.</p><p>However, there are problems around using <code>go run</code> directly. <a href="https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1e0bd">This great post</a> describes in detail all the issues around <code>go run</code> and potential workarounds, but the gist is:</p><ul><li><p><code>go run</code> does not properly return the script error code back to the operating system and this is important for scripts, because error codes are one of the most common ways multiple scripts interact with each other and the operating system environment.</p></li><li><p>you can't have a shebang line in a valid <code>.go</code> file, because Go does not know how to process lines starting with <code>#</code>. Other scripting languages do not have this problem, because for most of them <code>#</code> is a way to specify comments, so the final interpreter just ignores the shebang line, but Go comments start with <code>//</code> and <code>go run</code> on invocation will just produce an error like:</p></li></ul>
            <pre><code>package main:
helloscript.go:1:1: illegal character U+0023 '#'</code></pre>
            <p><a href="https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1e0bd">The post</a> describes several workarounds for above issues including using a custom wrapper program <a href="https://github.com/erning/gorun">gorun</a> as an interpreter, but all of them do not provide an ideal solution. You either:</p><ul><li><p>have to use non-standard shebang line, which starts with <code>//</code>. This is technically not even a shebang line, but the way how <code>bash</code> shell processes executable text files, so this solution is <code>bash</code> specific. Also, because of the specific behaviour of <code>go run</code>, this line is rather complex and not obvious (see <a href="https://gist.github.com/posener/73ffd326d88483df6b1cb66e8ed1e0bd">original post</a> for examples).</p></li><li><p>have to use a custom wrapper program <a href="https://github.com/erning/gorun">gorun</a> in the shebang line, which works well, however, you end up with <code>.go</code> files, which are not compilable with standard <code>go build</code> command because of the illegal <code>#</code> character.</p></li></ul>
    <div>
      <h3>How Linux executes files</h3>
      <a href="#how-linux-executes-files">
        
      </a>
    </div>
    <p>OK, it seems the shebang approach does not provide us with an all-rounder solution. Is there anything else we could use? Let's take a closer look how Linux kernel executes binaries in the first place. When you try to execute a binary/script (or any file for that matter which has executable bit set), your shell in the end will just use Linux <code>execve</code> <a href="https://en.wikipedia.org/wiki/System_call">system call</a> passing it the filesystem path of the binary in question, command line parameters and currently defined environment variables. Then the kernel is responsible for correct parsing of the file and creating a new process with the code from the file. Most of us know that Linux (and many other Unix-like operating systems) use <a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">ELF binary format</a> for its executables.</p><p>However, one of the core principles of Linux kernel development is to avoid "vendor/format lock-in" for any subsystem, which is part of the kernel. Therefore, Linux implements a "pluggable" system, which allows any binary format to be supported by the kernel - all you have to do is to write a correct module, which can parse the format of your choosing. And if you take a closer look at the kernel source code, you'll see that Linux supports more binary formats out of the box. For example, for the recent <code>4.14</code> Linux kernel <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/tree/fs?h=linux-4.14.y">we can see</a> that it supports at least 7 binary formats (in-tree modules for various binary formats usually have <code>binfmt_</code> prefix in their names). It is worth to note the <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/tree/fs/binfmt_script.c?h=linux-4.14.y">binfmt_script</a> module, which is responsible for parsing above mentioned shebang lines and executing scripts on the target system (not everyone knows that the shebang support is actually implemented in the kernel itself and not in the shell or other daemon/process).</p>
    <div>
      <h3>Extending supported binary formats from userspace</h3>
      <a href="#extending-supported-binary-formats-from-userspace">
        
      </a>
    </div>
    <p>But since we concluded that shebang is not the best option for our Go scripting, seems we need something else. Surprisingly Linux kernel already has a <a href="https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git/tree/fs/binfmt_misc.c?h=linux-4.14.y">"something else" binary support module</a>, which has an appropriate name <code>binfmt_misc</code>. The module allows an administrator to dynamically add support for various executable formats directly from userspace through a well-defined <code>procfs</code> interface and is <a href="https://www.kernel.org/doc/html/v4.14/admin-guide/binfmt-misc.html">well-documented</a>.</p><p>Let's follow <a href="https://www.kernel.org/doc/html/v4.14/admin-guide/binfmt-misc.html">the documentation</a> and try to setup a binary format description for <code>.go</code> files. First of all the guide tells you to mount special <code>binfmt_misc</code> filesystem to <code>/proc/sys/fs/binfmt_misc</code>. If you're using relatively recent systemd-based Linux distribution, it is highly likely the filesystem is already mounted for you, because systemd by default installs special <a href="https://github.com/systemd/systemd/blob/master/units/proc-sys-fs-binfmt_misc.mount">mount</a> and <a href="https://github.com/systemd/systemd/blob/master/units/proc-sys-fs-binfmt_misc.automount">automount</a> units for this purpose. To double-check just run:</p>
            <pre><code>$ mount | grep binfmt_misc
systemd-1 on /proc/sys/fs/binfmt_misc type autofs (rw,relatime,fd=27,pgrp=1,timeout=0,minproto=5,maxproto=5,direct)</code></pre>
            <p>Another way is to check if you have any files in <code>/proc/sys/fs/binfmt_misc</code>: properly mounted <code>binfmt_misc</code> filesystem will create at least two special files with names <code>register</code> and <code>status</code> in that directory.</p><p>Next, since we do want our <code>.go</code> scripts to be able to properly pass the exit code to the operating system, we need the custom <a href="https://github.com/erning/gorun">gorun</a> wrapper as our "interpreter":</p>
            <pre><code>$ go get github.com/erning/gorun
$ sudo mv ~/go/bin/gorun /usr/local/bin/</code></pre>
            <p>Technically we don't need to move <code>gorun</code> to <code>/usr/local/bin</code> or any other system path as <code>binfmt_misc</code> requires full path to the interpreter anyway, but the system may run this executable with arbitrary privileges, so it is a good idea to limit access to the file from security perspective.</p><p>At this point let's create a simple toy Go script <code>helloscript.go</code> and verify we can successfully "interpret" it. The script:</p>
            <pre><code>package main

import (
	"fmt"
	"os"
)

func main() {
	s := "world"

	if len(os.Args) &gt; 1 {
		s = os.Args[1]
	}

	fmt.Printf("Hello, %v!", s)
	fmt.Println("")

	if s == "fail" {
		os.Exit(30)
	}
}</code></pre>
            <p>Checking if parameter passing and error handling works as intended:</p>
            <pre><code>$ gorun helloscript.go
Hello, world!
$ echo $?
0
$ gorun helloscript.go gopher
Hello, gopher!
$ echo $?
0
$ gorun helloscript.go fail
Hello, fail!
$ echo $?
30</code></pre>
            <p>Now we need to tell <code>binfmt_misc</code> module how to execute our <code>.go</code> files with <code>gorun</code>. Following <a href="https://www.kernel.org/doc/html/v4.14/admin-guide/binfmt-misc.html">the documentation</a> we need this configuration string: <code>:golang:E::go::/usr/local/bin/gorun:OC</code>, which basically tells the system: "if you encounter an executable file with <code>.go</code> extension, please, execute it with <code>/usr/local/bin/gorun</code> interpreter". The <code>OC</code> flags at the end of the string make sure, that the script will be executed according to the owner information and permission bits set on the script itself, and not the ones set on the interpreter binary. This makes Go script execution behaviour same as the rest of the executables and scripts in Linux.</p><p>Let's register our new Go script binary format:</p>
            <pre><code>$ echo ':golang:E::go::/usr/local/bin/gorun:OC' | sudo tee /proc/sys/fs/binfmt_misc/register
:golang:E::go::/usr/local/bin/gorun:OC</code></pre>
            <p>If the system successfully registered the format, a new file <code>golang</code> should appear under <code>/proc/sys/fs/binfmt_misc</code> directory. Finally, we can natively execute our <code>.go</code> files:</p>
            <pre><code>$ chmod u+x helloscript.go
$ ./helloscript.go
Hello, world!
$ ./helloscript.go gopher
Hello, gopher!
$ ./helloscript.go fail
Hello, fail!
$ echo $?
30</code></pre>
            <p>That's it! Now we can edit <code>helloscript.go</code> to our liking and see the changes will be immediately visible the next time the file is executed. Moreover, unlike the previous shebang approach, we can compile this file any time into a real executable with <code>go build</code>.</p><hr /><p><i>Whether you like Go or digging in Linux internals, we have positions for either or these and even both of them at once. Check-out </i><a href="https://www.cloudflare.com/careers/"><i>our careers page.</i></a></p> ]]></content:encoded>
            <category><![CDATA[Linux]]></category>
            <category><![CDATA[Go]]></category>
            <category><![CDATA[Tech Talks]]></category>
            <category><![CDATA[Developers]]></category>
            <category><![CDATA[Programming]]></category>
            <category><![CDATA[Deep Dive]]></category>
            <guid isPermaLink="false">5Mg46SluRhqUD4mQaKqahI</guid>
            <dc:creator>Ignat Korchagin</dc:creator>
        </item>
    </channel>
</rss>