1 puan yazan GN⁺ 2024-04-05 | 1 yorum | WhatsApp'ta paylaş
  • GCC 14’ün -fanalyzer seçeneği, C kodunun birden çok yürütme yolunu sembolik yürütme ile izleyerek, çalışma zamanından önce yakalanması zor kusurları derleme aşamasında bulmaya odaklanır
  • Bu iyileştirmeler sonsuz döngü tespiti, buffer overflow görselleştirmesi, C string izleme ve taint analizinin varsayılan olarak etkinleştirilmesi başlıklarına ayrılıyor; odak noktası tanı çıktılarının daha anlaşılır hale getirilmesi
  • -Wanalyzer-out-of-bounds, basit bir uyarının ötesine geçerek buffer ile yazma konumu arasındaki ilişkiyi metin diyagramları ile gösteriyor ve UTF-8 stringlerde hangi karakterin ortasında taşma olduğunu da belirtiyor
  • Yeni null_terminated_string_arg(PARAM_IDX) özniteliği, NUL sonlandırılmış string bekleyen API koşullarını analizöre ve kodu okuyanlara bildirerek NUL ile sonlandırılmamış buffer aktarım yollarını daha görünür hale getiriyor
  • Taint analizi, -fanalyzer seçildiğinde varsayılan olarak açılıyor; böylece saldırgan kontrollü değerlerin üst sınır kontrolü olmadan boyut, indeks, ofset gibi alanlarda kullanıldığı akışlar daha kolay görülebiliyor

-fanalyzerın ele aldığı problem kapsamı

  • -fanalyzer, GCC’nin statik analiz geçişidir; C kaynak kodunun birden çok yürütme yolunu sembolik yürütme ile takip ederek derleme zamanında kusur bulmayı amaçlar
  • GCC 14’teki iyileştirmeler, 2024 Nisan’daki resmî sürüm öncesinde derlendi; yazının kaleme alındığı sırada GCC 14.1 sürümünün 2024 Nisan içinde gelmesi bekleniyordu
  • GCC 14.0 prerelease sürümü Fedora 40 Beta içinde zaten kullanılıyordu
  • Örnekler, Compiler Explorer üzerinde yeni derleyici seçenekleriyle denenebilir

Basit sonsuz döngü tespiti

  • GCC 14’e yeni uyarı -Wanalyzer-infinite-loop eklendi
  • Örnek kodda iç içe geçmiş for döngülerinde ikinci döngünün koşulu j < n iken artırma ifadesi yanlışlıkla i++ olarak kalıyor
    • Bu, ilk for döngüsü kopyalandıktan sonra artırma ifadesindeki i’nin j ile değiştirilmemesi hatasına karşılık geliyor
    • Analizör bu yolda j < n koşulunun true dalını izlemeyi sürdürerek döngü tekrarını uyarı olarak veriyor
  • Mevcut tanı çıktısında akışı anlamak için olay numaralarını (1) ile (5) arasında sırayla okumak gerekiyor
  • GCC 15 için, kontrol akışı yolunu vurgulayan ASCII art benzeri bir gösterimle okunabilirliği artırma fikri istek listesinde duruyor
  • İlgili kod Compiler Explorer örneğinde denenebilir

Buffer overflow’un metinle görselleştirilmesi

  • GCC 13 ile analizör -Wanalyzer-out-of-bounds üzerinden sınır denetimini desteklemeye başlamıştı
  • GCC 14’te tahmin edilen buffer overflow’un uzamsal ilişkisi metin tabanlı diyagram olarak gösterilebiliyor
  • char buf[10] üzerinde strcpy(buf, "hello") yapıldıktan sonra strcat(buf, " world!") çağrılan örnekte stack tabanlı buffer overflow tespit ediliyor
    • Önceki mesaj, buf kapasitesinin 10 bayt olduğunu ve 10. bayttan 12. bayta kadar sınır dışı yazma gerçekleştiğini belirtiyordu
    • GCC 14’ün diyagramı, strcpy tarafından doldurulan hedef buffer ile strcat için başlangıç noktası olan mevcut sonlandırıcı NUL baytını birlikte gösteriyor
  • ASCII dışı string örneklerinde de UTF-8 gösterimi izlenerek overflow konumu belirtiliyor
    • char buf[11] içine "サツキ" kopyalandıktan sonra "メイ" eklenen kod örnek olarak kullanılıyor
    • Diyagram, overflow’un karakterinin, yani U+30E1’in ortasında gerçekleştiğini gösteriyor
  • İlgili kodlar ASCII string örneği ve ASCII dışı string örneği üzerinden görülebilir

