Skip to content

CVE-2023-33106: Qualcomm Adreno GPU KGSL_GPU_AUX_COMMAND_SYNC OOB (in-the-wild)

Devasa bir sync-point sayısıyla bir AUX command submit etmek, Adreno KGSL driver'ının tek bir unsigned long bitmap'in (maksimum 32 sync point) sonunun ötesinde set_bit() yapmasına neden olur ve in-the-wild exploit edilmiş lineer bir out-of-bounds write üretir.

Mechanism

Sınırsız bir user count tarafından index'lenen 32-bit bir bitmap

Qualcomm'un Adreno GPU'su kernel KGSL driver'ı tarafından sürülür. IOCTL_KGSL_GPU_AUX_COMMAND ioctl'i, user space'in auxiliary GPU işini submit etmesine, opsiyonel olarak bir synchronization point listesi eklemek için KGSL_GPU_AUX_COMMAND_SYNC flag'iyle izin verir. Sync point sayısı user'ın verdiği param->numsyncs'ten alınır.

Dahili olarak bir sync object (struct kgsl_drawobj_sync), sync point'lerinden hangilerinin hâlâ bekleyen olduğunu tek bir unsigned long olan pending bitmap field'ı kullanarak izler. Driver KGSL_MAX_SYNCPOINTS == 32 etrafında tasarlandı — yani o tek word'e sığan en fazla 32 bit pending state. Eklenen her sync point bir index alır ve o index, set_bit(event->id, &syncobj->pending)'te bit pozisyonu olarak kullanılır.

Kodun sessizce varsaydığı invariant: event->id (numsyncs tarafından sınırlanan sync-point index'inden türetilir) her zaman < 32. Ama kgsl_ioctl_gpu_aux_command() iterate etmeden önce param->numsyncs'i KGSL_MAX_SYNCPOINTS'e karşı doğrulamadı. Büyük bir numsyncs ile loop (kgsl_drawobj_sync_add_synclist()kgsl_drawobj_sync_add_sync()drawobj_add_sync_timeline(), syncobj->numsyncs'i artırarak) eninde sonunda set_bit()'i >= 32 bir bit index'iyle çağırır.

Linux'ta set_bit(n, addr), addr'ı bir bitmap'in base'i olarak ele alır ve bit n'i addr[n / BITS_PER_LONG]'a yazar. n, tek-word'lük pending field'ının boyutunu aştığında, bit pending word'ünden sonraki memory'ye yazılır — memory'de sync object'i takip eden ne varsa ona lineer bir out-of-bounds write (CWE-787). ARM'de set_bit, hedef biti içeren word üzerinde çalışır, yani attacker'ın seçtiği büyük bir index write'ı bitmap'in ötesinde kontrol edilebilir bir mesafe yürütür. Komşu bir kernel allocation'a giren o başıboş bit-set, corruption primitive'idir.

In-the-wild exploit edildi

Google Project Zero'nun root-cause analizine göre CVE-2023-33106, Google'ın Threat Analysis Group'undan Clément Lecigne tarafından raporlanmış, RCA'sı gerçek bir exploit sample'a karşı yapılmış, in-the-wild exploit edilmiş bir 0-day'di. İlgili Adreno ve Mali bug'larıyla birlikte 2023 sonu Android saldırı dalgasının parçasıydı ve Aralık 2023 Qualcomm/Android security update'inde düzeltildi. Aynı dönemden ilgili Adreno KGSL sorunları arasında CVE-2023-33107 var.

Walkthrough

Bug'a, bir KGSL fd tutan unprivileged bir process'ten, aşırı büyük bir sync list'le tek bir AUX command submit ederek ulaşılır.

OOB set_bit'in kavramsal tetiklenmesi
/* 1. Open the Adreno KGSL device (reachable by unprivileged apps). */
int fd = open("/dev/kgsl-3d0", O_RDWR);

/* 2. Build an AUX command that requests SYNC with far more than
 *    KGSL_MAX_SYNCPOINTS (32) sync points. */
struct kgsl_gpu_aux_command param = {
    .flags    = KGSL_GPU_AUX_COMMAND_SYNC,
    .numsyncs = 0x4000,          /* >> 32 : the missing bound check */
    .synclist = (uintptr_t)synclist_with_many_entries,
    /* ... timestamp, context id, etc ... */
};

/* 3. Submit. The handler iterates numsyncs, assigns each a bit index,
 *    and eventually does set_bit(id >= 32, &syncobj->pending),
 *    writing past the single unsigned long -> linear OOB write. */
ioctl(fd, IOCTL_KGSL_GPU_AUX_COMMAND, &param);

Call chain (public RCA'dan):

kgsl_ioctl_gpu_aux_command()              // no numsyncs bound check
  -> kgsl_drawobj_sync_add_synclist()     // loops over numsyncs
    -> kgsl_drawobj_sync_add_sync()
      -> drawobj_add_sync_timeline()      // syncobj->numsyncs++
        -> set_bit(event->id, &syncobj->pending)   // id can be >= 32  => OOB

Patched bir driver'da beklenen davranış: kgsl_ioctl_gpu_aux_command() isteği baştan reddeder. Fix bound check'i ekler:

if ((param->flags & KGSL_GPU_AUX_COMMAND_SYNC) &&
    (param->numsyncs > KGSL_MAX_SYNCPOINTS))
        return -EINVAL;

böylece numsyncs > 32 olan her istek -EINVAL döner ve set_bit'e asla ulaşmaz.

Detection

  • Patch level. numsyncs <= KGSL_MAX_SYNCPOINTS check'ini içeren 2023-12-01 (veya sonrası) Android/Qualcomm security patch level'ını doğrula.
  • Behavioral. numsyncs > 32 ile KGSL_GPU_AUX_COMMAND_SYNC flag'i taşıyan bir IOCTL_KGSL_GPU_AUX_COMMAND çağrısı, patch'lenmemiş bir cihazda doğrudan bir exploitation girişimidir; patch'lenmiş bir cihazda basitçe -EINVAL döner.

Mitigation

  • Aralık 2023 Qualcomm/Android security update'ini uygula.
  • Root-cause fix, sync list işlenmeden önce param->numsyncs'in KGSL_MAX_SYNCPOINTS'e karşı açık upper-bound doğrulamasıdır.
  • Hardening prensibi: sonunda fixed-size bir kernel structure'ına bit index'i veya array index'i olan herhangi bir user-supplied count, derinlerdeki bir helper'da değil ioctl boundary'sinde range-check edilmeli.

References