<?xml version="1.0" encoding="UTF-8"?>
<rss 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" version="2.0">
  <channel>
    <title><![CDATA[Christiano Haesbaert - Elastic Security Labs]]></title>
    <description><![CDATA[Trusted security news & research from the team at Elastic.]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Christiano Haesbaert - Elastic Security Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte2c6b841aff36df4/6a88d9784acc96e3f324863d/security-labs-thumbnail.png</url>
      <link>https://www.elastic.co/security-labs/author/christiano-haesbaert</link>
    </image>
    <link>https://www.elastic.co/security-labs/author/christiano-haesbaert</link>
    <atom:link href="https://www.elastic.co/security-labs/rss/author/christiano-haesbaert.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Fri, 25 Sep 2026 00:11:16 GMT</lastBuildDate>
  <item>
    <title><![CDATA[Signaling from within: how eBPF interacts with signals]]></title>
    <description><![CDATA[This article explores some of the semantics of UNIX signals when generated from an eBPF program.]]></description>
    <content:encoded><![CDATA[<h2 id="background">Background</h2>
<p>Signals have been around since the UNIX First Edition in 1971 and while its semantics and system calls suffered changes throughout the years, its uses and application have remained largely the same. Usually, when we talk about signal semantics, we're talking about what userland can observe and interact with. After all, we mostly generate and handle signals to/from userland processes.</p>
<p>In this publication, we will explore some of the semantics from signals generated <em>inside</em> the kernel within an eBPF program. More so, we’ll identify what kind of effects and guarantees we observed after the handling of such signals. You can find more information about eBPF <a href="https://www.elastic.co/blog/ebpf-observability-security-workload-profiling">in this article</a>.</p>
<h2 id="motivation">Motivation</h2>
<p>In <a href="https://docs.elastic.co/integrations/cloud_defend">Elastic Defend for Containers</a> we utilize eBPF in Linux Security Module (<a href="https://www.kernel.org/doc/html/v4.16/admin-guide/LSM/index.html">LSM</a>) hooks that restrict access to system resources. Using LSM is the preferred way to conduct this kind of restriction as the eBPF program can return an error like <a href="https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_03">EPERM (an operation was attempted, but without proper privileges)</a>, which is propagated up to the system call return value.</p>
<p>The problem with using eBPF+LSM in this way is that support is relatively new and only applies to AMD64 for the most part. Therefore, we wanted to explore using the eBPF helper <a href="https://man7.org/linux/man-pages/man7/bpf-helpers.7.html"><code>bpf_send_signal</code>()</a> where necessary, like older kernels or different architectures. Instead of failing the system call with EPERM, <code>bpf_send_signal()</code> would be used to send a <code>SIGKILL</code> to the current process and terminate it, arguably more dramatic but still reasonable given the limitations.</p>
<p>Generally, we aim to answer these questions:</p>
<ul>
<li>What side effects are observed (if any) after the program receives a <code>SIGKILL</code></li>
<li>Which of the side effects (if any) result from the signal subsystem design versus the implementation</li>
<li>If the kernel code shifts in the future, how will that impact these side effects</li>
</ul>
<h2 id="scenarioblockingopenat2">Scenario: blocking openat(2)</h2>
<p>Imagine we would like to prevent certain processes from opening files and, for the sake of simplicity, we would like to prevent these processes from using an <a href="https://linux.die.net/man/2/openat"><code>openat(2)</code></a> system call.</p>
<p>If LSM were available, we would hook our eBPF program in the LSM hook <a href="https://elixir.bootlin.com/linux/v6.5.10/source/fs/open.c#L901"><code>security_file_open()</code></a>, return EPERM, and then <code>openat(2)</code> would fail gracefully. Because LSM is not available, we’ll instead generate a <code>SIGKILL</code>, but first, we need to figure out a place to hook our eBPF program in the kernel. </p>
<p>We have options: use a static tracepoint like syscalls:sys_enter_openat2 or we can use <a href="https://docs.kernel.org/trace/kprobes.html">kprobes</a> and run our eBPF program from a kernel function of our choice. Obvious candidates would be <a href="https://elixir.bootlin.com/linux/v6.5.10/source/fs/open.c#L1045"><code>vfs_open</code></a>, <a href="https://elixir.bootlin.com/linux/v6.5.10/source/fs/open.c#L1045"><code>do_sys_openat2</code></a> (happens a little earlier), or <a href="https://elixir.bootlin.com/linux/v6.5.10/source/fs/open.c#L1441"><code>__x64_sys_openat</code></a> (even earlier, but machine-dependent). We can test it with bpftrace:</p>
<pre><code>bpftrace --unsafe -e 
 'kprobe:vfs_open /str(((struct path *)arg0)-&gt;dentry-&gt;d_name.name) == "__noopen"/ 
 { signal("SIGKILL") }'

# In another tty we can put it to the test
$ strace /bin/cat /tmp/__noopen
...
openat(AT_FDCWD, "/tmp/__noopen", O_RDONLY) = ?
+++ killed by SIGKILL +++
Killed
</code></pre>
<p>We can see that <code>cat(1)</code> is terminated with <code>SIGKILL</code> the moment it attempts to open the file.  At first glance, this appears to work correctly, but it may be premature to declare victory.</p>
<p>It's important to note that the signal is not being generated by an external process but from the context of the <code>cat(1)</code> process doing the system call to itself. It is performing the equivalent of <code>kill(0, SIGKILL)</code> from within the kernel, where 0 means “self”.</p>
<p>The only thing we’ve proven is that the program is indeed terminated, but this opens more questions:</p>
<ul>
<li>Did we block <code>openat(2)</code> or not?</li>
<li>Does the outcome change If we successfully block <code>openat(2)</code>?</li>
<li>Are there more observable side effects?</li>
</ul>
<p>If we conduct the same experiment, on the same path but with a nonexistent file, and pass the <code>O_CREAT</code> flag to <code>openat(2)</code>, is the file created? Is the application still terminated? Let’s see what happens:</p>
<pre><code>$ rm /tmp/__noopen 
$ strace /bin/touch /tmp/__noopen
...
openat(AT_FDCWD, "/tmp/__noopen", O_WRONLY|O_CREAT|O_NOCTTY|O_NONBLOCK, 0666) = ?
+++ killed by SIGKILL +++
Killed
</code></pre>
<p>In this case, we are still terminated by <code>SIGKILL</code>. But, if we examine the filesystem, there is now an empty file created by the offending program! We can conclude that <code>openat(2)</code>  did somehow execute because the file creation was observed.</p>
<h2 id="kernelhandlingofasigkill">Kernel handling of a SIGKILL</h2>
<p>Signals can’t be handled online; instead, they must be post-processed at safe points. By online we mean: If I'm doing a system call, and a SIGKILL arrives, I cannot just cease to exist. Signals must be checked at safe points, and in most UNIXes this is done before returning to userland.</p>
<p>The check for signal pending is done after running the system call at <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/entry/common.c#L147"><code>exit_to_user_mode_loop()</code></a>. If <code>TIG_SIGPENDING</code> is set in the current task structure, the process branches into the signal handling code. When <code>SIGKILL</code> (a fatal signal) is pending, the process branches into <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/exit.c#L999"><code>do_group_exit()</code></a> which never returns, resulting in the end of the process.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltffe3a64eb9419a2c/6a7c93ddda3d05b4fb6340e7/image1.png" alt="" /></p>
<h2 id="whypostprocesssignals">Why post-process signals</h2>
<p>Signals must be post-processed and handled at safe points, otherwise the kernel would have to account for the process involuntarily exiting due to a fatal signal. We can conduct a thought experiment and imagine an implementation that attempts to process signals the moment they arrive. This could be implemented by interrupting the running process and forcing it to exit from the interrupt context, for example:</p>
<ul>
<li>Process A running on <code>cpu0</code> performs a system call</li>
<li>Process B running on <code>cpu1</code> sends a <code>SIGKILL</code> to process A</li>
<li>An IPI would be sent from <code>cpu1</code> to <code>cpu0</code></li>
<li><code>cpu0</code> would trap into an interrupt frame, realize it is here due to a signal being sent, and perform an exit of the current process</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt549dc8df8cab4fbe/6a7c93e01967ea323532abca/image2.png" alt="" /></p>
<p>Thankfully this is not the case, you cannot exit from an interrupt context – furthermore, this couldn’t be implemented without introducing significant changes to resource management. When a process exits, it must release any resources – like locks, reference counts, or any other kind of mutable data that may be influenced by the exiting process.</p>
<p>We can trace a parallel with <a href="https://wiki.linuxfoundation.org/realtime/documentation/technical_basics/preemption_models">kernel preemption</a>, as Linux is highly preemptive when configured with <code>CONFIG_PREEMPT_FULL</code>. This allows the scheduler to shelve the running process while it is in <a href="https://www.linfo.org/kernel_space.html">kernel space</a> and run other processes. From the point of view of the process being preempted, this is an involuntary context switch as it did not voluntarily release the CPU. This is orthogonal from a preemptive userland where the scheduler preempts a running process running in user mode. Historically, UNIX systems did not employ a preemptive kernel, the strategy to maintain low latency relied solely on fast(short) system calls and <a href="https://en.wikipedia.org/wiki/Spl_(Unix)">interrupt priorities</a>.</p>
<p>Programming with preemption is harder because the kernel programmer must always consider the impact of being preempted and judge when to disable preemption. Failure to disable preemption at the right time, for example on <a href="https://docs.kernel.org/locking/spinlocks.html">spinlocks</a>, could result in another process spinning on a lock of a preempted process indefinitely.</p>
<p>If we allowed a process to exit involuntarily from a trap frame, it would be a bit like preemption, but much harder – if not impossible. The kernel programmer would now have to always consider "what happens if my process involuntarily exits here?", and this would likely involve having to register callbacks to release resources on exit.</p>
<p>Hopefully, it's now clear why signals can’t be handled online. Signals in Linux, like other systems, are processed just before returning to userland.</p>
<h2 id="postingasigkillfromebpf">Posting a SIGKILL from eBPF</h2>
<p>Let us follow the lifecycle of a <code>SIGKILL</code> originating from an eBPF program until the process is terminated.</p>
<p>When an eBPF program calls the special helper <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/trace/bpf_trace.c#L873"><code>bpf_send_signal(SIGKILL)</code></a> we end up in <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/trace/bpf_trace.c#L831"><code>bpf_send_signal_common(SIGKILL, PIDTYPE_TGID)</code></a>. <code>PIDTYPE_TGID</code> is the "task group id" and it specifies that any task (meaning any pthread) of the current process may accept the signal. But eBPF also provides <code>bpf_send_signal_task()</code> which sends the signal only to the current task by specifying <code>PIDTYPE_PID</code> instead.</p>
<p><code>bpf_send_signal_common()</code> has to be used with caution because it must be able to generate a signal from any point in the kernel where you can attach an eBPF program; which is tricky work that has resulted in some past bugs like <a href="https://github.com/torvalds/linux/commit/1bc7896e9ef44fd77858b3ef0b8a6840be3a4494">this deadlock</a>. This is an interesting imposition created by eBPF; before it, signals generated from the kernel were done so in controlled points.</p>
<p>Most of the heavy lifting of posting a signal is done in <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/signal.c#L1083"><code>__send_signal_locked()</code></a> and <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/signal.c#L1003"><code>complete_signal()</code></a> and we get there through the following stack:</p>
<pre><code>complete_signal()          ^
__send_signal_locked()     |
send_signal_locked()       |
do_send_sig_info()         |
group_send_sig_info()      |
bpf_send_signal()          |


static int __send_signal_locked(int sig, 
    struct kernel_siginfo *info, struct task_struct *t, 
    enum pid_type type, bool force)
</code></pre>
<p>In our case, in <code>__send_signal_locked</code>: <code>sig</code> is <code>SIGKILL</code>, <code>info</code> is <code>SEND_SIG_PRIV</code>, <code>t</code> is the current task (the running thread), <code>type</code> is <code>PIDTYPE_TGID</code> and <code>force</code> is true, which is always set when <code>info</code> is <code>SEND_SIG_PRIV</code>, this means this is a signal originating from the kernel, not from some userland program.</p>
<p><code>__send_signal_locked(</code>) will register a <code>SIGKILL</code> as <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/signal.c#L1178">pending inside a structure of the current task</a> (our <code>t</code>), which is a process-wide structure shared by all tasks (pthreads) in this process (since we're using <code>PIDTYPE_TGID</code>),  and control is then passed to <code>complete_signal()</code>.</p>
<p><code>SIGKILL</code> is a bit special in <code>complete_signal()</code> as it is a fatal signal, the pending signal bit that was set in the shared structure of the process will then be <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/signal.c#L1065">replicated</a> to a per-task pending set. This means a <code>SIGKILL</code> is marked as pending for every pthread of the current process.</p>
<p><code>complete_signal()</code> then wakes up all threads via <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/signal.c#L768"><code>signal_wake_up+signal_wake_up_state()</code></a> so that they can be terminated. Each thread must terminate on its own and send a signal politely asking the thread to “please exit next time instead of returning to userland”.</p>
<p>In the <code>signal_wake_up()</code> stack, a flag <code>TIG_SIGPENDING</code> <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/signal.c#L772">will be set</a>, warning the task to check its pending signals. It might be that the thread is in userland at the time we try to wake it up, even worse it might be infinitely looping. In that case, it would not enter the kernel until the scheduler decides to preempt it or an interrupt fires. This case is avoided by forcing the thread to enter the kernel via <a href="https://elixir.bootlin.com/linux/v6.5.10/source/kernel/signal.c#L782"><code>kick_process()</code></a>, which sends an <a href="https://en.wikipedia.org/wiki/Inter-processor_interrupt">IPI</a> to the remote CPU, forcing it to trap the process into the kernel, which will then try to return to userland, check <code>TIG_SIGPENDING</code>, find a <code>SIGKILL</code>, and terminate.</p>
<h2 id="voluntarysignalchecking">Voluntary signal checking</h2>
<p>While signals are only processed when returning to userland, checking if those signals are pending can be done anywhere. tmpfs, ext4, xfs, and many other filesystems will check if a fatal signal is pending before starting a write. If a fatal signal is pending, they will return an error to the caller, unwinding the system call stack up until the point of returning to userland, which then terminates the program as we've seen before. The voluntary check for tmpfs and ext4 write can be seen <a href="https://elixir.bootlin.com/linux/v6.5.10/source/mm/filemap.c#L3918">here</a>.</p>
<p>We can now reason what happens in tmpfs if we install an eBPF program that generates a <code>SIGKILL</code> early in kernel entry: the write would not be issued, as the signal would be noticed, and the operation aborted. </p>
<p>Btrfs doesn't behave like other filesystems, however. It doesn't check for signals before issuing a write or read further down the IO stack. When a <code>SIGKILL</code> is received, it completes the IO operation before terminating.</p>
<p>We cannot prevent Btrfs from being able to write by generating a <code>SIGKILL</code> from an eBPF program when the program enters the write system call. Assuming this is what we would like to do, it’s logical to consider generating a <code>SIGKILL</code> earlier on <code>openat(2)</code>: this way we terminate the program much earlier, even before it has a chance to issue a write. Unfortunately, this is also unreliable, as demonstrated in the next section.</p>
<h2 id="racingopenwriteoperations">Racing open &amp; write operations</h2>
<p>If we generate the <code>SIGKILL</code> in <code>openat(2)</code>, it is still possible to write to a file descriptor that would be returned, at least with Btrfs. The following <a href="https://opensource.com/article/19/8/introduction-bpftrace">bpftrace</a> line will install a tiny eBPF program on <code>vfs_open()</code> that will generate a <code>SIGKILL</code> and terminate any process trying to open the file named <code>__nowrite</code>.</p>
<pre><code>bpftrace --unsafe -e 'kprobe:vfs_open /str(((struct path *)arg0)-&gt;dentry-&gt;d_name.name) == "__nowrite"/ 
 { signal("SIGKILL") }'
</code></pre>
<p>It's still possible to race the kernel and write to the would-be file descriptor, meaning we can't rely on this mechanism to prevent the file from being modified even if we can terminate the process.</p>
<p>It should be clear by now that the open operation happens, as discussed at the beginning of this article. A file can be created with the <code>O_CREAT</code> flag, and then the effects that occur between the open operation and process termination are observable. The important observable effect is that the process file table is <a href="https://elixir.bootlin.com/linux/v6.5.10/source/fs/open.c#L1412">populated</a> just before it terminates.</p>
<p>The process file table is a per-process in-kernel table that maps file descriptor numbers to file objects. This is where, for example, file descriptor 1 refers to a file object representing standard output, so if userland calls <code>write(1, "foo", strlen("foo"))</code>, the kernel will look for the object referenced by file descriptor 1 and call <code>vfs_write()</code> on it. The file structure has callbacks that know how to write to standard output, we say this is the backing of the file descriptor.</p>
<p>The general idea is to guess the file descriptor number that would be returned by an open operation and attempt to write to it before the process is terminated but after the open operation takes effect.</p>
<p>The first trick is figuring out what the file descriptor number would be, this can be done with:</p>
<pre><code>int guessed_fd;

guessed_fd = dup(0);
close(guessed_fd);
</code></pre>
<p>When a file descriptor is created via <code>dup(2)</code>, <code>open(2)</code>, <code>accept(2)</code>, <code>socket(2)</code>, or any other system call, it is guaranteed to use the lowest available number. If we <code>dup</code> any file descriptor and close it, the next system-call-creating file descriptor will likely end up using the same index that we got from <code>dup(2)</code> earlier. This isn’t necessarily true for multithreaded programs, as another thread might create a file descriptor and invalidate our guess. It’s because of these races that <code>dup2(2)</code> exists, to allow multithreaded programs to have a race-free <code>dup</code>. Multithreading was a late addition to UNIX systems, so the old semantics of file descriptor numbering had to be preserved.</p>
<p>This guessing is not necessary because we have a controlled environment. However, it is interesting because it could be used as the base block for an attack trying to exploit this race condition.</p>
<p>Now that we have a target file descriptor, we can spawn a bunch of worker threads attempting to write to it!</p>
<pre><code>/*
 * Guess the next file descriptor open will get
 */
if ((fd = dup(0)) == -1)
    err(1, "dup");
close(fd);

/*
 * Hammer Time, spawn a bunch of threads to write at the guessed fd,
 * they hammer it even before we open.
 */
while (num_workers--)
    if (pthread_create(&amp;t_writer, NULL, writer, &amp;fd) == -1)
        err(1, "pthread_create");

/* Give the workers some lead time */
msleep(10);

/*
 * This should never return, since we are supposed to be SIGKILLed.
 * The race depends on the workers hitting the file descriptor after
 * open(2) succeeded (after fd_install()) but before
 * exit_to_user_mode()-&gt;do_group_exit().
 */
fd = open(path, O_RDWR|O_CREAT, 0660);
errx(1, "not killed, open returned fd %d", fd);
</code></pre>
<p>The writer-worker code is as simple as you could expect:</p>
<pre><code>void *
writer(void *vpfd)
{
    ssize_t n;
    int fd = *(int *)vpfd;

    /*
     * We'll just hammer-write the guessed file descriptor, if we succeed
     * we just bail as the parent thread is about to do it anyway.
     */
    while (1) {
        n = write(fd, SECRET, strlen(SECRET));
        /* We expect to get EBADFD mostly */
        if (n &lt;= 0) {
            continue;
        }
        /* Hooray, the file has been written */
        break;
    }

    return (NULL);
}
</code></pre>
<p>The complete program is available <a href="https://github.com/elastic/ebpf-sig-exp/blob/main/race-openwrite.c">here</a>.</p>
<p>Most of the time we can't trigger the race condition and the program terminates with <code>SIGKILL</code>. With enough attempts from running the program in a loop, though, we can hit the race in about a minute.</p>
<pre><code>truncate -s0 __nowrite
until test -s __nowrite; do ./race-openwrite __nowrite; done
</code></pre>
<p>It's worth pointing out that this behavior is <strong>not</strong> a kernel bug in any way and is only reproducible in Btrfs. We've failed to trigger this race condition in other filesystems like ext4, tmpfs, and xfs as these implementations explicitly check for a fatal signal pending before proceeding with the write.</p>
<h2 id="othereffects">Other Effects</h2>
<p>We’ve talked about open and write, and we've also checked the behavior of attempting to block the effects of other system calls by generating <code>SIGKILL</code>. In the table below, <code>BLOCKED</code> means the effect did not occur. For example, unlink did not remove the file. As you can guess,  <code>UNBLOCKED</code> means the effect did occur – unlink did remove the file. In both cases the program is always SIGKILLed, meaning our signal generation did occur.</p>
<p>| 6.5.5-200.fc38.x86_64 | Btrfs     | tmpfs     | Ext4      |
|-----------------------|-----------|-----------|-----------|
| chmod(2)              | UNBLOCKED | UNBLOCKED | UNBLOCKED |
| link(2)               | UNBLOCKED | UNBLOCKED | UNBLOCKED |
| mknod(2)              | UNBLOCKED | UNBLOCKED | UNBLOCKED |
| write(2)              | UNBLOCKED | BLOCKED   | BLOCKED   |
| race-open-write       | UNBLOCKED | BLOCKED   | BLOCKED   |
| rename(2)             | UNBLOCKED | UNBLOCKED | UNBLOCKED |
| truncate(2)           | UNBLOCKED | UNBLOCKED | UNBLOCKED |
| unlink(2)             | UNBLOCKED | UNBLOCKED | UNBLOCKED |</p>
<p>| 6.1.55-75.123.amzn2023.aarch64 | XFS       |
|--------------------------------|-----------|
| chmod(2)                       | UNBLOCKED |
| link(2)                        | UNBLOCKED |
| mknod(2)                       | UNBLOCKED |
| write(2)                       | BLOCKED   |
| race-open-write                | BLOCKED   |
| rename(2)                      | UNBLOCKED |
| truncate(2)                    | UNBLOCKED |
| unlink(2)                      | UNBLOCKED |</p>
<p>| Instruction                       | 6.5.5-200.fc38.x86_64 | 6.1.55-75.123.amzn2023.aarch64 |
|-----------------------|-----------------------|--------------------------------|
| write(2) on a pipe(2) | UNBLOCKED             | UNBLOCKED                      |
| fork(2)               | BLOCKED               | BLOCKED                        |</p>
<p>The same behavior is observed for all the equivalent “at” system calls: <code>openat(2)</code>, <code>renameat(2)</code>…</p>
<h2 id="conclusion">Conclusion</h2>
<p>We’ve demonstrated some of the pitfalls of attempting to use <code>SIGKILL</code> as a security mechanism from eBPF, while there are cases where it can be used reliably, those are delicate and require a deep understanding of the environment in which they are run. The key takeaways from this article are:</p>
<ul>
<li>Signal generation from within eBPF is synchronous since it’s generated to-and-from the same process context</li>
<li>Signals are processed in the kernel after the system call takes place</li>
<li>Specific system calls and combinations will avoid starting an operation if a fatal signal is pending</li>
<li>We can’t reliably prevent a <code>write(2)</code> on Btrfs, even if we kill the program before <code>open(2)</code> returns from the kernel</li>
</ul>
<p>While our research is thorough, these are delicate semantics that might depend on external factors. If you believe we’ve missed something please do not hesitate to contact us. </p>
<p>If you’re interested in seeing more, the programs and scripts used in this research are public and available in <a href="https://github.com/elastic/ebpf-sig-exp/">this repository</a>. Interested in learning more about the kernel? Check out <a href="https://www.elastic.co/security-labs/peeling-back-the-curtain-with-call-stacks">this deep dive</a> on call-stacks.</p>]]></content:encoded>
    <link>https://www.elastic.co/security-labs/threat-command/signaling-from-within-how-ebpf-interacts-with-signals</link>
    <guid isPermaLink="false">signaling-from-within-how-ebpf-interacts-with-signals</guid>
    <category><![CDATA[Platform Internals]]></category>
    <dc:creator><![CDATA[Christiano Haesbaert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8720f53ea1c1554e/6a7c93e296b5a689f7875931/photo-edited-09@2x.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 28 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>