Trzy podatności, trzy różne podsystemy kernela, ten sam efekt: nieuprzywilejowany użytkownik zapisuje dane do page cache pliku tylko do odczytu i zostaje rootem. Dirty COW potrzebował race condition. Dirty Pipe był deterministyczny. CopyFail robi to samo w 732 bajtach Pythona.

Dirty COW (CVE-2016-5195)

Kiedy: w kernelu od 2007, odkryty w 2016. Siedział 9 lat.

Gdzie: mm/gup.c, obsługa copy-on-write w get_user_pages().

Jak działa:

Kernel przy zapisie do prywatnego mapowania pliku (MAP_PRIVATE) powinien najpierw utworzyć kopię COW, a potem zapisać. Problem w tym, że te dwie operacje nie były atomowe.

Exploit uruchamia dwa wątki:

  1. Pierwszy pisze do mapowania przez /proc/self/mem, wymuszając COW break
  2. Drugi wywołuje madvise(MADV_DONTNEED), każąc kernelowi wyrzucić właśnie utworzoną kopię COW

Jeśli madvise trafi w okno między wyszukaniem strony a zapisem, kernel zapisze dane bezpośrednio do oryginalnej strony page cache. Po milionach prób w końcu się udaje.

Niezawodność: niska, race condition wymaga wielu iteracji.

Odkrywca: Phil Oester. Znalazł exploit w ruchu sieciowym, czyli podatność była wykorzystywana zanim ją ujawniono.

Fix: commit 19be0eaffa3a, zablokowanie możliwości przerwania operacji COW przez madvise.


Dirty Pipe (CVE-2022-0847)

Kiedy: w kernelu od 5.8 (2020), odkryty w 2022. Siedział 2 lata.

Gdzie: lib/iov_iter.c, niezainicjalizowane pole flags w struct pipe_buffer.

Jak działa:

W 2020 dodano flagę PIPE_BUF_FLAG_CAN_MERGE, która mówi kernelowi, że może dopisywać dane do istniejącego bufora pipe zamiast alokować nowy. Problem: po opróżnieniu pipe bufory wracają do puli, ale flaga CAN_MERGE nie jest czyszczona.

Exploit:

  1. Utwórz pipe i zapełnij go danymi, każdy bufor dostaje flagę CAN_MERGE
  2. Opróżnij pipe, bufory zwolnione, ale flaga zostaje
  3. splice() jeden bajt z pliku docelowego do pipe, page cache trafia do bufora z zastaną flagą CAN_MERGE
  4. Zapis do pipe: kernel widzi CAN_MERGE i dopisuje dane bezpośrednio do strony page cache

Deterministyczny, bez race condition. Sekwencja kilku syscalli.

Ograniczenia: nie da się pisać na offset 0 strony, nie da się przekraczać granic stron, nie da się powiększać pliku.

Odkrywca: Max Kellermann (CM4all). Trafił na problem, diagnozując uszkodzone logi.

Fix: commit 9d2231c5d74e, inicjalizacja flags na zero przy tworzeniu nowych buforów.


CopyFail (CVE-2026-31431)

Kiedy: w kernelu od 4.14 (2017), odkryty w 2026. Siedział 9 lat.

Gdzie: algif_aead.c, optymalizacja in-place w podsystemie kryptograficznym kernela (AF_ALG).

Jak działa:

Trzy niezależne zmiany utworzyły podatność:

  • 2011: algorytm authencesn (IPsec ESN) zapisuje 4 bajty scratch data za obszarem tagu
  • 2015: AF_ALG dostał AEAD ze splice(), więc page cache trafia do scatterlists
  • 2017: optymalizacja req->src = req->dst sprawia, że strony page cache lądują w zapisywalnej liście docelowej

Exploit:

  1. Otwórz socket AF_ALG, binduj do authencesn(hmac(sha256),cbc(aes))
  2. splice() dane z pliku docelowego (np. /usr/bin/su), page cache ląduje w scatterliście
  3. sendmsg() z kontrolowanym AAD, gdzie bajty 4–7 to wartość zapisu
  4. recv() uruchamia deszyfrowanie, authencesn zapisuje 4 bajty do page cache
  5. Powtórz dla każdego fragmentu shellcode’u
  6. execve("/usr/bin/su") odpala zmodyfikowaną binarkę z page cache, shellcode jako root

Cały exploit: 732 bajty Pythona, standardowa biblioteka, zero zależności.

Odkrywca: Theori / Xint Code.

Fix: commit a664bf3d603d, powrót do operacji out-of-place, rozdzielenie req->src i req->dst.


Porównanie

Dirty COW Dirty Pipe CopyFail
CVE CVE-2016-5195 CVE-2022-0847 CVE-2026-31431
CVSS 7.0 7.8 7.8
Podsystem mm (COW) pipe crypto (AF_ALG)
Mechanizm race condition w madvise/write stała flaga CAN_MERGE po splice scratch write authencesn do page cache
Deterministyczny nie tak tak
Czas w kernelu 9 lat (2007–2016) 2 lata (2020–2022) 9 lat (2017–2026)
Exploit C, dwa wątki, miliony prób C, sekwencja syscalli Python, 732 bajty
Modyfikuje dysk tak nie (tylko page cache) nie (tylko page cache)
Ucieczka z kontenera nie dotyczy tak tak
Używany in the wild tak (przed ujawnieniem) tak (CISA KEV) niepotwierdzony

Ewolucja

Trzy podatności pokazują ten sam prymityw (zapis do page cache pliku tylko do odczytu), ale z rosnącą elegancją:

Dirty COW wymagał wygrania wyścigu między dwoma wątkami. Niestabilny, ale działał na kernelach od 2.6.22, czyli praktycznie na wszystkim.

Dirty Pipe wyeliminował race condition. Jedna niezainicjalizowana flaga wystarczyła, żeby splice + write trafiały bezpośrednio do page cache. Nie modyfikował pliku na dysku, więc był trudniejszy do wykrycia.

CopyFail poszedł dalej: exploit mieści się w jednym krótkim skrypcie Pythona, działa identycznie na Ubuntu, RHEL, Amazon Linux i SUSE. Też nie dotyka dysku. Kontrolowany 4-bajtowy zapis pod dowolnym offsetem, powtarzalny bez limitu.

Wspólny mianownik: page cache jako współdzielony zasób między userspace a kernelem to potężny mechanizm, ale każdy błąd, który pozwala na nieautoryzowany zapis do niego, natychmiast daje eskalację uprawnień.


Więcej o CopyFail: CopyFail: 9 lat ukrytej eskalacji uprawnień | Sprawdź czy Twój kernel jest podatny

Źródła: dirtycow.ninja, dirtypipe.cm4all.com, copy.fail, NVD