🔥 CVE-2026-43284 深度独立研究:源码审计 · 二次发现 · 利用方案

🔥 高危漏洞深度独立研究 · CVSS ≥ 9.8

CVE-2026-43284 深度独立研究:源码审计 · 二次发现 · 利用方案

📊 4 来源🔍 源码审计🧪 PoC
NVD-LatestPoC-in-GitHubExploit-DB-RSSQualys Security Blog

🔍 源码独立审计

https://github.com/V4bel/dirtyfrag 源码进行独立审计(置信度 75%)。

🧬 根因独立理解

在Linux内核网络发送路径中,TCP通过skb_splice_from_iter()在从pipe附加页面时设置SKBFL_SHARED_FRAG标志,而IPv4/IPv6的datagram append路径(如ip_append_data()/ip6_append_data())在将splice的pipe页面直接添加到skb frag时缺少该标志。SKBFL_SHARED_FRAG用于提示skb的frag页面可能不归本skb独占(例如pipe buffer或文件页),后续对数据内容的修改必须触发COW(写时复制)。当ESP-in-UDP包由这种共享pipe页面组成时,skb本身既不是clone也没有frag_list,因此esp_input()采用无需COW的快速路径,直接在frag页面上进行解密运算,导致同一页面的pipe端内容被同步修改。攻击者可以从pipe读取到被解密后的明文,绕过ESP加密。补丁在IPv4/IPv6的datagram splice场景中为skb设置SKBFL_SHARED_FRAG,与TCP保持一致。

🛤️ 漏洞触发链路

触发链路:1) 攻击者创建pipe,写入截获的ESP-in-UDP密文;2) 使用splice()将pipe读端连接到UDP socket,通过sendmsg(MSG_SPLICE_PAGES)发送数据,ip_append_data()把pipe页面直接挂入skb frags,但未设置SKBFL_SHARED_FRAG;3) 该skb作为UDP payload进入XFRM框架,被识别为ESP-in-UDP;4) esp_input()/esp_input_done()检查skb为非线性但未clone、无frag_list,且未标记shared_frag,故安全地选择原地解密(no-COW),直接在共享页面上解出明文;5) 攻击者从pipe读端读取,获得解密后数据。

🔁 二次发现(同类漏洞/扩展攻击面)

  • net/ipv4/raw.c raw_sendmsg(): IPv4 RAW socket也使用ip_append_data()的datagram append路径,同样可能因未设置SKBFL_SHARED_FRAG而将pipe页共享给ESP输入,造成同类问题。
  • net/ipv6/ip6_output.c ip6_append_data(): IPv6封装的ESP-in-UDP若通过该函数发送,同样存在未标记共享frag的路径,需要确认补丁是否覆盖所有调用者。

🩹 修复完整性分析

补丁为IPv4/IPv6的datagram splice frags显式设置SKBFL_SHARED_FRAG,能强制ESP input进入COW流程,修复根本原因。但需要检查所有调用ip_append_data/ip6_append_data的发送路径(包括UDP、RAW、ICMP等)是否都使用了统一的标记逻辑;如果只在内层通用helper中添加而某些特化路径(如packet socket、SCTP)未同步,则仍有绕过。此外,还需确保其他可能构造共享frag的入口(如MSG_ZEROCOPY)不会遗漏该标志。

⚔️ 利用方案设计

利用该漏洞可构造拒绝服务或信息泄露。关键步骤:1) 创建pipe,用截获/构造的ESP密文填充;2) 调用splice(pipe_rfd, NULL, udp_fd, NULL, len, 0),将pipe页以零拷贝方式作为UDP负载发送,目标端口为ESP-in-UDP(如4500/500);3) 内核UDP收包后送入ESP输入,因skb未标记SKBFL_SHARED_FRAG,esp_input()选择就地解密,密文所在的pipe页被直接改写成明文IPsec payload;4) 攻击者再从pipe_rfd读取,得到解密明文。若攻击者处于中间人位置且能控制目标主机的用户态进程,可截获合法密文并转交到本地socket,最终获取明文。也可以结合多个pipe页面构造超长ESP包,触发碎片/重组合并路径增大影响面积。Payload思路:在pipe中放置标准ESP-in-UDP帧(IP/UDP/ESP头),确保标记正确;利用splice的零拷贝避免复制,维持共享状态。该攻击需要能调用splice且能接收网络包,实际提升的是本地信息泄露/权限利用能力。

🧪 PoC 复现

从 GitHub 公开仓库抓取的实际 PoC 代码(仓库)。

📋 代码元数据语言md来源linnemanlabs/dirtyfrag-arm64针对性✅ 已验证与漏洞相关(代码含 CVE 引用)依赖见代码注释/README用法详见代码注释中的使用说明

