C'yi tampon taşmalarına daha az eğilimli yapmak neden bu kadar zor?


23

Üniversitelerde bir kurs yapıyorum, laboratuarlardan biri bize verdikleri kodda tampon taşması istismarları yapmak. Bu, farklı bir işleve geri dönmek için bir yığında bir işlevin dönüş adresini değiştirmek, basitçe program kayıt / bellek durumunu değiştiren ancak daha sonra çağırdığınız işleve geri dönen kod kadar Çağrdığınız işlev tamamen istismara açık değildir.

Bunun üzerine biraz araştırma yaptım ve bu türden istismarlar Wii'de homebrew çalıştırmak ve iOS 4.3.1 için ayrılmamış jailbreak gibi şeylerde bile hemen hemen her yerde kullanılıyor.

Sorum şu: bu sorunu neden düzeltmek bu kadar zor? Bunun yüzlerce şeyi kırmak için kullanılan büyük bir istismar olduğu açıktır, ancak izin verilen uzunluktaki herhangi bir girişi keserek ve aldığınız tüm girdileri basitleştirerek düzeltmek oldukça kolay görünüyor.

EDIT: Cevaplarını almak istediğim bir başka bakış açısı - neden C yaratıcıları kütüphaneleri yeniden uygulayarak bu sorunları çözmüyorlar?

Yanıtlar:


35

Kütüphaneleri düzelttiler.

Herhangi Modern C standart kütüphanesi daha güvenli varyantı var strcpy, strcat, sprintf, vb.

En Unix'leri olduğunu - - C99 sistemlerde gibi isimlerle bu bulacaksınız strncatve snprintf"n" bir tampon veya kopyalamak için elementlerin maksimum sayıda boyutundaki bir argüman aldığını belirten.

Bu işlevler birçok işlemi daha güvenli bir şekilde ele almak için kullanılabilir, ancak geriye dönük olarak kullanılabilirlikleri iyi değildir. Örneğin, bazı snprintfuygulamalar tamponun boş bırakılmadığını garanti etmemektedir. strncatkopyalamak için birkaç eleman alır, ancak birçok insan yanlışlıkla dest tamponu boyutunu geçer.

Windows'ta, bir çoğunlukla bulur strcat_s, sprintf_s"_s" eki belirten "güvenli". Bunlar da C11'deki C standart kütüphanesine girme yolunu bulmuşlar ve taşma durumunda ne olduğu konusunda daha fazla kontrol sağlıyorlar (örneğin, kesmeye karşı, örneğin).

Birçok satıcı asprintfGNU libc'deki gibi standart dışı alternatifler sunar ve bu da uygun boyutta bir tamponu otomatik olarak tahsis eder.

"Sadece C'yi düzeltir" fikri bir yanlış anlaşılmadır. C'yi tamir etmek sorun değil - ve zaten yapıldı. Sorun, cahil, yorgun veya acele programcılar tarafından yazılmış onlarca yıllık C kodunu ya da güvenliğin nerede olduğu bağlamında güvenliğin önemli olmadığı bağlamlardan alınan kodları düzeltmektir. Standart kütüphanede yapılan hiçbir değişiklik bu kodu düzeltemez, ancak daha yeni derleyiciler ve standart kitaplıklara geçiş çoğu zaman sorunları otomatik olarak tanımlamaya yardımcı olabilir.


11
Problemi dil programcılara değil, programcılara hedeflemek için +1.
Nicol Bolas,

8
@Nicol: "Sorun [programcılar" dır) demek haksız yere indirgemecidir. Sorun şu ki, yıllarca (C) güvenli olmayan kod yazmayı, özellikle "güvenli" tanımımızın herhangi bir dil standardından daha hızlı bir şekilde geliştiği ve bu kodun hala bu alanda olduğu gibi, güvenli koddan daha kolay yazılmasını sağlamıştır. Bunu bir isme indirgemeyi denemek istiyorsanız, sorun "programcılar" değil, "1970-1999 libc" dir.

1
Bu problemleri çözmek için sahip oldukları araçları kullanmak programcıların sorumluluğundadır . Yarım gün ya da öylesine alın ve bu işler için kaynak kodunu biraz okuyun.
Nicol Bolas

