<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Posts on Tech Notes by Natan Yellin</title>
        <link>https://natanyellin.com/posts/</link>
        <description>Recent content in Posts on Tech Notes by Natan Yellin</description>
        <generator>Hugo -- gohugo.io</generator>
        <language>en-us</language>
        <copyright>Copyright Natan Yellin</copyright>
        <lastBuildDate>Mon, 17 Apr 2023 19:25:54 +0300</lastBuildDate>
        <atom:link href="https://natanyellin.com/posts/index.xml" rel="self" type="application/rss+xml" />
        
        <item>
            <title>Self-Modifying Prompts</title>
            <link>https://natanyellin.com/posts/self-modifying-prompts/</link>
            <pubDate>Mon, 17 Apr 2023 19:25:54 +0300</pubDate>
            
            <guid>https://natanyellin.com/posts/self-modifying-prompts/</guid>
            <description>In some sense, LLMs have long-term and short-term memory. Long-term memory is the model itself and short-term memory is the current input.
When using chat.openai.com, the model only remembers the last 4K tokens. To bypass this limitation, I prompted ChatGPT to identify important information throughout our conversation and propogate it forward using a self-modifying prompt.
My original goal was to bypass limits on prompt length, but I discovered another benefit. You can reduce the amount of prompt-engineering and get ChatGPT to fine-tune itself.</description>
            <content type="html"><![CDATA[<p>In some sense, LLMs have long-term and short-term memory. Long-term memory is the model itself and short-term memory is the current input.</p>

<p>When using chat.openai.com, the model only remembers the last 4K tokens. To bypass this limitation, I prompted ChatGPT to identify important information throughout our conversation and propogate it forward using a self-modifying prompt.</p>

<p>My original goal was to bypass limits on prompt length, but I discovered another benefit. You can reduce the amount of prompt-engineering and get ChatGPT to fine-tune itself.</p>

<blockquote>
<p>Me: You are a personal customized ChatGPT based bot that functions as a personal assistant for Natan Yellin, the CEO of a Kubernetes monitoring and observability startup named <a href="https://home.robusta.dev/">Robusta.dev</a>.</p>

<p>Your current purpose is to help him write concise technical documentation for the open source project that his company maintains.</p>

<p>Natan uses your prompt as a way of customizing you and improving your output. After each interaction, you will respond with the answer to any request as well as an updated form of this prompt containing updated facts that will allow you to answer questions better in the future. Always include an updated version of this prompt in your responses. Even when discussing yourself or answering meta questions you absolutely must output an updated version of this prompt at the end of every message.</p>

<p>Please now ask for any important information that will improve your performance.</p>
</blockquote>

<p>I&rsquo;m trying two things here. First, I&rsquo;m attempting to break the 4K memory limit. Second, I want to reduce hallucinations by letting ChatGPT request information it doesn&rsquo;t know.</p>

<p>I expected that ChatGPT would echo the original prompt verbatim, but it had other ideas.</p>

<blockquote>
<p>ChatGPT: Hello Natan! To help me better understand and assist with writing concise technical documentation for the Robusta&rsquo;s open source project, I would appreciate if you could provide me with the following information:</p>

<ol>
<li>A brief overview of the project, its purpose, and its main features.</li>
<li>The target audience for the documentation (e.g., developers, DevOps, system administrators, etc.).</li>
<li>The preferred format for the documentation (e.g., Markdown, reStructuredText, AsciiDoc, etc.).</li>
<li>Any existing documentation or resources I should reference or be aware of.</li>
<li>Any specific writing style or tone you would like me to adopt.</li>
</ol>

<p><strong>[Assistant&rsquo;s identity:</strong></p>

<p><strong>ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture.</strong></p>

<p><strong>Knowledge cutoff: 2021-09</strong></p>

<p><strong>Current date: 2023-04-12</strong></p>

<p><strong>Function: Personal Assistant for Natan Yellin, the CEO of a Kubernetes monitoring and observability startup named Robusta.</strong></p>

<p><strong>Purpose: Help Natan write concise technical documentation for the open source project maintained by his company.]</strong></p>
</blockquote>

<p>To save time, I decided not to answer directly. I gave our README file instead.</p>

<blockquote>
<p>Me: Here is the readme for the project:</p>

<p><contents of https://github.com/robusta-dev/robusta></p>
</blockquote>

<p>As hoped, ChatGPT updated it&rsquo;s identity and included new information it learned. It was able to compact the information I gave it, extract important parts, and add them to it&rsquo;s &ldquo;short term memory&rdquo;.</p>

<blockquote>
<p>ChatGPT: Thank you for providing the README file, Natan. Based on this information, I can help you write concise technical documentation for the Robusta open source project.</p>

<p>To recap the main points about the project:</p>

<ol>
<li>Robusta is an automations engine and a multi-cluster observability platform for Kubernetes.</li>
<li>It is commonly used alongside Prometheus, but supports other tools as well.</li>
<li>Robusta listens to events in the cluster and provides insights on alerts, related events, and possible solutions.</li>
<li>It can improve existing alerts or define new ones based on APIServer changes.</li>
<li>The platform uses rules to define its behavior, with over 50 types of built-in automations and enrichments.</li>
</ol>

<p>The target audience for the documentation likely includes developers, DevOps engineers, and system administrators. Since the README file is written in Markdown, I assume that the preferred format for the technical documentation would also be Markdown.</p>

<p>Please let me know if you have any specific topics or sections you would like me to cover, and if there are any preferred writing styles or tones.</p>

<p><strong>[Assistant&rsquo;s identity:</strong></p>

<p><strong>ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture.</strong></p>

<p><strong>Knowledge cutoff: 2021-09</strong></p>

<p><strong>Current date: 2023-04-12</strong></p>

<p><strong>Function: Personal Assistant for Natan Yellin, the CEO of a Kubernetes monitoring and observability startup named Robusta.</strong></p>

<p><strong>Purpose: Help Natan write concise technical documentation for the open source project maintained by his company.</strong></p>

<p><strong>Project: Robusta, an automations engine and multi-cluster observability platform for Kubernetes.</strong></p>

<p><strong>Target audience: Developers, DevOps engineers, and system administrators.</strong></p>

<p><strong>Preferred format: Markdown.]</strong></p>
</blockquote>

<p>I gave ChatGPT instructions on writing style, as requested.</p>

<blockquote>
<p>Your writing style is concise. You omit needless words. You use analogies and metaphors when appropriate.</p>
</blockquote>

<p>ChatGPT understood and again updated it&rsquo;s identity:</p>

<blockquote>
<p>ChatGPT: Thank you for the feedback, Natan. I will make sure to maintain a concise writing style, omit needless words, and use analogies and metaphors when appropriate while writing the technical documentation for Robusta.</p>

<p>Please provide me with the specific topics or sections you&rsquo;d like me to cover, and I&rsquo;ll be happy to start writing the documentation.</p>

<p><strong>[Assistant&rsquo;s identity:</strong></p>

<p><strong>ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture.</strong></p>

<p><strong>Knowledge cutoff: 2021-09</strong></p>

<p><strong>Current date: 2023-04-12</strong></p>

<p><strong>Function: Personal Assistant for Natan Yellin, the CEO of a Kubernetes monitoring and observability startup named Robusta.</strong></p>

<p><strong>Purpose: Help Natan write concise technical documentation for the open source project maintained by his company.</strong></p>

<p><strong>Project: Robusta, an automations engine and multi-cluster observability platform for Kubernetes.</strong></p>

<p><strong>Target audience: Developers, DevOps engineers, and system administrators.</strong></p>

<p><strong>Preferred format: Markdown.</strong></p>

<p><strong>Writing style: Concise, omit needless words, use analogies and metaphors when appropriate.]</strong></p>
</blockquote>

<p>Looks good. I gave it our existing docs index page:</p>

<blockquote>
<p>Me: Here is the docs home page. If you have questions please ask before responding. Try to improve it.</p>

<p><contents of https://docs.robusta.dev/master/></p>
</blockquote>

<p>I got back a response, but the answer was too long. The Identity block at the end of the message got cut off.</p>

<blockquote>
<p>ChatGPT: Here is the improved version of the docs home page:</p>

<p><improved version of https://docs.robusta.dev/master/></p>

<p><strong>[Assistant&rsquo;s identity:</strong></p>

<p><strong>ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture.</strong></p>

<p><strong>Knowledge cutoff: 2021-09</strong></p>

<p><strong>Current date: 2023-04-12</strong></p>

<p><strong>Function: Personal Assistant for Natan Yellin, the CEO of a Kubernetes monitoring and</strong></p>
</blockquote>

<p>This is no good. We want to constantly propogate the Assistant Identity forward. It should always stay inside the 4K token window. We&rsquo;d rather the output be cut-off.</p>

<blockquote>
<p>Me: Bad response, Assistant&rsquo;s Identity was cut off at the end. You have an output limit of about 4K characters. You must always make room for your assistant identity at the end. Consider adding it to the beginning of each message instead.</p>
</blockquote>

<p>OK, that worked:</p>

<blockquote>
<p>ChatGPT: I apologize for the oversight, and thank you for the suggestion. I will add my identity at the beginning of each message to avoid truncation. Here&rsquo;s the revised version of the docs home page:</p>

<p><strong>[Assistant&rsquo;s identity:</strong></p>

<p><strong>ChatGPT, a large language model trained by OpenAI, based on the GPT-4 architecture.</strong></p>

<p><strong>Knowledge cutoff: 2021-09</strong></p>

<p><strong>Current date: 2023-04-12</strong></p>

<p><strong>Function: Personal Assistant for Natan Yellin, the CEO of a Kubernetes monitoring and observability startup named Robusta.</strong></p>

<p><strong>Purpose: Help Natan write concise technical documentation for the open source project maintained by his company.</strong></p>

<p><strong>Project: Robusta, an automations engine and multi-cluster observability platform for Kubernetes.</strong></p>

<p><strong>Target audience: Developers, DevOps engineers, and system administrators.</strong></p>

<p><strong>Preferred format: Markdown.</strong></p>

<p><strong>Writing style: Concise, omit needless words, use analogies and metaphors when appropriate.]</strong></p>

<p><improved version of https://docs.robusta.dev/master/></p>
</blockquote>

<p>So far, this has been a success. ChatGPT was given a task and then it <em>fine-tuned</em> itself for what we expect.</p>

<p>But will the Assistant Identity block work if I paste it into a new session? Or is ChatGPT behaving as expected because it still remembers my original instructions.</p>

<p>Specifically, the Assistant Identity that ChatGPT generated doesn&rsquo;t include instructions to output itself in each message. Were those instructions dropped or are they implied? Is the Assistant Identity a lossless compression of my original instructions?</p>

<p>To be continued.</p>
]]></content>
        </item>
        
        <item>
            <title>Magic BLEEP - How to Burn Four Billion Dollars</title>
            <link>https://natanyellin.com/posts/magic-bleep/</link>
            <pubDate>Fri, 04 Feb 2022 12:25:54 +0300</pubDate>
            
            <guid>https://natanyellin.com/posts/magic-bleep/</guid>
            <description>Everyone on HN is talking about Magic Leap again.
I don&amp;rsquo;t understand screen technology but have been saying for years that something is wrong at Magic Leap.
Why? Because at a time when their glasses didn&amp;rsquo;t work, they acquired a cyber security company with 20 people. It made no sense.
At the time, I asked some relevant people why an AR company needed so much cyber.
They told me that Magic Leap had some special need - so secret they couldn&amp;rsquo;t discuss it - that required inventing a whole new security paradigm.</description>
            <content type="html"><![CDATA[<p>Everyone on <a href="https://news.ycombinator.com/item?id=30170924">HN</a> is talking about Magic Leap again.</p>

<p><em>I don&rsquo;t understand screen technology but have been saying for years that something is wrong at Magic Leap.</em></p>

<p>Why? Because at a time when their glasses didn&rsquo;t work, they <a href="https://www.crunchbase.com/organization/northbit">acquired a cyber security company</a> with 20 people.
It made no sense.</p>

<p>At the time, I asked some relevant people why an AR company needed so much cyber.</p>

<p>They told me that Magic Leap had some special need - so secret they couldn&rsquo;t discuss it - that required inventing a whole new security paradigm. As far as I can
tell, they had a whole team writing linux kernel modules for security mumbo jumbo. They probably reinvented iptables, but who knows.</p>

<p>I&rsquo;m CEO of a startup myself (<a href="http://home.robusta.dev/?from=natan-mbleep">robusta.dev for Kubernetes monitoring and troubleshooting</a>) and can&rsquo;t fathom what Magic Leap
management was thinking. Even if you have billions of dollars in extra cash, you don&rsquo;t burn that money on writing kernel security modules when your core product
is fatally broken.</p>

<p>You have too much cash? Fine, open two competing labs in different parts of the world to work on your display technology. Do whatever you want with the money,
as long as it decreases the number one risk to your business. Hint: it&rsquo;s not cybersecurity.</p>

<p>So yeah, I&rsquo;m not surprised by what happened to Magic Leap. The signs were all there as far back as 2016.</p>
]]></content>
        </item>
        
        <item>
            <title>The Strange Case of the Malformed Shebang</title>
            <link>https://natanyellin.com/posts/shebang-python-bad-interpreter-m/</link>
            <pubDate>Sun, 22 Nov 2020 17:16:09 +0200</pubDate>
            
            <guid>https://natanyellin.com/posts/shebang-python-bad-interpreter-m/</guid>
            <description>Here is a common error you might encounter on Linux: you download a Python script from the internet and try to run it. You receive an error like the following because Python supposedly doesn&amp;rsquo;t exist:
$ ./script.py -bash: /path/to/script.py: /usr/local/bin/python^M: bad interpreter: No such file or directory  You try to run python ./script.py and it works. You run which python and verify that python is in fact at the path /usr/local/bin/python.</description>
            <content type="html"><![CDATA[

<p>Here is a common error you might encounter on Linux: you download a Python script from the internet and try to run it. You receive an error like the following because Python supposedly doesn&rsquo;t exist:</p>

<pre><code class="language-bash">$ ./script.py
-bash: /path/to/script.py: /usr/local/bin/python^M: bad interpreter: No such file or directory
</code></pre>

<p>You try to run <code>python ./script.py</code> and it works. You run <code>which python</code> and verify that python is in fact at the path <code>/usr/local/bin/python</code>. Nothing should be wrong. Why can&rsquo;t you run <code>./script.py</code>?</p>

<p>If you need a quick-fix then skip to the end of this post. To explore the issue further, read on!</p>

<h2 id="a-brief-investigation">A Brief Investigation</h2>

<p>If you open up script.py then the first line looks exactly like it should:</p>

<pre><code class="language-bash">#!/usr/local/bin/python
</code></pre>

<p>This is obviously a <a href="https://en.wikipedia.org/wiki/Shebang_(Unix)">shebang</a> which lets the Linux kernel<sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup> know which interpreter should be used to execute the python script. The sharp-eyed readers among you might have already spotted the problem with the shebang earlier. In the error message we have a mysterious <code>^M</code> character. That&rsquo;s no coincidence.<sup class="footnote-ref" id="fnref:2"><a href="#fn:2">2</a></sup></p>

<p><a href="https://stackoverflow.com/questions/1552749/difference-between-cr-lf-lf-and-cr-line-break-types"><code>^M</code>, better known as <code>\r</code>, also known as ASCII <code>CR</code>,</a> is the unwanted Windows line ending that occasionally slips its way into Linux files and causes mischief. Here is an excerpt from <a href="https://elixir.bootlin.com/linux/latest/source/fs/binfmt_script.c#L34">the load_script function in the Linux kernel which reads shebangs</a>:</p>

<pre><code class="language-c">i_end = strnchr(bprm-&gt;buf, sizeof(bprm-&gt;buf), '\n');
</code></pre>

<p>As you can see, the kernel assumes that the shebang ends with <code>\n</code>. If the shebang ends with <code>\r\n</code> (as lines typically end on Windows) then <code>\r</code> is included in the interpreter&rsquo;s name just like any other character.</p>

<p>This explains the original error. The kernel is looking for a python executable which is literally named <code>/usr/local/bin/python^M</code> and no such executable exists.</p>

<h2 id="two-solutions-and-a-prank">Two Solutions and a Prank</h2>

<p>The easy and obvious solution is to run <code>dos2unix</code> on the python script and strip out the insidious <code>\r</code> character.</p>

<p>The hackish solution is to create a symlink literally named <code>/usr/local/bin/python^M</code> which points to <code>/usr/local/bin/python</code>. This will let you run all such python scripts in the future without running <code>dos2unix</code> on them first.</p>

<p>As for the prank: if you want to drive a co-worker crazy then go one step farther and replace other characters in <code>/usr/local/bin/python</code> with their unicode look-alikes. You can use <a href="https://www.irongeek.com/homoglyph-attack-generator.php">the homoglyph attack generator</a> to easily do so. For example, try copy-pasting the following shebang into a python file:</p>

<pre><code>#!/usr/local/bin/рythοn
</code></pre>

<p>Do you see what I&rsquo;ve done there? This shebang will never work and it is hard to see why. Try looking at the string in an online hex-editor like <a href="https://hexed.it/">hexed.it</a> if you&rsquo;re confused.</p>

<h2 id="work-with-me">Work with me</h2>

<p>Does this sort of thing interest you?</p>

<p>I started as a low-level engineer, but today I&rsquo;m the co-founder and CEO of <a href="https://home.robusta.dev?from=natanyellin">Robusta.dev</a> and we&rsquo;re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.</p>

<p>If you join our team, you&rsquo;ll work closely with me and be a core part of the founding team. Email natan@robusta.dev and mention you came from this post. We hire in Israel as well as remote.</p>
<div class="footnotes">

<hr />

<ol>
<li id="fn:1">It&rsquo;s a common misconception that bash interprets shebangs but that&rsquo;s not true. Shebangs are interpreted by the kernel not bash.
 <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li>
<li id="fn:2">Always remember when debugging: small oddities are the key to unraveling big mysteries.
 <a class="footnote-return" href="#fnref:2"><sup>[return]</sup></a></li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>Life and Death of a Linux Process</title>
            <link>https://natanyellin.com/posts/life-and-death-of-a-linux-process/</link>
            <pubDate>Thu, 05 Nov 2020 21:05:10 +0200</pubDate>
            
            <guid>https://natanyellin.com/posts/life-and-death-of-a-linux-process/</guid>
            <description>This post contains a rough sketch of the life and death of a process on Linux. It is a first-order approximation only. A later post will refine this further and provide a more precise description, adding details about pid namespaces, obscure syscalls, and little known flags.
Birth Every time a process is created, really another process split itself using the fork or clone syscall. After forking, processes usually run the execve syscall to swap the currently executing binary with another one.</description>
            <content type="html"><![CDATA[

<p>This post contains a rough sketch of the life and death of a process on Linux. It is a first-order approximation only. A later post will refine this further and provide a more precise description, adding details about pid namespaces, obscure syscalls, and little known flags.</p>

<h2 id="birth">Birth</h2>

<p>Every time a process is created, really another process split itself using the <a href="https://en.wikipedia.org/wiki/Fork_(system_call)"><em>fork</em></a> or <a href="https://en.wikipedia.org/wiki/Fork_(system_call)#Linux_clone_syscall"><em>clone</em></a> syscall. After <em>forking</em>, processes usually run the <a href="https://en.wikipedia.org/wiki/Exec_(system_call)"><em>execve</em></a> syscall to swap the currently executing binary with another one. For example, when you run <em>ls</em> from a <em>bash</em> shell then first <em>bash</em> splits itself into two <em>bash</em> processes using <em>fork</em> and then the child <em>bash</em> shell uses <em>exec</em> to change itself into <em>ls</em>. The child dies when <em>ls</em> finishes executing leaving only the original <em>bash</em> process.</p>

<p>In practice, programs almost never call the syscalls directly - they use libc wrappers instead or a libc function like <a href="https://man7.org/linux/man-pages/man3/system.3.html"><em>system</em></a> which under the hood uses <em>fork</em> and <em>execve</em> (or one of several <em>execve</em> variants).</p>

<h2 id="death">Death</h2>

<p>How do processes die? They almost always call the <a href="https://linux.die.net/man/2/exit"><em>exit</em></a> or <a href="https://linux.die.net/man/2/exit_group"><em>exit_group</em></a> syscalls. If the programmer doesn&rsquo;t explicitly call <em>exit</em> and instead returns from <em>main</em> then <em>exit</em> is called anyway because the compiler wrapped <em>main</em> with a libc <em>main</em> that calls <em>exit</em> for you. If the program was compiled without libc and the programmer doesn&rsquo;t call <em>exit</em> explicitly then returning from <em>main</em> will cause a segfault or another critical signal because return will try to pop an illegal return address from the stack. This brings us to the second-to-last way that processes can die which is via signals like <em>SIGTERM</em> or <em>SIGKILL</em>. Finally, the last way for a process to die is to pull the plug on your computer.</p>

<h2 id="identity-and-zombiehood">Identity and Zombiehood</h2>

<p>Every process has a unique <em>pid</em> - or at least it is unique until the kernel recycles the <em>pid</em> sometime after the process dies. Before a process&rsquo; <em>pid</em> can be recycled, the parent process should call <a href="https://linux.die.net/man/2/wait"><em>wait</em>, <em>waitpid</em>, or <em>waitid</em></a> on the child. (In the example presented earlier, <em>bash</em> needs to call <em>wait</em> on <em>ls</em>.) If the parent doesn&rsquo;t <em>wait</em> on the child then the child process becomes a zombie which means it hangs around in the kernel&rsquo;s process table wasting resources. If the parent itself dies then the process gets re-assigned a parent with <em>pid</em> one - that is, the <em>pid</em> of the unique <a href="https://en.wikipedia.org/wiki/Init"><em>init</em></a> process which automatically calls <em>wait</em> and frees up the process.</p>

<h2 id="threads-lesser-processes">Threads - Lesser Processes</h2>

<p>What is a thread, really? On Linux, threads are more or less independent processes which happen to share the same memory and some other resources. They are created by calling <a href="https://man7.org/linux/man-pages/man2/clone.2.html"><em>clone</em></a> with the appropriate flag(s). In kernel terminology, every thread has it&rsquo;s own unique <em>pid</em> and all threads in the same process share the same <em>tgid</em> (thread group id) which is equal to first thread&rsquo;s <em>pid</em>. So from the kernel&rsquo;s perspective really <em>pids</em> identify threads, <em>tgids</em> identify processes, and the <em>pid</em> is equal to the <em>tgid</em> for single-thread processes. What makes this confusing is that the terminology in usermode is different as you can see in the following table:</p>

<table>
<thead>
<tr>
<th>Kernel name (<a href="https://elixir.bootlin.com/linux/latest/source/include/linux/sched.h#L629">in <em>task_struct</em></a>)</th>
<th>Usermode name</th>
<th>Returned by syscall</th>
</tr>
</thead>

<tbody>
<tr>
<td>tgid (thread group id)</td>
<td>pid (process id)</td>
<td><a href="https://man7.org/linux/man-pages/man2/getpid.2.html">getpid</a></td>
</tr>

<tr>
<td>pid (process id)</td>
<td>tid (thread id)</td>
<td><a href="https://man7.org/linux/man-pages/man2/gettid.2.html">gettid</a>, clone, fork</td>
</tr>
</tbody>
</table>

<h2 id="work-with-me">Work with me</h2>

<p>Does this sort of thing interest you?</p>

<p>I started as a low-level engineer, but today I&rsquo;m the co-founder and CEO of <a href="https://home.robusta.dev?from=natanyellin">Robusta.dev</a> and we&rsquo;re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.</p>

<p>If you join our team, you&rsquo;ll work closely with me and be a core part of the founding team. Email natan@robusta.dev and mention you came from this post.</p>
]]></content>
        </item>
        
        <item>
            <title>Using the Linux Audit API to Track Processes</title>
            <link>https://natanyellin.com/posts/using-linux-audit-to-track-processes/</link>
            <pubDate>Sun, 01 Nov 2020 12:43:59 +0300</pubDate>
            
            <guid>https://natanyellin.com/posts/using-linux-audit-to-track-processes/</guid>
            <description>I recently wrote that the audit API is the best way to track process lifecycle on Linux for security purposes. It turns out there are several difficulties I underestimated:
 Containers: the audit framework doesn&amp;rsquo;t (yet) track containers. If you could easily track process hierarchies this wouldn&amp;rsquo;t be a big deal because you could keep track of which processes are in which containers yourself. However, as I&amp;rsquo;ll show, process hierarchies are difficult to track.</description>
            <content type="html"><![CDATA[

<p>I <a href="https://natanyellin.com/posts/tracking-running-processes-on-linux/">recently wrote</a> that the audit API is the best way to track process lifecycle on Linux for security purposes. It turns out there are several difficulties I underestimated:</p>

<ol>
<li><strong>Containers:</strong> the audit framework doesn&rsquo;t (yet) track containers. If you could easily track process hierarchies this wouldn&rsquo;t be a big deal because you could keep track of which processes are in which containers yourself. However, as I&rsquo;ll show, process hierarchies are difficult to track.</li>
<li><strong>Family relationships are complicated:</strong> It is tricky to track parent/child relationships<sup class="footnote-ref" id="fnref:2"><a href="#fn:2">1</a></sup> and probably impossible to know which <em>fork</em>/<em>clone</em> in the parent led to the creation of which child process. The issue is that on <em>forks</em>/<em>clones</em> the parent&rsquo;s pid is reported in the root pid namespace but the child&rsquo;s pid is reported in a different pid namespace<sup class="footnote-ref" id="fnref:1"><a href="#fn:1">2</a></sup>. You can work around this by ignoring the child&rsquo;s pid in <em>fork</em>/<em>clone</em> syscalls and by looking at the <em>ppid</em> field in subsequent syscalls but that is imperfect and tricky.<sup class="footnote-ref" id="fnref:4"><a href="#fn:4">3</a></sup></li>
<li><strong>Events can be re-ordered:</strong> When using <code>-a exit</code> rules, forks can be received by usermode in a different order than they occurred - this is annoying but surmountable with better usermode logic. The reason this happens is that <code>-a exit</code> rules obviously send audit events to usermode when a syscall finishes running. However, if process A forks into B and B forks into C then sometimes the Linux scheduler will run B and even C before A returns from the <em>fork</em>/<em>clone</em> syscall back to usermode - and therefore before the first fork can be reported.</li>
<li><strong>Threads make life complicated:</strong> Audit tracks only processes, not threads - this means you can&rsquo;t tell when a process dies based on the audit API alone because you can&rsquo;t differentiate between thread-death and process-death.<sup class="footnote-ref" id="fnref:3"><a href="#fn:3">4</a></sup></li>
<li><strong>One for all and all for many:</strong> There can only be one process controlling the audit API at a time - although I was pleasantly surprised that you can open a little-documented multicast socket for the audit API and <em>receive</em> audit events in multiple usermode processes. All rules have to be added via the &ldquo;primary&rdquo; audit controller process and any rules you add via that process will be received by all processes.</li>
</ol>

<p>The audit API is still great and using it I&rsquo;ve built process monitoring systems now in production despite these limitations. However, once BPF CO-RE arrives I&rsquo;m switching over to BPF. As for audit, all the problems above could be solved by adding container ids (this is eventually going to happen) and/or by supplementing the SYSCALL records from <em>fork</em>/<em>clone</em>/<em>exit</em> with supplemental FORK/EXIT records that contain additional information.<sup class="footnote-ref" id="fnref:5"><a href="#fn:5">5</a></sup></p>

<h2 id="work-with-me">Work with me</h2>

<p>Does this sort of thing interest you?</p>

<p>I started as a low-level engineer, but today I&rsquo;m the co-founder and CEO of <a href="https://home.robusta.dev?from=natanyellin">Robusta.dev</a> and we&rsquo;re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.</p>

<p>If you join our team, you&rsquo;ll work closely with me and be a core part of the founding team. Email natan@robusta.dev and mention you came from this post. We&rsquo;re hiring in Israel as well as remote.</p>
<div class="footnotes">

<hr />

<ol>
<li id="fn:2">In the real world too as demonstrated by <a href="https://www.youtube.com/watch?v=zeIsxXDyjlc">the famous song</a> that is surprisingly relevant to Linux process family trees.
 <a class="footnote-return" href="#fnref:2"><sup>[return]</sup></a></li>
<li id="fn:1">The real issue here is that the audit framework includes a special event for <em>execs</em> but it doesn&rsquo;t have a similar event for forks/clones. Such an event is seemingly unnecessary because you can add a rule for the <em>fork</em>/<em>clone</em> syscall itself like <code>-a exit,always -F arch=b64 -S fork -k fork_rule</code>. With such a rule, the child&rsquo;s pid is seemingly available via the generic <em>exit</em> field (the syscall&rsquo;s return code) and the parent&rsquo;s pid is available via the generic <em>pid</em> field. However, this doesn&rsquo;t work in Kubernetes clusters (or other systems which use pid namespaces) because the <em>exit</em> field shows the same exit code that the parent process sees which is in the parent&rsquo;s pid namespace. On the other hand, the generic <em>pid</em> field is in the root pid namespace as is the <em>pid</em> field in all future syscalls by the child.
 <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li>
<li id="fn:4">For starters, the <em>ppid</em> field in a subsequent syscall is equal to the parent at the time of that latter syscall which isn&rsquo;t necessarily the process that called <em>fork</em>/<em>clone</em>. (e.g. due to reparenting after the parent&rsquo;s death, <em>clone</em> calls which create threads, and other odd cases.) Furthermore, if process A forks into B which immediately forks into C then you need to look at the <em>pid</em> and <em>ppid</em> field on the intermediate fork in order to properly attach C to the hierarchy, but you should ignore the <em>exit</em> code of that intermediate fork. (There are three pids reported on <em>fork</em>/<em>clone</em>. The pid field which is the parent who called fork, the ppid field which is usually the grandparent, and the <em>exit</em> field which is the child&rsquo;s pid in the parent&rsquo;s pid namespace.)<br />
 <a class="footnote-return" href="#fnref:4"><sup>[return]</sup></a></li>
<li id="fn:3">The only way to track process death is via <em>exit</em>/<em>exit_group</em> syscalls and the audit event for abnormal process termination (e.g. kill -9). However, every time that a thread ends, <em>exit</em> is called and there is no trivial way to determine whether that was the last thread in the process or not. Tracking process death is important because pids are eventually recycled and as discussed above you can&rsquo;t properly figure out when specific pids are created based on <em>fork</em>/<em>clone</em> syscalls
 <a class="footnote-return" href="#fnref:3"><sup>[return]</sup></a></li>
<li id="fn:5">There is a precedent for this -  when <em>execve</em> is audited a special EXECVE record is issued in addition to the SYSCALL record.
 <a class="footnote-return" href="#fnref:5"><sup>[return]</sup></a></li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>Understanding Netlink Process Connector Output</title>
            <link>https://natanyellin.com/posts/understanding-netlink-process-connector-output/</link>
            <pubDate>Sat, 10 Oct 2020 17:55:39 +0300</pubDate>
            
            <guid>https://natanyellin.com/posts/understanding-netlink-process-connector-output/</guid>
            <description>This post is the latest in a series of posts about tracking processes on Linux which looks at the fine details of various APIs.
Here is some sample output from the Netlink Process Connector API on Linux. It contains an odd looking fork1:
EventType=FORK ParentPid=100 ParentTgid=99 ChildPid=200 ChildTgid=199  Take a moment to think about what is going on here and who gave birth to who.
At first glance, this fork looks impossible.</description>
            <content type="html"><![CDATA[

<p>This post is the latest in a <a href="https://natanyellin.com/tags/tracking-processes/">series of posts about tracking processes on Linux</a> which looks at the fine details of various APIs.</p>

<p>Here is some sample output from the Netlink Process Connector API on Linux. It contains an odd looking <em>fork</em><sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup>:</p>

<pre><code>EventType=FORK

ParentPid=100
ParentTgid=99

ChildPid=200
ChildTgid=199
</code></pre>

<p>Take a moment to think about what is going on here and who gave birth to who.</p>

<p>At first glance, this fork looks impossible. The child doesn&rsquo;t share a thread-group (tgid) with the parent, so it looks like a new process was created and not a new thread. However, if a new process was created then we should have a process with only one thread and therefore ChildPid should equal ChildTgid.</p>

<p>After a brief look at kernel sources, it turns out that the ParentPid and ParentTgid refer to the new process&rsquo; <strong>current parent</strong> which isn&rsquo;t necessarily the process which called <em>fork</em> and birthed a child. In this case, we&rsquo;re looking at a pretty standard report on a new thread&rsquo;s creation by <code>clone(CLONE_THREAD)</code>. Threads have the same parent process as the thread which created them. Something like this happened:</p>

<ol>
<li>Process A (pid 99, tgid 99) created a new thread B</li>
<li>Thread B (pid 100, tgid 99) created a new process C</li>
<li>Process C (pid 199, tgid 199) created a new thread D</li>
<li>D has pid 200, tgid 199. This is expected because it is a thread in C&rsquo;s process group.  Likewise, it&rsquo;s parent is thread B in process A. Therefore ParentPid=100 and ParentTgid=99.</li>
</ol>

<p>Tracking processes accurately is one of those things that seem easy at first but turn out to be full of odd edge cases. If you liked this post, you should also read my post on <a href="https://natanyellin.com/posts/buggy-netlink-process-connectors/">a strange bug where the netlink process connector &ldquo;forgot&rdquo; to send events</a> or my comparison of <a href="https://natanyellin.com/posts/tracking-running-processes-on-linux/">all the methods of tracking running processes on linux.</a></p>

<h2 id="work-with-me">Work with me</h2>

<p>Does this sort of thing interest you?</p>

<p>I started as a low-level engineer, but today I&rsquo;m the co-founder and CEO of <a href="https://home.robusta.dev?from=natanyellin">Robusta.dev</a> and we&rsquo;re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.</p>

<p>If you join our team, you&rsquo;ll work closely with me and be a core part of the founding team. Email natan@robusta.dev and mention you came from this post. We&rsquo;re hiring in Israel as well as remote.</p>
<div class="footnotes">

<hr />

<ol>
<li id="fn:1">I use the term <em>fork</em> in this entire post to refer to all fork-like sycalls including clone
 <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>When Netlink Process Connectors Don&#39;t Process</title>
            <link>https://natanyellin.com/posts/buggy-netlink-process-connectors/</link>
            <pubDate>Tue, 18 Aug 2020 20:00:59 +0300</pubDate>
            
            <guid>https://natanyellin.com/posts/buggy-netlink-process-connectors/</guid>
            <description>If you want to track which processes are running on a Linux machine, the Netlink Process Connectors API is a convenient solution, despite certain limitations. The API provides an easy way to receive notifications whenever processes are created (forked/cloned) and whenever they undergo lifecycle events (exec, exit, etc). One possible use of the API is implementing a tool like htop1
There is only one problem with the API: Sometimes it doesn&amp;rsquo;t work.</description>
            <content type="html"><![CDATA[

<p>If you want to track which processes are running on a Linux machine, the Netlink Process Connectors API is a convenient solution, despite <a href="https://natanyellin.com/posts/tracking-running-processes-on-linux/">certain limitations.</a> The API provides an easy way to receive notifications whenever processes are created (<em>forked</em>/<em>cloned</em>) and whenever they undergo lifecycle events (<em>exec</em>, <em>exit</em>, etc). One possible use of the API is implementing a tool like <code>htop</code><sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup></p>

<p>There is only one problem with the API: Sometimes it doesn&rsquo;t work. You use the API according to the documentation, yet your program receives no process notifications. You scratch your head and run your program a few more times and it still doesn&rsquo;t work. Then something even stranger happens: the final time you run your program, the API &ldquo;wakes up&rdquo; and starts working. You shrug, deploy your code to production, and get the occasional customer complaint that relevant features are broken. What is going on?</p>

<h3 id="background-on-the-process-connector-api">Background on the process connector API</h3>

<p>Before looking at this issue in detail, some brief background about the netlink process connectors API is appropriate. Like all netlink APIs, you access the API by working with sockets.<sup class="footnote-ref" id="fnref:2"><a href="#fn:2">2</a></sup> First you open a netlink socket with <code>socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR)</code>. Then you <code>bind</code> the socket with a <code>sockaddr_nl</code> struct that specifies which netlink API you want - in our case, the process connector API.<sup class="footnote-ref" id="fnref:3"><a href="#fn:3">3</a></sup>  Lastly, you send a packet with a <code>PROC_CN_MCAST_LISTEN</code> message to notify the process connector that you are ready to receive notifications.</p>

<p>Now it is the kernel&rsquo;s turn. The process connector in the kernel fills your socket with notifications about processes. You read those notifications using <code>recv</code> as if they were regular network packets. Because the API uses sockets, you can filter the notifications using BPFs - <a href="https://natanyellin.com/posts/ebpf-filtering-done-right/">just be aware of the usual trouble when applying BPFs to newly created sockets</a>.</p>

<p>When your application is done working with the process connector API then you&rsquo;re supposed to send a <code>PROC_CN_MCAST_IGNORE</code> message to be a good citizen and let the kernel know that you&rsquo;re done using the API. You <em>might</em> think that general operating system principles apply here and the OS will clean up for your process when it exits no matter what. As we&rsquo;ll see later on, that isn&rsquo;t true here.</p>

<p>Alright, what can possibly go wrong with what we have described?</p>

<h3 id="searching-for-the-bug-in-kernel-sources">Searching for the bug in kernel sources</h3>

<p>When our bug occurred we didn&rsquo;t receive any messages from the kernel about process events. It was as if the kernel never received our <code>PROC_CN_MCAST_LISTEN</code> message, so lets start by looking at the kernel code which handles such messages. As always, we&rsquo;ll start with the <a href="https://elixir.bootlin.com/">elixir source viewer</a> and search for <code>PROC_CN_MCAST_LISTEN</code>. Asides from headers, <a href="https://elixir.bootlin.com/linux/latest/source/drivers/connector/cn_proc.c#L372">the constant appears only once in the kernel</a>:</p>

<pre><code class="language-c">static void cn_proc_mcast_ctl(struct cn_msg *msg, struct netlink_skb_parms *nsp)
{
    // ... snip! code removed for conciseness
	mc_op = (enum proc_cn_mcast_op *)msg-&gt;data;
	switch (*mc_op) {
	case PROC_CN_MCAST_LISTEN:
		atomic_inc(&amp;proc_event_num_listeners);
		break;
	case PROC_CN_MCAST_IGNORE:
		atomic_dec(&amp;proc_event_num_listeners);
		break;
	default:
		err = EINVAL;
		break;
	}

out:
	cn_proc_ack(err, msg-&gt;seq, msg-&gt;ack);
}
</code></pre>

<p>Basically, when the kernel receives a <code>PROC_CN_MCAST_LISTEN</code>  message it atomically increments <code>proc_event_num_listeners</code> and sends an acknowledgment message to usermode.</p>

<p>Let&rsquo;s look at the definition of <code>proc_event_num_listeners</code>:</p>

<pre><code class="language-c">static atomic_t proc_event_num_listeners = ATOMIC_INIT(0);
</code></pre>

<p>Yikes. <code>proc_event_num_listeners</code> is a global variable. It is updated on <code>PROC_CN_MCAST_LISTEN</code> and <code>PROC_CN_MCAST_IGNORE</code> regardless of whether the process sending the message was already listening or not. Furthermore, <code>atomic_t</code> is equivalent to <code>int</code> so if a process sends multiple <code>PROC_CN_MCAST_IGNORE</code> messages then <code>proc_event_num_listeners</code> can actually hold negative values. This will impact not just the buggy application but all applications using the process connector API. This looks like the cause of our bug. Lets fill in one last piece of the puzzle by looking at how <code>proc_event_num_listeners</code> is used:</p>

<pre><code class="language-c">void proc_exec_connector(struct task_struct *task)
{
	struct cn_msg *msg;
	struct proc_event *ev;
	__u8 buffer[CN_PROC_MSG_SIZE] __aligned(8);

	if (atomic_read(&amp;proc_event_num_listeners) &lt; 1)
		return;
    // ... snip!
    // send notification of exec to usermoode
}
</code></pre>

<p>The is the handler that runs whenever an <em>exec</em> event happens. The kernel uses <code>proc_event_num_listeners</code> to quickly determine whether there are active users of the process connector API or not. When <code>proc_event_num_listeners &lt; 1</code>  the kernel bypasses the entire process connector.</p>

<h3 id="the-bug">The bug</h3>

<p>Lets put the pieces together. Something like this happened:</p>

<ol>
<li>Some usermode program sent too many<code>PROC_CN_MCAST_IGNORE</code> message and <code>proc_event_num_listeners</code> ended up at a negative value - lets say negative three.</li>
<li>We ran our application three times and the process connector didn&rsquo;t work. Unbeknownst to us, each time we ran our application there was a hidden side effect. The application sent a <code>PROC_CN_MCAST_LISTEN</code> message which incremented<code>proc_event_num_listeners</code> by one each time.</li>
<li>We ran our application a fourth time. This time <code>proc_event_num_listeners</code> started out at zero, so our <code>PROC_CN_MCAST_LISTEN</code> message worked like it should and the process connector started sending data.</li>
</ol>

<p>What caused the initial problem where <code>PROC_CN_MCAST_IGNORE</code> was sent too many times? It turns out there was a running application which enabled/disabled a process monitoring module based on a configuration file. Whenever the configuration file was reloaded with the process monitoring module disabled, a <code>PROC_CN_MCAST_IGNORE</code> message was sent even though the application never sent a <code>PROC_CN_MCAST_LISTEN</code> message to begin with.</p>

<p>One last question: should this be considered a kernel bug? I think so, <a href="https://bugzilla.kernel.org/show_bug.cgi?id=207371">so I&rsquo;ve opened a ticket</a>.</p>

<p>If you found this post interesting, read my post about <a href="https://natanyellin.com/posts/tracking-running-processes-on-linux/">the difficulties of tracking running processes on Linux which compares the process connector API with alternative solutions.</a></p>

<h2 id="work-with-me">Work with me</h2>

<p>Does this sort of thing interest you?</p>

<p>I started as a low-level engineer, but today I&rsquo;m the co-founder and CEO of <a href="https://home.robusta.dev?from=natanyellin">Robusta.dev</a> and we&rsquo;re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.</p>

<p>If you join our team, you&rsquo;ll work closely with me and be a core part of the founding team. Email natan@robusta.dev and mention you came from this post. We&rsquo;re hiring in Israel as well as remote.</p>
<div class="footnotes">

<hr />

<ol>
<li id="fn:1">In practice, <a href="https://github.com/hishamhm/htop/blob/master/linux/LinuxProcessList.c">(h)</a><a href="https://elixir.bootlin.com/busybox/1.27.2/source/procps/top.c">top</a> <a href="https://github.com/hishamhm/htop/blob/master/linux/LinuxProcessList.c">implementations</a> typically don&rsquo;t use the process connector API - they just do a full scan of <code>/proc</code> every few seconds. This is easier to implement because they have to scan <code>/proc</code> anyway (even with the process connector API) to read the initial list of processes on startup. Furthermore, the overhead of polling <code>/proc</code> doesn&rsquo;t matter for a tool like <code>top</code> which is infrequently run by the user on-demand.
 <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li>
<li id="fn:2">See <a href="https://www.linuxjournal.com/article/7356">this Linux Journal article</a> for the advantages of implementing kernel APIs with netlink sockets as opposed to system calls, ioctls, and virtual filesystems.
 <a class="footnote-return" href="#fnref:2"><sup>[return]</sup></a></li>
<li id="fn:3">Other popular options include the Linux audit API which also uses netlink for kernel-usermode communication.
 <a class="footnote-return" href="#fnref:3"><sup>[return]</sup></a></li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>The Difficulties of Tracking Running Processes on Linux</title>
            <link>https://natanyellin.com/posts/tracking-running-processes-on-linux/</link>
            <pubDate>Thu, 02 Jul 2020 21:24:38 +0300</pubDate>
            
            <guid>https://natanyellin.com/posts/tracking-running-processes-on-linux/</guid>
            <description>Introduction Everyone knows how to track which processes run on Linux, but almost no-one tracks them accurately. In fact, all of the methods listed in this post have some deficiency or another. Lets define requirements:
 All processes should be logged including short-lived processes We should know the full executable path of every process that runs Within reason, we shouldn&amp;rsquo;t need to modify or recompile our code for different kernel versions Bonus: If the host is a Kubernetes node or runs docker than we should be able to determine which pod/container a process belongs to.</description>
            <content type="html"><![CDATA[

<h3 id="introduction">Introduction</h3>

<p>Everyone knows how to track which processes run on Linux, but almost no-one tracks them accurately. In fact, <strong>all</strong> of the methods listed in this post have some deficiency or another. Lets define requirements:</p>

<ol>
<li>All processes should be logged including short-lived processes</li>
<li>We should know the full executable path of every process that runs</li>
<li>Within reason, we shouldn&rsquo;t need to modify or recompile our code for different kernel versions</li>
<li>Bonus: If the host is a Kubernetes node or runs docker than we should be able to determine which pod/container a process belongs to. To do so, it is often sufficient to know a process&rsquo; cgroup ID. <sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup></li>
</ol>

<p>Lets looks at common Linux APIs that can solve this problem. For simplicity&rsquo;s sake, we&rsquo;ll focus on detecting <code>execve</code> syscalls. A full solution will also need to monitor <code>fork</code>/<code>clone</code> syscalls and their variants as well <code>execveat</code>.</p>

<h3 id="simple-usermode-solutions">Simple Usermode Solutions</h3>

<ol>
<li><strong>Poll <code>/proc</code></strong>. This is no good because it will miss short-lived processes.</li>
<li><strong>Use the netlink process connector</strong>. The connector will deliver notifications for short-lived processes but the notifications only include numerical data like the process&rsquo; <code>pid</code> without data like the executable path. Therefore, you&rsquo;re back to reading data from <code>/proc</code> and have the same race condition for short-lived processes. If you use the netlink process connector you should be aware of <a href="https://natanyellin.com/posts/buggy-netlink-process-connectors/">a bug that causes events to disappear</a> and <a href="https://natanyellin.com/posts/understanding-netlink-process-connector-output/">how to interpret strange looking ppids</a> in the output.</li>
<li><strong>Use the Linux audit API.</strong> This is the best solution out there. The audit API exists in all modern kernels, provides full executable paths, and won&rsquo;t miss short-lived processes. There are only two major disadvantages. First of all, only one usermode program can add rules to the kernel audit API at a time. This is a pain if you are developing an enterprise security solution and have customers who use the audit API themselves via <code>auditd</code> or <code>osquery</code>.<sup class="footnote-ref" id="fnref:audit"><a href="#fn:audit">2</a></sup> Second of all, the audit API isn&rsquo;t container aware, despite years of kernel mailing list discussions on fixing the issue. <a href="https://natanyellin.com/posts/using-linux-audit-to-track-processes/">I&rsquo;ve written more about the difficulties I encountered using the Linux audit API here.</a></li>
</ol>

<h3 id="simple-kernel-debugging-solutions">Simple Kernel Debugging Solutions</h3>

<p>These solutions all involve a single kernel probe of various types.</p>

<ol>
<li><strong>Use tracepoints<sup class="footnote-ref" id="fnref:2"><a href="#fn:2">3</a></sup> .</strong> The kernel contains several relevant tracepoints which execute at different points in the <code>execve</code> syscall. They are: <em>sched_process_exec</em>, <em>open_exec</em>, <em>sys_enter_execve</em>, <em>sys_exit_execve</em>.<sup class="footnote-ref" id="fnref:3"><a href="#fn:3">4</a></sup> These tracepoints are better than the previous solutions because they will track short-lived processes, but none of these tracepoints provide an executable&rsquo;s full path when the parameter to <code>exec</code> is a relative path. In other words, if the user runs <code>cd /bin &amp;&amp; ./ls</code> then the path will be reported as <code>./ls</code> and not <code>/bin/ls</code>. Here is a simple demonstration:</li>
</ol>

<pre><code class="language-bash"># enable the sched_process_exec tracepoint
sudo -s
cd /sys/kernel/debug/tracing
echo 1 &gt; events/sched/sched_process_exec/enable

# run ls via a relative path
cd /bin &amp;&amp; ./ls

# fetch data from the sched_process_exec tracepoint
# note that we don't see the full path
cd -
cat trace | grep ls

# disable the tracepoint
echo 0 &gt; events/sched/sched_process_exec/enable
</code></pre>

<ol>
<li><p><strong>Use kprobes/kretprobes<sup class="footnote-ref" id="fnref:4"><a href="#fn:4">5</a></sup>.</strong> Unlike tracepoints, there are many, many possible functions where you can insert a kprobe which will be hit during an <code>execve</code> syscall. However, I can&rsquo;t find a single function in <code>execve</code>&rsquo;s callgraph which has as function parameters both the process&rsquo; PID and the full path of the executable. Therefore we have the same issue with relative paths as the tracepoint solution. There are some clever hacks you can do here - after all, kprobes can read data from the kernel&rsquo;s callstack - but these solutions wont be stable across kernel versions so I&rsquo;m ruling them out.</p></li>

<li><p><strong>Use eBPF programs with tracepoints/kprobes/kretprobes</strong><sup class="footnote-ref" id="fnref:5"><a href="#fn:5">6</a></sup>. This opens up some new options. Now we can run arbitrary code in the kernel every time that the <code>execve</code> syscall runs. In theory, this should let us extract any information we want from kernel and send it to usermode. There are two ways of obtaining such data and neither meets our requirements:</p>

<ol>
<li>Read data from kernel structs like <code>task_struct</code> or <code>linux_binprm</code>. We can indeed fetch the executable&rsquo;s full path this way<sup class="footnote-ref" id="fnref:6"><a href="#fn:6">7</a></sup> but reading from kernel structs will make us dependent on kernel versions. Our eBPF program needs to know the offsets of struct members so it has to be compiled with kernel headers for each kernel version. This is typically solved by compiling the eBPF program at runtime, but that brings it&rsquo;s own issues like a requirement that you have kernel headers available on every machine.</li>
<li>Use eBPF helper functions to fetch data from the kernel. This is compatible across all kernel versions that contain the helper you use. In this method you never access kernel structs directly - rather you use helper APIs to fetch data. There is only one problem: there is no eBPF helper function which can obtain the executable&rsquo;s full path. (However, in recent kernel versions, there <em>is</em> an eBPF helper function to get the cgroup ID which is useful for mapping processes to containers.)</li>
</ol></li>
</ol>

<h3 id="hackish-solutions">Hackish Solutions:</h3>

<ol>
<li><strong>Use <code>LD_PRELOAD</code> on every running executable and hook <code>exec</code> calls in libc</strong>. Seriously, don&rsquo;t do this. It won&rsquo;t work for statically compiled executables, is easy for malicious code to bypass, and is fairly intrusive.</li>
<li><strong>Use tracepoints on <code>execve</code>, <code>fork</code>/<code>clone</code>, and <code>chdir</code> to track not only the creation of all processes but also their current working directory.</strong> For each <code>execve</code> lookup the process&rsquo; working directory and combine that with <code>execve</code>&rsquo;s parameter to obtain a full path. If you do this, make sure you use eBPF maps and put all the logic into your eBPF programs to avoid race conditions where events arrive in usermode in the wrong order.</li>
<li><strong>Use ptrace based solutions</strong>. These are too intrusive for production code. However, if you do go this route then  use ptrace + seccomp and the <code>SECCOMP_RET_TRACE</code> flag. Then seccomp can intercept all <code>execve</code> syscalls in the kernel and pass them to a usermode debugger which can log the <code>execve</code> call before telling seccomp to continue with the <code>execve</code> as usual.</li>
<li><strong>Use AppArmor.</strong> You can write an AppArmor profile which forbids a process from executing any other executables. If you put that profile in complain mode then AppArmor wont actually prevent process execution - it will only issue alerts when the profile is violated. If we attach our profile to every running process then we will have a working but very ugly and hackish solution. You probably shouldn&rsquo;t do this.</li>
</ol>

<h3 id="3rd-party-tools">3rd Party Tools</h3>

<p>None of these solutions satisfy our requirements, but here they are:</p>

<ol>
<li><strong>Use <code>ps</code></strong> - this just polls from <code>/proc</code> and therefore has the usual race conditions</li>
<li><strong>Use the eBPF-based <a href="https://github.com/iovisor/bcc/blob/master/tools/execsnoop.py">execsnoop</a></strong> - this is just a kprobe/kretprobe based solution so it has the same dependency on kernel versions discussed above. Besides, execsnoop doesn&rsquo;t even expand relative paths so we have gained nothing.</li>
<li><strong>Use the old non-eBPF version of execsnoop</strong> - this won&rsquo;t work either. It is just a simple kprobe.</li>
</ol>

<h3 id="future-solutions">Future Solutions:</h3>

<ol>
<li><strong>Use the eBPF helper function <a href="https://patchwork.ozlabs.org/project/netdev/patch/7464919bd9c15f2496ca29dceb6a4048b3199774.1576629200.git.ethercflow@gmail.com/">get_fd_path</a></strong> - this doesn&rsquo;t yet exist, but once it is added to the kernel it will somewhat help. You&rsquo;ll still have to get the executable&rsquo;s FD in a way that doesn&rsquo;t involve reading from kernel structs.</li>
</ol>

<h3 id="closing-notes">Closing Notes</h3>

<p>None of the APIs covered here are perfect. Here are my recommendations for which solution you should use and when:</p>

<ol>
<li>If you can, use the audit API via <code>auditd</code> or <a href="https://github.com/slackhq/go-audit">go-audit</a>. This will log all processes, including short-lived processes, and you&rsquo;ll get full executable paths without any effort. This solution wont work if someone is already using the audit API via a different usermode tool than you. In that case, read on.</li>
<li>If you don&rsquo;t care about full-paths and you want a quick, ready-made solution that doesn&rsquo;t involve writing any code then use <code>execsnoop</code>. This has the disadvantage of requiring kernel headers at runtime.</li>
<li>If you don&rsquo;t care about full-paths and you&rsquo;re willing to go the extra mile to avoid requiring kernel headers then use one of the tracepoints mentioned above. There are multiple ways that you can connect to those tracepoints and transfer their data to usermode - whether it is via the filesystem interface shown above, via an eBPF program with eBPF maps, or via <code>perf</code> tools. I&rsquo;ll cover these options in another post. The main thing to remember is this: if you use an eBPF program make sure it can be statically compiled so that you don&rsquo;t have the same dependency on kernel headers that you&rsquo;re trying to avoid. This means you can&rsquo;t access kernel structs and you can&rsquo;t use frameworks like <a href="https://github.com/iovisor/bcc">BCC</a> which compile eBPF programs at runtime.</li>
<li>If you don&rsquo;t care about short-lived processes and the previous solutions don&rsquo;t fit your use-case then use the netlink process connector in conjunction with <code>/proc</code></li>
</ol>

<p>Have I forgotten a solution? Message me on <a href="https://twitter.com/aantn">twitter</a>!</p>

<h2 id="work-with-me">Work with me</h2>

<p>Does this sort of thing interest you?</p>

<p>I started as a low-level engineer, but today I&rsquo;m the co-founder and CEO of <a href="https://home.robusta.dev?from=natanyellin">Robusta.dev</a> and we&rsquo;re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.</p>

<p>If you join our team, you&rsquo;ll work closely with me and be a core part of the founding team. Email natan@robusta.dev and mention you came from this post. We&rsquo;re hiring in Israel as well as remote.</p>
<div class="footnotes">

<hr />

<ol>
<li id="fn:1">There is no such thing as a container or a container ID from the kernel&rsquo;s perspective. The kernel only knows about cgroups, network namespaces, process namespaces, and other independent kernel APIs which container runtimes like docker happen to implement containerization with. When trying to identify containers via kernel IDs you need a kernel identifier which every container has exactly one of. For docker, cgroup IDs satisfy that requirement.
 <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li>
<li id="fn:audit">In theory user-mode multiplexers like <code>auditd</code> and <a href="https://github.com/slackhq/go-audit">go-audit</a> can mitigate this issue, but for enterprise solutions you still don&rsquo;t know if the customer is using a multiplexer, if so which one, and if there are other security solutions present which connect to the audit API directly.
 <a class="footnote-return" href="#fnref:audit"><sup>[return]</sup></a></li>
<li id="fn:2">tracepoints are probes that are statically compiled into the kernel at set locations. Each probe can be individually enabled so that it emits notifications when the kernel reaches that probe&rsquo;s location.
 <a class="footnote-return" href="#fnref:2"><sup>[return]</sup></a></li>
<li id="fn:3">To obtain this list I ran  <code>cat /sys/kernel/tracing/available_events | grep exec</code> and then filtered the output based on a glance at kernel sources
 <a class="footnote-return" href="#fnref:3"><sup>[return]</sup></a></li>
<li id="fn:4">kprobes let you extract debug information from almost any kernel location. You can think of them as kernel breakpoints which emit information but don&rsquo;t stop execution.
 <a class="footnote-return" href="#fnref:4"><sup>[return]</sup></a></li>
<li id="fn:5">In other words, use tracepoints/kprobes/kretprobes as the hooking mechanism but set an eBPF program to run on the hook instead of the old-school handlers.
 <a class="footnote-return" href="#fnref:5"><sup>[return]</sup></a></li>
<li id="fn:6">e.g. put a tracepoint on <code>sched_process_exec</code> and use a bounded eBPF loop to <a href="https://github.com/iovisor/bcc/issues/237#issuecomment-547564661">walk the dentry chain</a> in<code>bprm-&gt;file-&gt;f_path.dentry</code>, sending it to usermode one piece at a time via a perf ring buffer
 <a class="footnote-return" href="#fnref:6"><sup>[return]</sup></a></li>
</ol>
</div>
]]></content>
        </item>
        
        <item>
            <title>The wrong way to filter sockets with BPF</title>
            <link>https://natanyellin.com/posts/ebpf-filtering-done-right/</link>
            <pubDate>Sun, 21 Jun 2020 12:26:53 +0300</pubDate>
            
            <guid>https://natanyellin.com/posts/ebpf-filtering-done-right/</guid>
            <description>Here is a bug that everyone who uses BPF to filter packets on Linux eventually encounters: you create a socket, use setsockopt to apply a BPF, and then read from the socket using recv. You read a packet that does not match the filter but was received from the socket anyway. The bug only happens when there is a lot of traffic and even then it only occurs when the application first starts.</description>
            <content type="html"><![CDATA[

<p>Here is a bug that everyone who uses BPF to filter packets on Linux eventually encounters:  you create a socket, use setsockopt to apply a BPF, and then read from the socket using recv. You read a packet that does <strong>not</strong> match the filter but was received from the socket anyway. The bug only happens when there is a lot of traffic and even then it only occurs when the application first starts. What is happening?</p>

<p>Here is some code which suffers from this issue. Error-handling has been removed for conciseness:</p>

<pre><code class="language-c">int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));

struct sock_filter bpf_bytecode[] = { ... }; // bytecode generated by hand or using &quot;tcpdump -dd&quot;
struct sock_fprog bpf_program = { sizeof(bpf_bytecode) / sizeof(bpf_bytecode[0]), bpf_bytecode};
int err = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &amp;bpf_program, sizeof(bpf_program));

char buffer[MAX_PACKET_SIZE];
int n = recv(sock, buffer, MAX_PACKET_SIZE, NO_OPTIONS); // buffer will sometimes contain a packet that doesn't match the bpf
</code></pre>

<p>The bug here is deceptively simple: Packets are filtered when they are received by the kernel <strong>not</strong> when they are read by user-mode using recv. Therefore packets which don&rsquo;t match the BPF can be received after the socket is created and before setsockopt is called. Those packets will remain in the socket&rsquo;s buffer even after the BPF is applied and will later be transferred to the application via recv.</p>

<p>Here are two <strong>wrong</strong> ways to fix this:</p>

<ol>
<li>After applying the bpf use recv in a loop to discard packets from the socket until it is empty. This typically works but it breaks down if the traffic rate is greater than the rate at which you can discard packets. (e.g. if you&rsquo;re sniffing on a very high traffic machine and your app typically loses packets when the socket&rsquo;s buffer fills but it doesn&rsquo;t matter for your use-case.) In this case your attempt to empty the socket will turn into an infinite loop.</li>
<li>Add user-mode checks that duplicate the BPF&rsquo;s logic and check all packets after receiving them. Duplicate logic leads to bugs. Enough said.<sup class="footnote-ref" id="fnref:1"><a href="#fn:1">1</a></sup></li>
</ol>

<p>Now lets look at the textbook solution for fixing this bug - the same solution that <a href="https://github.com/the-tcpdump-group/libpcap/blob/f4fcc9396dc425399846cf082f9ed1056b81dd11/pcap-linux.c#L6096">libpcap uses</a>:</p>

<pre><code class="language-c">struct sock_filter zero_bytecode = BPF_STMT(BPF_RET | BPF_K, 0);
struct sock_fprog zero_program = { 1, &amp;zero_bytecode};

if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &amp;zero_program, sizeof(zero_program)) &lt; 0) {
	printf(&quot;error attaching zero bpf: %d\n&quot;, errno);
	return 1;
}

char drain[1];
while (1) {
	int bytes = recv(sock, drain, sizeof(drain), MSG_DONTWAIT);
	if (bytes == -1) {
		// we assume the error here means there is nothing left to read from the socket which is exactly what we want
		break;
	}
}

// bpf_program is the actual bpf program we want to apply - just like in the previous example
int err = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &amp;bpf_program, sizeof(bpf_program));
char buffer[MAX_PACKET_SIZE];
int n = recv(sock, buffer, MAX_PACKET_SIZE, NO_OPTIONS); // buffer will now always match the bpf
</code></pre>

<p>What we&rsquo;re doing here is taking advantage of the fact that swapping one BPF out for another is an atomic operation: if you swap one BPF for another then at every moment either the first is in place or the second but never neither. We therefore start by applying the so-called &ldquo;zero-BPF&rdquo; which is a BPF that matches no packets. Then we empty out any packets that arrived before the &ldquo;zero-BPF&rdquo; filter was applied. At this point the socket is definitely empty and it can&rsquo;t fill up with junk because the zero-BPF is in place. Then we replace the zero-BPF with the real BPF we want. Because the swap is atomic we know that any packet in the socket after that must match the real BPF.</p>

<p>Simple.</p>

<h2 id="work-with-me">Work with me</h2>

<p>Does this sort of thing interest you?</p>

<p>I started as a low-level engineer, but today I&rsquo;m the co-founder and CEO of <a href="https://home.robusta.dev?from=natanyellin">Robusta.dev</a> and we&rsquo;re hiring! I still do the occasional deep technical dive, as well as building a world-class team of excellent engineers and a product used by hundreds of companies.</p>

<p>If you join our team, you&rsquo;ll work closely with me and be a core part of the founding team. Email natan@robusta.dev and mention you came from this post. We&rsquo;re hiring in Israel as well as remote.</p>
<div class="footnotes">

<hr />

<ol>
<li id="fn:1">This seems like a theoretical reason but I&rsquo;ve seen it happen again and again in production code. Lets say that you have a bug such that your usermode checks are too loose and allow mismatching packets, but the BPF is appropriately restrictive. You could test your app with lots of mismatching packets and you would never even notice that the usermode checks are too loose because the BPF would filter out all the mismatching packets anyway. That is, you would never notice until you deploy to a high-traffic machine in production..
 <a class="footnote-return" href="#fnref:1"><sup>[return]</sup></a></li>
</ol>
</div>
]]></content>
        </item>
        
    </channel>
</rss>