# dirtyfrag-arm64

arm64/aarch64 port of [V4bel/dirtyfrag](https://github.com/V4bel/dirtyfrag) (CVE-2026-43284,CVE-2026-43500).

Tested on Ubuntu 24.04.4 LTS with `linux-aws 6.17.0-1013-aws` on AWS Graviton (newest available as of this writing).

>**Full writeup with AppArmor bypass analysis,hardening notes,
and detection notes:** [linnemanlabs.com/posts/porting-dirtyfrag-arm64](https://linnemanlabs.com/posts/porting-dirtyfrag-arm64/)

## ⚠️ Ubuntu AppArmor userns restrictions do not reliably prevent this exploit 

Ubuntu has two AppArmor sysctls:
- kernel.apparmor_restrict_unprivileged_userns
- kernel.apparmor_restrict_unprivileged_unconfined

Both can be bypassed by chaining `aa-exec` with itself using profiles present on the standard Ubuntu cloud and installer images I tested:

```
aa-exec -p crun -- aa-exec -p crun -- ./dirtyfrag_arm64 --force-esp
```

For more info see [Two Hops and a Shell](https://linnemanlabs.com/posts/two-hops-and-a-shell/) for the full analysis of the Ubuntu AppArmor bypass.

## What's different on arm64

The upstream x86\_64 PoC uses two exploit paths: an ESP/xfrm path that corrupts `/usr/bin/su`,
and an rxrpc/rxkad fallback that corrupts `/etc/passwd`. On arm64 the rxrpc path kernel-oopses and cannot be used. The ESP path works cleanly.

### rxrpc crash: `flush_dcache_page`

On x86\_64,`flush_dcache_page()` is a no-op. x86 has hardware-coherent data/instruction caches. On arm64,
it performs real dcache maintenance and dereferences the `struct page*` metadata. When the rxrpc crypto path (`rxkad_secure_packet` ->`crypto_pcbc_encrypt` ->`skcipher_walk_done`) calls `flush_dcache_page` on a page whose reference has been manipulated through the splice/vmsplice chain,
x86\_64 silently skips it but arm64 hits a translation fault and oopses:

```
pc : flush_dcache_page+0x18/0x58
lr : skcipher_walk_done+0xbc/0x260
     crypto_pcbc_encrypt+0xe8/0x1c8 [pcbc]
     crypto_skcipher_encrypt+0x48/0xb8
     rxkad_secure_packet+0x108/0x270 [rxrpc]
     rxrpc_send_data+0x264/0x550 [rxrpc]
```

On the arm64 systems I tested,
denying the `uid_map` write removed the working ESP path. The namespace may still be created,but the process cannot map itself to root inside it or gain the namespaced capabilities needed for XFRM setup. The rxrpc fallback did not provide a working namespace-free privilege-escalation path on arm64,it oopsed the kernel instead.

### ESP-only operation

On arm64,
only the ESP path was viable in my testing. This path requires creating a user and network namespace and then successfully mapping the calling user to root inside that namespace. Distro hardening can break that path in different ways: Ubuntu can deny the `uid_map` write through AppArmor userns restrictions,I have not tested on Debian/RHEL.

### AppArmor: blocked by default,
bypassable on Ubuntu

On the Ubuntu 24.04 AWS image I tested,`apparmor_restrict_unprivileged_userns=1` blocked direct exploitation from my normal SSH shell by denying the `uid_map` write inside the new namespace.

However,with the default `apparmor_restrict_unprivileged_unconfined=0`,
an unconfined user can transition into an existing complain-mode profile (e.g. `runc`) via `aa-exec` and bypass the restriction:

```bash
aa-exec -p runc -- ./dirtyfrag_arm64 --force-esp
```
Setting `kernel.apparmor_restrict_unprivileged_unconfined=1` blocks this path,and is widely recommended currently as a solution to block all paths. However,
adding another aa-exec bypasses that also:

```
aa-exec -p crun -- aa-exec -p crun -- ./dirtyfrag_arm64 --force-esp
```

See the earlier linked posts for details.

### Architecture-specific payload

The exploit overwrites `/usr/bin/su` in the page cache with a minimal static ELF. The upstream PoC embeds an x86\_64 ELF with x86\_64 shellcode. This port replaces it with an equivalent aarch64 ELF:

- ELF `e_machine`: `EM_AARCH64` (183) instead of `EM_X86_64` (62)
- Shellcode: aarch64 instructions using `svc #0` instead of `syscall`
- Syscall numbers: setgid=144,
setuid=146,setgroups=159,execve=221 (vs 106,105,116,59 on x86\_64)
- Fixed 4-byte instruction width (vs x86\_64 variable-length),resulting in a slightly larger payload (~216 bytes vs 192)

## Build &
run

```bash
# Clone
git clone https://github.com/linnemanlabs/dirtyfrag-arm64.git

# Build
cd dirtyfrag-arm64
gcc -O0 -Wall -o dirtyfrag_arm64 dirtyfrag_arm64.c -lutil

# Run
./dirtyfrag_arm64 --force-esp
```

The `--force-esp` flag skips the rxrpc path entirely to ensure avoiding the arm64 kernel oops.

## Tested environment

|Property |Value ||---|---||Instance |AWS t4g.micro (Graviton2) ||OS |
Ubuntu 24.04.4 LTS ||Kernel |`6.17.0-1013-aws #13~24.04.1-Ubuntu` (built 2026-04-24) ||Architecture |aarch64 ||`unprivileged_userns_clone` |1 (enabled) ||esp4 module |available,loadable ||rxrpc module |available,loadable (but crashes on arm64) ||Configuration |stock ubuntu 24.04 cloud image,default kernel modules |As of 2026-05-09,the latest available Ubuntu 24.04 aws kernel (`6.17.0-1013-aws`,
built April 24) ships without patches for either Copy Fail (CVE-2026-31431,disclosed April 29) or Dirty Frag (CVE-2026-43284/43500,disclosed May 7).

## Immediate mitigation

Blacklist the vulnerable modules,
apply appropriate system hardening for your distro.

### Blacklist the vulnerable modules
Safe on any system not actively using IPsec transport mode or AFS. To prevent loading of the modules put the following into `/etc/modprobe.d/dirtyfrag.conf`:

```
install esp4 /bin/false
install esp6 /bin/false
install rxrpc /bin/false
```

### Update initramfs

Ubuntu also recommends regenerating initramfs so the blacklist is present during early boot:

```bash
update-initramfs -u -k all
``

### Remove read permissions from SUID binaries

Blocks this splice-based page-cache attack class against those SUID binary targets. The exploit needs read permissions to the target file for `splice()`. Users can still execute the binaries.

**Do not implement this without testing it in your environment and across all of your tooling.**

```bash
chmod o-r /usr/bin/su
```

This is not a fix,
only a mitigation. There are many more paths to privilege escalation.

### Unload the modules

To unload the modules from the running system:

```bash
rmmod esp4 esp6 ipcomp4 ipcomp6 rxrpc 2>/dev/null
```

Ensure they are unloaded:

```bash
grep -qE '^(esp4|esp6|rxrpc) ' /proc/modules \
  &&echo "Affected modules are loaded" \
  ||
echo "Affected modules are NOT loaded"
```

### Flush page cache

Flushing the page cache should remove the malicious contents and cause the files to be read from disk again.

```bash
echo 3 >/proc/sys/vm/drop_caches
```

Note: I have had some inconsistent results with this,but the more I try to reproduce it the more it's working as expected. For this PoC,
you can check the md5sum on /usr/bin/su,and if it doesn't match,reboot.

### Cleaning up after testing

Run the flush page cache step,
then verify after with:

```bash
sha256sum /usr/bin/su

# Or from package manager:

dpkg -V util-linux    # Debian/Ubuntu
rpm -V util-linux     # RHEL/Amazon Linux
```

### Proactive moves

For a more proactive posture that addresses this entire vulnerability class (not just the specific CVEs),see the full writeup for AppArmor userns restrictions,module preloading prevention,
and Tetragon-based runtime detection as well as YARA rules.

## Credits

- [Hyunwoo Kim (@v4bel)](https://github.com/V4bel) - original vulnerability research,disclosure,and x86\_64 PoC
- [SiCk](https://afflicted.sh) - bypass-pwn research on Ubuntu AppArmor bypass
- [Keith Linneman / LinnemanLabs](https://linnemanlabs.com) - arm64 port,`flush_dcache_page` crash analysis,AppArmor research,
detection notes

## Legal

This tool is intended for authorized security testing and research only.

Unauthorized use against systems you do not own or have explicit permission to test is illegal and unethical.

## License

MIT. Copy it,steal it,modify it,learn from it,share your improvements with me. Or don't. It's code,do what you want with it.

⚔️ EXP 利用代码

截至分析时,Exploit-DB 未收录该 CVE 的公开利用代码。可利用上述 PoC 进行验证,或关注 Exploit-DB 更新。

🕵️ 检测指纹

针对该 CVE 的自动化检测规则(可直接用于扫描与审计)。

🛡️ Semgrep 审计规则: CVE-2026-43284.yaml

📋 代码元数据语言yaml来源rules/semgrep/CVE-2026-43284.yaml针对性✅ 按 CVE 匹配依赖semgrep用法semgrep --config CVE-2026-43284.yaml

rules:
  - id: CVE-2026-43284-offbyone-c
    languages: [c]
    severity: ERROR
    message: "Potential off-by-one or fragmentation vulnerability in kernel space (CVE-2026-43284). Avoid improper flush_dcache_page usage and rxrpc path errors on arm64."
    patterns:
      - pattern: flush_dcache_page(...)
      - pattern: rxrpc(...)
    fix: "Ensure proper page alignment and bounds checking;
use flush_dcache_page() only with validated struct page pointers."
    metadata:
      cwe: "CWE-131"
      owasp: "A6: Security Misconfiguration"
      technology: linux-kernel
      references:
        - "https://nvd.nist.gov/vuln/detail/CVE-2026-43284"
  - id: CVE-2026-43284-kernel-oops-c
    languages: [c]
    severity: ERROR
    message: "Potential kernel oops due to incorrect page cache handling or flush_dcache_page on unsupported paths in arm64."
    patterns:
      - pattern: flush_dcache_page($PAGE)
      - pattern-not: flush_dcache_page(NULL)
    fix: "Add architecture-specific condition checks before calling flush_dcache_page();
verify !PageHighMem() and proper mapping."
    metadata:
      cwe: "CWE-476"
      owasp: "A6: Security Misconfiguration"
      technology: linux-kernel
      references:
        - "https://nvd.nist.gov/vuln/detail/CVE-2026-43284"

🛡️ CodeQL 审计规则: CVE-2026-43284.ql

📋 代码元数据语言ql来源rules/codeql/CVE-2026-43284.ql针对性✅ 按 CVE 匹配依赖codeql用法codeql database run

/**
 * @kind problem
 * @id cpp/kernel-memory-corruption/cve-2026-43284
 * @name Potential memory corruption in rxrpc path (CVE-2026-43284)
 * @description Potential use-after-free or double-free in kernel memory management related to dirtyfrag pattern
 * @problem.severity error
 * @tags security
 *       external/cwe/cwe-416
 */
import cpp
import semmle.code.cpp.dataflow.DataFlow
import semmle.code.cpp.dataflow.TaintTracking

class KernelAllocSink extends DataFlow::ExprNode {
KernelAllocSink() {exists(FunctionCall fc |fc.getTarget().getName() = "kmalloc" and
      fc.getArgument(0) = this.asExpr()
    ) or
    exists(FunctionCall fc |fc.getTarget().getName() = "kzalloc" and
      fc.getArgument(0) = this.asExpr()
    )
  }}class KernelFreeSource extends DataFlow::FunctionCallNode {KernelFreeSource() {
this.getTarget().getName() = "kfree" and
    not this.getTarget().getFile().getRelativePath().matches("%test%") and
    not this.getTarget().getFile().getRelativePath().matches("%kunit%")
  }}class KernelMemoryDirtyFragConfig extends TaintTracking::Configuration {KernelMemoryDirtyFragConfig() {this = "KernelMemoryDirtyFragConfig" }override predicate isSource(DataFlow::Node source) {
source.asExpr().(FunctionCall).getTarget().getName().matches("rxrpc%") or
    source.asExpr().(FunctionCall).getTarget().getName().matches("%dirty%") or
    source.asExpr().(FunctionCall).getTarget().getName().matches("flush_dcache_page")
  }override predicate isSink(DataFlow::Node sink) {sink instanceof KernelAllocSink or
    sink instanceof KernelFreeSource
  }
override predicate isAdditionalTaintStep(DataFlow::Node node1,DataFlow::Node node2) {exists(Assignment a |a.getLValue() = node1.asExpr() and
      a.getRValue() = node2.asExpr()
    ) or
    exists(FunctionCall fc |fc.getArgument(0) = node1.asExpr() and
      fc.getTarget().getName().matches("memcpy") and
      fc.getArgument(1) = node2.asExpr()
    )
  }}from DataFlow::Node source,
DataFlow::Node sink,KernelMemoryDirtyFragConfig config
where config.hasFlow(source,sink)
select sink,"Potential memory corruption flow from $@ to $@",source,"kernel operation",sink,"allocation/free site"

🤖 高危漏洞深度独立研究引擎生成 · 2026-08-06 12:02

[!] CONTACT_CHANNELS

如需商务合作、技术咨询或漏洞反馈,请通过以下离岸节点联系作者。

> PING_AUTHOR (@A1RedTeam)