1
@Nicol: Potansiyel bir arabellek taşması tespit etmek önemsiz olmakla birlikte, aralarında gerçek bir tehdit olduğundan emin olmak önemsiz değildir ve arabellek taşmışsa ne olması gerektiğine karar vermek daha az önemsizdir. Hata işleme genellikle kabul edilmez / dikkate alınmaz, bir modülün davranışını beklenmedik şekillerde değiştirebileceğinizden bir iyileştirmeyi "hızlı" yapmak mümkün değildir. Bunu az önce milyonlarca dolarlık eski bir kod tabanında yaptık. Ancak bir süre egzersiz yaparken bir miktar zamana (ve Para'ya) mal oldu.
mattnz

4
@NicolBolas: Ne tür bir mağazada çalıştığınızdan emin değilsiniz , ancak üretimde kullanım için C yazdığım en son yer, detaylı tasarım dokümanını değiştirmek, incelemek, kodu değiştirmek, test planını değiştirmek, test planını gözden geçirmek, tamamlamak için gerekli sistem testi, test sonuçlarını gözden geçirme ve sistemi müşterinin sitesinde yeniden sertifikalandırma. Bu, artık bulunmayan bir şirket için yazılmış farklı bir kıtadaki telekom sistemi içindir. Bildiğim kadarıyla, uygun bir teyp sürücüsü bulabilirseniz , kaynak hala okunması gereken bir QIC teyp üzerinde bir RCS arşivindeydi .
TMN

19

C'nin tasarım gereği "hataya açık" olduğunu söylemek gerçekten yanlış değil . Bazı ağır hataların yanı sıra gets, C dili, insanları ilk etapta C'ye çeken birincil özelliği kaybetmeden başka bir şekilde olamaz.

C bir tür "taşınabilir montaj" olarak işlev görecek bir sistem dili olarak tasarlanmıştır. C dilinin önemli bir özelliği, üst seviye dillerin aksine, C kodunun genellikle gerçek makine koduna çok yakın eşleşmesidir. Başka bir deyişle, ++igenellikle sadece bir inctalimattır ve C koduna bakarak işlemcinin çalışma zamanında ne yapacağına dair genel bir fikir edinebilirsiniz.

Ancak, örtülü sınırlar denetimi eklenmesi, programcının istemediği ve istemediği bir sürü ek yükü de ekler. Bu ek yük, her bir dizinin uzunluğunu saklamak için gereken ekstra depolama alanının veya her dizi erişimindeki dizi sınırlarını kontrol etmek için ek talimatların ötesine geçer. İşaretçi aritmetiği ne olacak? Ya da işaretçiyi alan bir işleviniz varsa? Çalışma zamanı ortamı, bu işaretçinin yasal olarak tahsis edilmiş bir bellek bloğunun sınırları dahilinde olup olmadığını bilmenin bir yolu yoktur. Bunu izleyebilmek için, her işaretçiyi şu anda tahsis edilmiş bellek blokları tablosuna göre kontrol edebilecek bazı çalışma zamanı mimarisine ihtiyacınız olacak, bu noktada Java / C # tarzı yönetilen çalışma zamanı alanına zaten giriyoruz.


12
Dürüst olmak gerekirse, insanlar neden C'nin "güvenli" olmadığını sorduğunda, beni derlemenin "güvenli" olmadığını şikayet edip etmediklerini merak ediyor.
Ben Brocka

5
C dili bir Digital Equipment Corporation PDP-11 makinesinde taşınabilir bir montaj gibidir. Aynı zamanda, Burroughs makinelerinin CPU'da dizi sınırlamaları denetimi vardı, bu yüzden programlara giriş yapmak gerçekten çok kolaydı. Rockwell Collins donanımında (çoğunlukla havacılıkta kullanılan) donanım yaşamlarında dizi kontrolleri yapıldı.
Tim Williscroft

15

Eğer kullanıyorsanız: Ben gerçek bir sorun böcek bu tür düzeltme zor olduğunu, ancak bu kadar kolay hale getirmek olduğunu olmadığını düşünüyorum strcpy, sprintfve (görünüşte) basit şekilde arkadaş kutu çalışması olduğunu, sonra muhtemelen ettik tampon taşması için kapıyı açtı. Ve hiç kimse birileri sömürmedene kadar farketmeyecektir (çok iyi kod incelemeleriniz yoksa). Şimdi, birçok vasat programcı olduğu ve çoğu zaman zaman baskısı altında oldukları gerçeğini de ekleyin - ve arabellek taşmalarıyla çok fazla kod çözülmüş kod için bir reçeteniz var, çünkü hepsini düzeltmek zor olacak çünkü birçoğu ve çok iyi saklanıyorlar.


3
Gerçekten "çok iyi kod incelemelerine" ihtiyacınız yok. Sadece sprintf'i yasaklamanız veya sprintf'i sizeof () ve bir işaretçinin boyutundaki hataları vb. Kullanan bir şey için yeniden tanımlamanız gerekir. kanca ve grep.

1
JoeWreschnig: sizeof(ptr)genel olarak 4 veya 8'dir. Bu da bir başka C sınırlaması: bir göstergenin uzunluğunu belirlemenin bir yolu yok.
MSalters

@ MSalters: Evet, bir int [1] veya char [4] dizisi veya yanlış bir pozitif olan ne olabilir, fakat pratikte bu boyuttaki tamponları asla bu işlevlerle işlemezsiniz. (Ben burada teorik olarak konuşmuyorum - bu yaklaşımı kullanan dört yıl boyunca büyük bir C kod tabanı üzerinde çalıştım. Asla bir karaktere sprintfing sınırlandırmasına asla çarpmadım [4].)

5
@ BlackJack: Çoğu programcı aptalca değildir - eğer boyutları geçmeye zorlarsanız, doğru olanı geçerler. Sadece çoğu, zorunlu olmadıkça boyutu geçmeyecek. Statik veya otomatik boyutta bir dizinin uzunluğunu döndürecek bir makro, ancak bir işaretçi verildiğinde hataları yazacak bir makro yazabilirsiniz. O zaman # sprintf'yi, bu makroyu kullanarak snprintf'yi çağırmak için yeniden tanımlayın. Artık yalnızca bilinen boyutlardaki dizilerde çalışan ve programlayıcıyı snprintf'i elle belirtilen boyutta çağırmaya zorlayan bir sprint sürümü var.

1
Böyle bir makronun basit bir örneği, #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / (sizeof(a) != sizeof(void *))derleme zamanı sıfıra bölme tetikleyecektir. Chromium'da ilk gördüğüm bir başka zeki #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]) / !(sizeof(a) % sizeof((a)[0]))olan da, bazı yanlış negatifler için bir avuç yanlış pozitif alıp satıyor. Maalesef char için faydasız. Bunu daha da güvenilir hale getirmek için çeşitli derleyici uzantılarını kullanabilirsiniz, örneğin blogs.msdn.com/b/ce_base/archive/2007/05/08/… .

7

Arabellek taşmalarını düzeltmek zordur, çünkü C sorunu çözmek için neredeyse hiçbir kullanışlı araç sağlamamaktadır. Bu yerli tamponlar hiçbir koruma sağladığını temel bir dil kusur ve C ++ ile yapmış gibi, üstün bir ürün ile değiştirmek için, neredeyse tamamen olmasa bile, imkansız std::vectorve std::array, ve o taşmaları tampon bulmak için bile hata ayıklama modunda altında zor.


13
"Dil kusuru" oldukça taraflı bir iddiadır. Kütüphanelerin sınır kontrolü sağlamadığı bir kusurdu; dilin ek yükü önlemek için bilinçli bir seçim olmadığı. Bu seçim, üst düzey yapıların std::vectorverimli bir şekilde uygulanmasını sağlayan şeyin bir parçasıdır . Ve vector::operator[]güvenlik üzerindeki hız için aynı seçimi yapar. Güvenlik, vectormodern C kütüphanelerinin kullandığı aynı yaklaşıma sahip büyüklükte araba taşımayı kolaylaştırmaktan geliyor.

1
@Charles: "C yalnızca standart kütüphanenin bir parçası olarak herhangi bir dinamik olarak genişleyen arabellek sağlamaz." Hayır, bunun bununla ilgisi yok. İlk olarak, C bunları reallocsağlar (C99 ayrıca çalışma zamanı tarafından belirlenen ancak sabit bir boyut kullanarak yığın dizilerini neredeyse her zaman tercih edilen bir otomatik değişken aracılığıyla boyutlandırmanıza da izin verir char buf[1024]). İkincisi, sorunun genişleyen arabelleklerle bir ilgisi yoktur, arabelleklerin yanlarında boyut taşıyabilip taşımayacağı ve bunlara eriştiğinizde bu büyüklüğü kontrol etmekle ilgisi vardır.

5
@Joe: Sorun, yerel dizilerin kırılması kadar önemli değil. Değiştirmeleri imkansızdır. Başlangıç ​​için, vector::operator[]hata ayıklama modunda sınır denetimi yapıyor - yerel dizilerin yapamadığı bir şey var - ve ikincisi, C'nin yerel dizi türünü sınır denetimi yapabilen biriyle değiştirmesi mümkün değil , çünkü şablon yok ve operatör yok aşırı. C ++ ' T[]dan std::array,' dan ' ya geçmek istiyorsanız, pratikte sadece typedef'ini değiştirebilirsiniz. C'de, bunu başarmanın bir yolu yoktur ve tek başına bir arayüze eşdeğer işlevselliğe sahip bir sınıf yazmanın bir yolu yoktur.
DeadMG

3
@Joe: Asla statik olarak boyutlandırılamaz ve onu asla genel hale getiremezsiniz. C std::vector<T>ve std::array<T, N>C ++ ile aynı rolü üstlenen herhangi bir kütüphane yazmak mümkün değildir . Bunu yapabilen herhangi bir kütüphaneyi, hatta Standart bir kütüphaneyi tasarlamak ve belirlemek mümkün olmazdı.
DeadMG

1
Ne demek istediğinizi "asla statik olarak boyutlandırılamaz" diye emin değilim. Bu terimi kullandığım gibi, std::vectorasla statik olarak da ölçülemez. Jenerik olarak, iyi C'nin gerektirdiği kadar jenerik yapabilirsiniz - boşluğa az miktarda temel işlem * (ekle, kaldır, yeniden boyutlandır) ve özel olarak yazılmış her şey. C'nin C ++ tarzı jenerik olmadığından şikayet edecekseniz, güvenli tampon kullanımı kapsamı dışındadır.

7

Sorun C dilinde değil .

IMO, üstesinden gelinmesi gereken tek büyük engel, C'nin kötü bir şekilde öğretildiği sadece düz olmasıdır . Her yeni nesil programcının zihnini baştan beri zehirleyen, onlarca yıllık kötü uygulama ve yanlış bilgi referans el kitaplarında ve ders notlarında kurumsallaştırılmıştır. Öğrencilere, gets1 veya scanfdaha sonra gibi "kolay" G / Ç işlevlerinin kısa bir açıklaması verilir ve sonra kendi aygıtlarına bırakılır. Bu araçların nerede veya nasıl başarısız olabileceği veya bu arızaların nasıl önlenebileceği söylenmedi. Kullanma hakkında söylenmedi fgetsvestrtol/strtodÇünkü bunlar "gelişmiş" araçlar olarak kabul edilir. O zaman profesyonel dünyaya tahribatlarını engellemek için serbest bırakıldılar. Daha tecrübeli programcıların çoğunun daha iyisini bilmesi değil, çünkü aynı beyin hasarı eğitimi almışlar. Çıldırtıcı. Burada ve Stack Overflow'ta ve soruyu soran kişinin ne hakkında konuştuğunu bilmeyen bir kişi tarafından öğretildiği ve tabii ki söyleyemeyeceğiniz açık olan diğer sitelerde çok fazla soru görüyorum. o bir Profesör ve sen sadece çünkü "profesörün, yanlıştır" biraz adam internette.

Ve sonra, "iyi, standart diline göre ..." ile başlayan her cevabı küçümseyen kalabalığa sahipsin çünkü gerçek dünyada çalışıyorlar ve onlara göre standart gerçek dünya için geçerli değil . Sadece kötü bir eğitim almış biriyle başa çıkabilirim, ama cahil olmakta ısrar eden herkes sektörde bir yanıklık.

Dil güvenli kod yazmaya önem verilerek doğru bir şekilde öğretilirse arabellek taşması sorunu olmaz . Bu "zor" değil, "gelişmiş" değil, sadece dikkatli olmak.

Evet, bu bir rant oldu.


1 Neyse ki, nihayetinde, 40 yıl boyunca sürecek olan eski kurallarda sonsuza dek gizlenecek olmasına rağmen, nihayetinde dil belirtiminden yararlanılmıştır.


1
Sizinle çoğunlukla aynı fikirdeyken, hala haksızlık ettiğinizi düşünüyorum. "Güvenli" olarak kabul ettiğimiz şey aynı zamanda zamanın bir işlevidir (ve benden çok daha uzun bir süredir profesyonel bir yazılım geliştirici olduğunuzu görüyorum, bu yüzden buna aşina olduğunuzdan eminim). Bundan on yıl sonra, 2012'de herkesin neden DoS-etkin hash masa uygulamaları kullandığı hakkında da aynı konuşmayı yapacaksınız, güvenlik hakkında bir şey bilmiyor muyduk? Öğretimde bir sorun varsa, "en iyi" uygulamanın öğretilmesine çok fazla odaklanmamız ve en iyi uygulamanın kendisinin evrimleştirmemesi sorunudur.

1
Ve dürüst olalım. Sen olabilir sadece güvenli kod yazmak sprintf, ama bu dil kusurlu değildi anlamına gelmez. C edildi kusurlu ve bir kusurlu - herhangi bir dil gibi - ve biz bunları düzeltmek için devam edebilir bu yüzden bu kusurları itiraf önemlidir.

@JoeWreschnig - Daha büyük noktaya katılıyorum, ancak DoS mümkün hash tablo uygulamaları ile arabellek taşmaları arasında niteliksel bir fark olduğunu düşünüyorum. İlki, çevrenizde gelişen koşullara bağlanabilir, ancak ikincisinin mazereti yoktur; arabellek taşması kodlama hataları, periyod. Evet, C'nin bıçak muhafızları yoktur ve dikkatsizseniz sizi keser; Bunun dilde bir kusur olup olmadığını tartışabiliriz. Bu, çok az sayıda öğrenciye dili öğrenirken herhangi bir güvenlik talimatı verilmesi gerçeğine diktir .
John Bode,

5

Sorun, programcı beceriksizliğinden ziyade yönetimsel yetersizliklerden bir tanesidir. Unutmayın, 90.000 hatlı bir uygulamanın tamamen güvensiz olması için yalnızca bir güvensiz işlem yapması gerekir . Temel olarak güvensiz dize işleminin üzerine yazılan herhangi bir uygulamanın% 100 mükemmel olması ihtimalinin ötesine geçiyor - bu güvensiz olacağı anlamına geliyor.

Sorun, güvensiz olma maliyetlerinin ya doğru muhatapa (ya da uygulamayı satan şirketin neredeyse hiç satın alma fiyatını geri ödemesi gerekmeyeceği) tahsil edilmemesi ya da kararlar alındığında açıkça görülmemesidir ("Göndermek zorundayız" dır. Mart ayında ne olursa olsun! "). Şirketinizin kâr etmesinden ziyade kullanıcılarınıza uzun vadeli maliyetleri ve maliyetleri hesaba kattıysanız, C veya ilgili dillerde yazmanın çok daha pahalı olacağından, muhtemelen o kadar pahalı olacağından eminim: Günümüzde geleneksel bilgeliğin bir zorunluluk olduğunu söylediği alanlar. Ancak bu, sektörde kimsenin istemediği çok daha katı bir yazılım yükümlülüğü getirilmedikçe değişmeyecektir.


-1: Tüm kötülüklerin kökü olarak suç yönetimi, özellikle yapıcı değil. Tarihi görmezden gelmek biraz daha az. Cevap neredeyse son cümleye itiraz edildi.
mattnz

Daha sıkı yazılım sorumluluğu, güvenlikle ilgilenen ve bunun için para ödemek isteyen kullanıcılar tarafından getirilebilir. Muhtemelen, güvenlik ihlalleri için ağır cezalar verilebilir. Piyasaya dayalı bir çözüm, kullanıcılar güvenlik için para ödemeye istekli olurlarsa işe yararlar, ancak değildir.
David Thornley

4

C kullanmanın en büyük güçlerinden biri, hafızayı uygun gördüğünüz şekilde değiştirmenize olanak sağlamasıdır.

C kullanmanın en büyük zayıf yönlerinden biri, hafızayı uygun gördüğünüz şekilde değiştirmenizi sağlamasıdır.

Güvenli olmayan işlevlerin güvenli sürümleri vardır. Bununla birlikte, programcılar ve derleyici kullanımlarını kesinlikle zorlamazlar.


2

C yaratıcıları neden kütüphaneleri yeniden uygulayarak bu sorunları çözmüyorlar?

Muhtemelen C ++ zaten bunu yaptı ve C kodu ile geriye doğru uyumlu olduğu için. Bu nedenle, C kodunuzda güvenli bir dize türü istiyorsanız, sadece std :: string kullanın ve bir C ++ derleyicisi kullanarak C kodunuzu yazın.

Altta yatan bellek alt sistemi, koruyucu bloklar ve bunların geçerlilik kontrolleri getirerek arabellek taşmalarını önlemeye yardımcı olabilir - böylece tüm tahsislerde 4 baytlık “fefefe” eklenir, bu bloklar yazıldığında, sistem bir yalpalayıcı atar. Hafızanın yazılmasını engelleme garantisi yoktur, ancak bir şeylerin ters gittiğini ve düzeltilmesi gerektiğini gösterecektir.

Bence sorun eski strcpy etc rutinlerinin hala mevcut olmasıdır. Eğer strncpy vs. lehine çıkarıldılarsa o zaman yardımı olurdu.


1
Strcpy vs.'nin tamamen kaldırılması, artan yükseltme yollarını daha da zorlaştıracak ve bu da insanların hiç terfi etmemelerine neden olacaktır. Şimdi yapılması gerekenler, bir C11 derleyicisine geçebilir, daha sonra _s varyantlarını kullanmaya başlayabilir, daha sonra _s olmayan varyantları yasaklayabilir, daha sonra ne kadar süre geçerli olursa olsun mevcut kullanımı düzeltebilirsiniz.

-2

Taşma sorununun neden çözülmediğini anlamak kolaydır. Birkaç alanda C hatalıydı. O zaman bu kusurlar tolere edilebilir ve hatta bir özellik olarak görülüyordu. Şimdi onlarca yıl sonra bu kusurlar düzeltilemez.

Programlama topluluğunun bazı kısımları bu deliklerin tıkalı olmasını istemiyor. Sadece dizgilerden, dizilerden, işaretçilerden, çöp toplanmasından başlayan tüm alev savaşlarına bakın.


5
Lol, korkunç ve yanlış başlı cevap.
Heath Hunnicutt

1
Bunun niçin kötü bir cevap olduğunun açıklanması: C'nin aslında birçok kusuru var, ancak arabellek taşmalarına izin vermek vb. Onlarla, ancak temel dil gereksinimleriyle çok az ilgisi var. C'nin işini yapmak için bir dil tasarlamak ve arabellek taşmalarına izin vermemek mümkün olmazdı. Topluluğun bazı kısımları, C'nin çoğunlukla iyi bir sebeple izin verdiği yeteneklerden vazgeçmek istememektedir. Ayrıca, bu dillerden bazılarının nasıl önlenebileceği konusunda anlaşmazlıklar var, bu da programlama dili tasarımını tam olarak anlamadığımızı gösteriyor, başka bir şey değil.
David Thornley,

1
@DavidThornley: Birisi C'nin işini yapacak bir dil tasarlayabilir, ancak bunu yapmanın normal deyimsel yollarının en azından bir derleyicinin bunu yapmayı seçmesi halinde arabellek taşmalarını makul bir şekilde kontrol etmesine izin vermesini sağlayabilir . memcpy()Bir dizi segmenti etkin bir şekilde kopyalamak için standart olan ve kullanılabilir olması ile arasında çok büyük bir fark vardır .
supercat
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.