CAN ISOTP socket LPE (CVE-2021-32606)¶
net/can/isotp.ciçindekiisotp_setsockopt()ileisotp_bind()arasındaki bir race, freed bir socket üzerinde kayıtlı kalan bir CAN receiver bırakır; bu daisotp_rcv()içinde use-after-free ve local root sağlar.
Mechanism¶
Bind sonrası imkansız olması gereken bir socket option set etmek
CAN ISO-TP protokolü (net/can/isotp.c, ISO 15765-2), socket bir CAN interface'ine bind edildiğinde socket başına bir receive callback kaydeder. Bu callback'in bookkeeping'ini tutarlı tutmak için isotp_setsockopt(), socket bir kez bind edildikten sonra option değişikliğini reddeder — if (so->bound) kontrolünü yapıp reddeder.
Asıl açık, bu kontrol ile başka bir thread'de çalışan isotp_bind() arasındaki bir time-of-check/time-of-use race'tir:
isotp_setsockopt(), socket hâlâ unbound ikenso->bounddeğerini okur, dolayısıyla guard geçer.isotp_bind()çalışır, bir CAN receiver kaydeder veso->bound = 1yapar.isotp_setsockopt()ardındancopy_from_sockptr()çağrısını tamamlar ve artık bound olan socket'in flag'lerineCAN_ISOTP_SF_BROADCASTyazar.
Bu, imkansız bir state üretir: kayıtlı bir CAN receiver'ı olan ama CAN_ISOTP_SF_BROADCAST flag'i olmaması gerektiğini söyleyen bir socket. Unregister path'inin dayandığı invariant kırılır. isotp_release() içinde cleanup, if (so->bound && !(so->opt.flags & CAN_ISOTP_SF_BROADCAST)) koşuluna bağlıdır; bozulmuş flag bu koşulu false yapar, böylece struct isotp_sock freed olsa bile receiver hiç unregister edilmez. Artık dangling bir callback freed memory'ye işaret eder.
Race'in kök nedeni, isotp_setsockopt()'in lock_sock() olmadan çalışmasıdır; bu yüzden so->bound okuması ile so->opt.flags yazması arasına paralel bir isotp_bind() girebilir. Fix ("can: isotp: prevent race between isotp_bind() and isotp_setsockopt()") kodu lock_sock() altında isotp_setsockopt_locked()'a refactor eder ve isotp_bind() içinde SF_BROADCAST kontrolünü lock altına alır.
Bu bug, CAN_ISOTP_SF_BROADCAST desteğini ekleyen 921ca574cd38 commit'i ile 5.11-rc1'de tanıtıldı ve 5.11'den 5.12.2'ye kadar olan kernel'leri etkiler.
Walkthrough¶
UAF'i tetiklemek
Race, kayıtlı-ama-freed bir socket bıraktıktan sonra, eşleşen herhangi bir CAN frame soft-IRQ context'inden isotp_rcv()'a ulaşır ve freed struct isotp_sock üzerinde çalışır:
isotp_rcv()frame'i validate eder vecheck_pad()çağırır; bozuk padding onusk_error_report(sk)çağırmaya iter.- Attacker, freed allocation'ı,
sk_error_reportfunction pointer'ının overwrite edildiği birstruct isotp_sockkopyası ile spray ederek reclaim etmiştir. - Hijack edilmiş pointer, freed struct'ın adresini tutan
RDIüzerinden bir stack pivot gerçekleştirir ve aynı kontrol edilen allocation'a gömülü bir ROP chain'e iner. - ROP chain
modprobe_path'i overwrite eder, böylece bad-magic bir dosyanın ardından gelen bir execve, attacker binary'sini root olarak çalıştırır.
Thread A: setsockopt(..., CAN_ISOTP_SF_BROADCAST) // passes !bound check
Thread B: bind(...) // registers rcv, sets bound=1
Thread A: copy_from_sockptr() writes BROADCAST flag // corrupts bound socket
close() -> isotp_release() skips unregister (flag is set)
-> struct isotp_sock freed, receiver still live
incoming CAN frame -> isotp_rcv() on freed sock -> UAF -> hijack sk_error_report
Modern mitigation'lar kapsamda, ama engel değil
Yayınlanan exploitation, data-only/ROP kalarak SMEP/SMAP/KPTI'yi tolere eder; AF_CAN'a ulaşmak için unprivileged bir user namespace (CONFIG_USER_NS), bir dmesg/WARN tabanlı KASLR infoleak ve güvenilirlik için race window'unu genişletmek üzere FUSE kullanır.
Detection¶
- KASAN, freed
isotp_socküzerindeisotp_rcv()içindeki use-after-free'i işaretler. - Unprivileged namespace'lerden beklenmedik
AF_CAN/ISOTP socket kullanımı, otomotiv olmayan sistemlerde güçlü bir anomalidir. modprobe_pathintegrity'sini ve beklenmedik modprobe çağrılarını izleyin.
Mitigation¶
- setsockopt/bind sıralamasını düzelten 5.12.3 veya sonrası kernel'e güncelleyin.
- Kullanılmayan yerlerde
CONFIG_CAN_ISOTP=nayarlayın ya dacan-isotpmodülünü blacklist'leyin. - CAN stack'ine unprivileged erişimi kaldırmak için unprivileged user namespace'leri devre dışı bırakın (
kernel.unprivileged_userns_clone=0).