C string işlemlerinin izlenmesinin güçlendirilmesi

  • GCC 14 analizörü, C string işlemlerini izlemeyi geliştirerek NUL sonlandırıcı baytı ararken buffer tarayan API’leri simüle ediyor
  • Pointer, NUL ile sonlandırılmamış bir buffer gösterdiği halde ilgili API’ye iletilen yollar için uyarı veriyor
  • Yeni fonksiyon özniteliği null_terminated_string_arg(PARAM_IDX), belirli bir parametrenin NUL sonlandırılmış string olması gerektiğini analizöre ve kod okuyucularına bildiriyor
  • Örnekte example_fn(const char *p) fonksiyonuna null_terminated_string_arg(1) ve nonnull öznitelikleri eklenmiş durumda
    • char str[3] = "abc"; ifadesinde NUL sonlandırıcı bayt için yer yok
    • example_fn(str) çağrısında analizör, str sonrasından 1 bayt okuyan stack tabanlı buffer over-read uyarısı veriyor
    • Tanı çıktısında ayrıca example_fn fonksiyonunun 1. argümanının NUL sonlandırılmış string pointer olması gerektiğini belirten bir note da yer alıyor
  • Bu örnek Compiler Explorer üzerinde çalıştırılabilir

Taint analizinin varsayılan olarak etkinleştirilmesi

CVE-2011-2210 üzerinden bir kernel analiz örneği

  • Linux kernelindeki CVE-2011-2210 alıntı örneği, taint analizi vakası olarak kullanılıyor
  • __SYSCALL_DEFINEx makrosuna __attribute__((tainted_args)) eklenerek osf_getsysinfo argümanlarının güven sınırını aşarak geldiği ve tainted değerler olarak ele alınması gerektiği analizöre bildiriliyor
  • GCC 14 analizörü, copy_to_user(buffer, hwrpb, nbytes) çağrısında saldırgan kontrollü nbytes değerinin üst sınır denetimi olmadan boyut olarak kullanıldığını uyarıyor
    • Tanı çıktısı, nbytes değerinin if (nbytes < sizeof(*hwrpb)) içinde yalnızca alt sınır kontrolünden geçtiğini gösteriyor
    • copy_to_user fonksiyonunun üçüncü parametresi, access(write_only, 1, 3) özniteliğiyle boyut parametresi olarak işaretleniyor
  • Sorun, arındırma koşulunun if (nbytes < sizeof(*hwrpb)) şeklinde yazılmış olması
  • Kernel üzerinde analizörü çalıştırarak zafiyet bulma ve analizördeki false positive’leri düzeltme çalışmaları sürüyor

