Skip to content

n_gsm TTY use-after-free (CVE-2023-6546)

Linux n_gsm GSM 0710 line discipline'ında bir race condition: eşzamanlı GSMIOC_SETCONF ioctl'leri struct gsm_dlci'yi free edip yeniden okuyor ve mux restart sırasında bir use-after-free doğuruyor — bu n_gsm DLCI UAF'ı için gerçek CVE, CVE-2023-31436 değil CVE-2023-6546.

Mechanism

Warning

CVE ID düzeltmesi. Başlıktaki CVE-2023-31436 alias'ı bir n_gsm bug'ını tarif etmiyor. NVD'ye göre CVE-2023-31436, QFQ packet scheduler net/sched/sch_qfq.c'deki bir out-of-bounds write (qfq_change_class, lmax'ın QFQ_MIN_LMAX'ı aşması) — tamamen farklı bir subsystem. Bu sayfanın asıl anlattığı n_gsm DLCI use-after-free'i CVE-2023-6546 olarak takip ediliyor. Biz doğrulanmış n_gsm bug'ını belgeliyoruz ve yanlış etiketi tekrarlamak yerine işaret ediyoruz.

Info

Aynı vulnerability. Bu sayfa, gsm-0710-multiplexing-race-uaf ile aynı n_gsm race'ini (CVE-2023-6546, fix commit 3c4f8333b582) anlatır. Fark yalnızca vurgu: bu sayfa hatalı CVE-2023-31436 alias'ının düzeltilmesine odaklanır; diğer sayfa exploitation path'ine (ZDI-24-020 PoC, gsm->output() function-pointer overwrite) odaklanır. İki sayfa birbirinin karşılıklı see_also'sunda listelenir.

Note

n_gsm line discipline'ı (drivers/tty/n_gsm.c), tek bir serial/tty link üzerine birden çok logical channel'ı (DLCI — Data Link Connection Identifier) katmanlayan 3GPP GSM 07.10 / 0710 multiplexing protokolünü implemente eder. Her channel bir struct gsm_dlci; mux'un kendisi struct gsm_mux (gsm), gsm->dlci[0] ise control channel.

GSMIOC_SETCONF ioctl'i, canlı bir mux'u yeniden konfigüre eder. Reconfiguration mux'u yıkıp yeniden kurar ve teardown, gsm_cleanup_mux() üzerinden işler — bu fonksiyon DLCI nesnelerini free eder ve slot'larını NULL'a temizler.

Bug, use-after-free'e yol açan bir race condition. İki thread, gsm line discipline etkinken aynı tty fd'si üzerinde GSMIOC_SETCONF çağırdığında:

  • Thread 2, mux mutex'ini almadan önce DLCI pointer'ını cache'liyor: struct gsm_dlci *dlci = gsm->dlci[0];
  • Thread 1, mutex_lock(&gsm->mutex) alıyor, DLCI nesnelerini free edip slot'ları NULL'a set ediyor, sonra lock'ı bırakıyor.
  • Thread 2 nihayet mutex'i alıyor ve bayatlamış cache'li pointer'ını dereference ediyor — örn. dlci->dead = true; — free edilmiş belleğe dokunuyor.

İhlal edilen invariant: "paylaşılan bir yapıdan okunan bir pointer, o yapıyı koruyan lock altında yeniden doğrulanmalıdır." gsm_cleanup_mux(), gsm->dlci[0]'ı critical section dışında okudu; böylece free edilmiş/NULL'lanmış slot ile lokal cache'lenmiş pointer birbirinden ayrıştı.

Upstream fix (commit 3c4f8333b582), dlci = gsm->dlci[0] atamasını mutex_lock(&gsm->mutex)'tan sonraya taşıyarak window'u kapatıyor; böylece pointer yalnızca lock, DLCI'nin eşzamanlı olarak free edilmediğini garantilediğinde okunuyor.

Walkthrough

Race için kavramsal tetikleme (yalnızca yetkili test):

  1. Bir pty/serial master aç ve line discipline'ını gsm'e geçir:

    int ldisc = N_GSM0710;
    ioctl(fd, TIOCSETD, &ldisc);   // enable n_gsm on the tty
    
  2. Aynı fd'yi paylaşan iki thread'den, reconfigure ioctl'ini döveç gibi çağır; böylece bir thread mux'u free ederken diğeri yeniden içeri giriyor:

    struct gsm_config cfg = { /* ... */ };
    // thread A and thread B both loop on:
    ioctl(fd, GSMIOC_SETCONF, &cfg);
    

Reconfigure yolu mux'u yeniden başlatır; gsm_cleanup_mux() bir thread'de gsm->dlci[0]'ı free ederken, diğer thread hâlâ o pointer'ın pre-lock bir kopyasını tutuyor.

  1. Yarışı kaybeden thread, free edilmiş struct gsm_dlci'yi dereference eder (dlci->dead = true;) — use-after-free.

Note

struct gsm_dlci free edildiği ve attacker timing artı tty input'unu kontrol ettiği için, free edilmiş slab nesnesi aynı boyutta bir spray ile geri alınabilir (örn. tty nesnelerine karşı kullanılan heap-grooming teknikleri). Free edilmiş DLCI, attacker'ın kontrol ettiği data ile bir kez örtüştüğünde, sonraki field write/dereference'ler kontrol edilebilir bir primitive haline gelir — tty-subsystem bug'ları için standart UAF-to-LPE yolu.

Fix'in etkisi (commit 3c4f8333b582, "tty: n_gsm: Fix UAF in gsm_cleanup_mux"), pseudo-diff biçiminde:

- struct gsm_dlci *dlci = gsm->dlci[0];
  ...
  mutex_lock(&gsm->mutex);
+ dlci = gsm->dlci[0];   // read only after the lock is held
  ...
  mutex_unlock(&gsm->mutex);

Pointer'ı lock altında okumak, eşzamanlı bir gsm_cleanup_mux()'un onu okuma ile kullanım arasında free etmiş olamayacağı anlamına gelir.

Detection

  • Reachability: tarihsel olarak gsm line discipline'ı unprivileged kullanıcılar tarafından attach edilebiliyordu; n_gsm'i CAP_NET_ADMIN arkasına alan / module-blocklisting yapan dağıtımlar local attack surface'ini ortadan kaldırır.
  • Runtime: KASAN bunu doğrudan, eşzamanlı GSMIOC_SETCONF sırasında gsm_cleanup_mux içinde bir use-after-free read/write olarak işaretler.
  • Behavioural: bir tty açan, N_GSM0710 set eden ve birden çok thread'den GSMIOC_SETCONF veren unprivileged bir process, cellular-modem userland'ı dışında anormaldir.

Mitigation

  • Commit 3c4f8333b582'yi içeren bir kernel'e patch'le (yetkili fix; aynı n_gsm race ailesi ayrıca CVE-2023-6546 altında da takip ediliyor).
  • GSM 0710 muxing'in kullanılmadığı yerlerde n_gsm modülünü blocklist'le.
  • Race'in unprivileged koddan ulaşılamaz olması için gsm line discipline'ı attach etmeyi CAP_NET_ADMIN'e bağla.

References