1 yorum

 
GN⁺ 2024-04-05
Hacker News yorumları
  • Bana göre fanalyzer, Clang’e kıyasla GCC’nin killer özelliklerinden biri. Hataları açıklaması C programlamayı çok daha kolaylaştırıyor; hata mesajlarının geliştirici dostu olması açısından da Rust’a benzemeye başladığını hissettiriyor.

    • Rust’ın özellikle HN’de bellek güvenliği ve iyi soyutlamalarıyla büyük ilgi gördüğünü biliyorum, ama Rust’ın popülerliğinin ne kadarının hata mesajları sayesinde olduğunu merak ediyorum.
      Teknik bir konuyu öğrenmeyi bırakmanın en büyük nedeni çoğu zaman sinir bozucu ya da belirsiz hatalar oluyor. Konuyu biraz dağıttım ama demek istediğim, C’yi seviyorum ve Rust seviyesinde hata mesajları olursa daha da iyi olurdu.
    • Clang’de de benzer bir araç, Clang Static Analyzer, var: https://clang-analyzer.llvm.org/
    • Ben tam tersini deneyimledim. Clang, GCC’den çok daha iyi hata mesajlarını sık sık gösteriyor; bazı uyarı ya da hata implementasyonları daha fazla durumu yakalıyor ve clang-tidy çok daha iyi statik analiz yapıyor.
    • C++’tan bu kadar nefret etmemin nedenlerinden biri aklıma geldi. error: missing semicolon yerine şablon örneklemesi ile ilgili 1000 satırdan fazla hata mesajı yağardı.
    • Bu oldukça şaşırtıcı geliyor. GCC analizörünün, Clang statik analizörünün zaten raporlamadığı neyi daha yakaladığını merak ediyorum.
      GCC analizörünü birkaç kez kullandım ama çıktıyı okunabilir hâle getiren iyi bir frontend bulamadım. Clang tarafında oldukça iyi HTML çıktısı, CodeChecker, Xcode entegrasyonu gibi çeşitli seçenekler var; GCC tarafındaki çıktıyı nasıl okuduğunuzu merak ediyorum. Üstelik GCC, Clang’den çok daha fazla yanlış pozitif üretiyor gibi görünüyor.
  • Başka bir başlıkta 36 yorum daha var: https://news.ycombinator.com/item?id=39918278
    “GCC 14 Boasts Nice ASCII Art for Visualizing Buffer Overflows (phoronix.com)”, 2 saat önce yazılmış.

  • Birkaç ay önce küçük bir Linux yardımcı programı yaptım. Herhangi bir çalıştırılabilir dosyanın yerine geçen drop-in bir shim’di; çağrıldığında asıl programmış gibi davranıp orijinali fork ediyor ve stdout/stderr’e bağlıyordu.
    Hata çıktısını, o programın bağlamını bilen özel bir GPT asistanına gönderiyordu; asistan da orijinal hatayı insanın okuyabileceği bir forma dönüştürüp shim’in stderr’ine yazıyordu. GCC/Clang’in concept/template ile ilgili iç içe geçmiş derleyici dökümlerini görmekten bıktığım için kullanıyordum, ama isterseniz herhangi bir programla kullanılabilirdi. İyi çalışıyordu ama ciddi şekilde hastalandığım için üzerinde daha fazla çalışamadım; birinin bunu yeniden düzgünce yapıp genelleştirmesi iyi bir proje olabilir.

  • Analiz sonuçları için daha iyi çıktı biçimleri olsa iyi olurdu. Mevcut yöntem ekran okuyucular için tam bir cehennem.

    • Bilgi olsun, GCC 13’te SARIF çıktısı implemente ettim; örneğin VS Code’da bir eklentiyle görüntülenebiliyor. Ancak ASCII art dahil değil.
      Çıktı örneğini burada görebilirsiniz: https://godbolt.org/z/aan6Kfxds
      Yazıdaki ilk örneğe -fdiagnostics-format=sarif-stderr komut satırı seçeneğinin eklenmiş hâli. Diyagramları SVG olarak çıktı alma denemeleri de yaptım ama GCC 14’e koyacak kadar cilalayamadım.
  • Orijinal kod, nbytes < sizeof(*hwrpb) ise -1 döndürüyor, copy_to_user(buffer, hwrpb, nbytes) başarısız olursa -2 döndürüyordu. Uygulanan düzeltme nbytes > sizeof(*hwrpb) kontrolüydü, ama bence doğru düzeltme copy_to_user(buffer, hwrpb, sizeof(*hwrpb)).
    hwrpb işaretçisinden sizeof(*hwrpb) dışında bir boyut kopyalayıp çıkarmak mantıklı değil.

    • Çağıran taraf nbytes = 4 geçirir ve sizeof(hwrpb) 16 baytsa, çağıran tarafın tamponundan 12 bayt fazla kopyalayarak sahip olunmayan belleği okuyabilir. Bence bundan kaçınmak gerekir.
      Daha iyi çözüm, çağıran ve çağrılan tarafın ikisinin de desteklediği minimum baytı kopyalamaktır. Örneğin nbytes = MIN(nbytes, sizeof(hwrpb)); gibi. hwrpb->size içindeki sürüm bilgisinin korunduğu varsayımıyla, yapının bir kısmı başlatılmamış olsa bile geriye/ileriye dönük uyumluluk sağlanabilir.
    • Doğru. Ama tampon boyutu verildiğine göre çağıran tarafın tamponunun sonunu aşacak şekilde yazmak da mantıklı değil; dolayısıyla nbytes’ten uzun bir değer de geçilemez.
  • Gerçekten harika. Harcanan emek muazzam görünüyor. Zorluk seviyesi, standart kütüphaneye ve C standardına fat pointer/dizi view eklemeye benzer düzeyde görünüyor.

  • -Wstringop-overflow çok fazla yanlış pozitif verdiği için ilk kapattığım uyarı. Analizör varyantının daha iyi olacağından şüpheliyim.

    • Bu, sürekli ötüp baş ağrısı yapıyor ve uykunu getiriyor diye karbonmonoksit dedektörünün pilini çıkarmaya benzemiyor mu?
  • Çok güzel. Bu aralar pek C geliştirmesi yapmıyorum; strcpy ve strcat ne kadar sık kullanılıyor merak ediyorum. Son baktığımda, goto kadar tabu sayılan şeylere yakındılar.
    Elbette çekirdek geliştirmede gotonun sıkça tercih edildiğini biliyorum. C string analizinin pratikte ne kadar fayda sağladığını merak ediyorum.

    • Bazı bağlamlarda goto kullanımı açıkça doğru ve zariftir. Ne pahasına olursa olsun kaçınmaya çalışmak, bakımı zor, çirkin ve dolambaçlı koda yol açabilir. Yaygın olmasa da geçerli kullanım alanları var.
      strcpy gibi işlevler daha az önerilir; ama daha güçlü değişmezlerin, örneğin dil düzeyindeki değişmezlerin bozulmadığı sürece doğru olduğu garanti edilen durumlar da vardır. Böyle bir durum bozulduysa zaten çok daha büyük bir sorun var demektir. Nadir durumlarda, sözde daha güvenli alternatifin hiçbir kazanç sağlamadan biraz daha az verimli olabileceği de savunulabilir.
    • C’de bulunmayan yapısal programlama yapılarıyla mantıksal olarak eşdeğer şekilde kullanıldığı sürece, bazı goto kullanımları C’de hâlâ idiomatiktir. Dikkatli olmak gerekir ama sonuçta C, böyle şeyler olabiliyor.
      Ancak longjmp’i hiç sevmiyorum.
    • Basit goto kullanımında sorun yok. Buna karşılık strxcpy ailesi tamamen berbat ve hiçbir gerekçeyle kullanılmamalı. Çekirdekte kullanılıyor olmaları korkunç.
      O işlevler ve onları “düzeltmeye” yönelik tüm başarısız girişimler yörüngeden imha edilmeliydi.
    • goto dikkatli kullanılırsa sorun değil. strcpy ve strcat da kodun doğru olduğunu biliyorsanız ve yanlışsa büyük bir sorun çıkacağı anlamında “sorun değil”. Ne yazık ki bu açıklama C’nin büyük kısmı için geçerli.
    • gotonun strcat, strcpy kadar tabu olduğunu düşünmüyorum. goto gayet iyi; aynı kapsamda doğru boyutta malloc olmadan kullanılan strcat ve strcpy ise daha çok kod kokusu gibi.
  • Çok iyi. Neyin yanlış olduğunu açıklayan ayrıntılı raporların hepsinin eklenmiş olması sevindirici.