Bu kod neden arabellek taşması saldırılarına karşı savunmasız?


148
int func(char* str)
{
   char buffer[100];
   unsigned short len = strlen(str);

   if(len >= 100)
   {
        return (-1);
   }

   strncpy(buffer,str,strlen(str));
   return 0;
}

Bu kod bir arabellek taşması saldırısına karşı savunmasız ve ben nedenini anlamaya çalışıyorum. Bunun yerine bir lenilan edilmesi ile ilgili olduğunu düşünüyorum , ama gerçekten emin değilim.shortint

Herhangi bir fikir?


3
Bu kodla ilgili birden fazla sorun var. C dizelerinin boş sonlandığını hatırlayın.
Dmitri Chubarov

4
@DmitriChubarov, dize sonlandırma null değil sadece dize çağrıldıktan sonra kullanılırsa bir sorun olacaktır strncpy. Bu durumda değil.
R Sahu

43
Bu koddaki problemler doğrudan strlenhesaplanan, geçerlilik kontrolü için kullanılan ve daha sonra tekrar saçma olarak hesaplanan gerçeğinden akar - bu bir KURU başarısızlıktır. İkincisi strlen(str)ile değiştirilirse len, tipine bakılmaksızın tampon taşması olasılığı olmazdı len. Cevaplar bu noktaya değinmiyor, sadece bundan kaçınmayı başarıyorlar.
Jim Balter

3
@CiaPan: Boş sonlandırılmamış bir dize geçiren Wenn, strlen tanımsız davranış gösterecektir.
Kaiserludi

3
@JimBalter Nah, sanırım onları orada bırakacağım. Belki bir başkası aynı ahmak yanılgısına sahip olacak ve ondan öğrenecektir. Sizi rahatsız ederlerse onları işaretlemekten çekinmeyin, biri gelip silebilir.
Asad Saeeduddin

Yanıtlar:


192

Çoğu derleyicide maksimum değeri unsigned short65535'tir.

Yukarıdaki herhangi bir değer sarılır, böylece 65536 0 olur ve 65600 65 olur.

Bu, doğru uzunlukta uzun dizelerin (örn. 65600) denetimi geçeceği ve arabelleğin taşacağı anlamına gelir.


Kullanım size_tsonucunu depolamak için strlen()değil, unsigned shortve karşılaştırma lenile doğrudan boyutunu kodlayan bir ifade için buffer. Yani mesela:

char buffer[100];
size_t len = strlen(str);
if (len >= sizeof(buffer) / sizeof(buffer[0]))  return -1;
memcpy(buffer, str, len + 1);

2
@PatrickRoberts Teorik olarak, evet. Ancak, kodun% 10'unun çalışma zamanının% 90'ından sorumlu olduğunu unutmayın, bu nedenle performansın güvenlikten önce gitmesine izin vermemelisiniz. Ve zaman içinde kodun değiştiğini unutmayın, bu da aniden bir önceki kontrolün bittiğini gösterebilir.
orlp

3
Arabellek taşmasını önlemek için lenstrncpy'nin üçüncü argümanı olarak kullanın . Yine strlen kullanmak her durumda aptal.
Jim Balter

15
/ sizeof(buffer[0])- sizeof(char)C'de her zaman 1 (bir karakter bir gazilyon bit içeriyor olsa bile) olduğunu unutmayın, bu nedenle farklı bir veri türü kullanma olasılığı olmadığında bu gereksizdir. Yine de ... tam bir cevap için kudos (ve yorumlara cevap verdiğiniz için teşekkürler).
Jim Balter

3
@ rr-: char[]ve char*aynı şey değildir. A'nın dolaylı olarak a'ya dönüştürüleceği birçok durum vardır . Örneğin, işlev bağımsız değişkenleri için kullanılan türle tamamen aynıdır . Ancak, dönüşüm gerçekleşmez . char[]char*char[]char*sizeof()
Dietrich Epp

4
@Controll Çünkü bir buffernoktada boyutunu değiştirirseniz ifade otomatik olarak güncellenir. Bu, güvenlik açısından kritik öneme sahiptir, çünkü bildirimi, buffergerçek koddaki check-in'den oldukça uzak olabilir. Bu nedenle, arabellek boyutunu değiştirmek kolaydır, ancak boyutun kullanıldığı her konumda güncellemeyi unutmayın.
orlp

28

Sorun burada:

strncpy(buffer,str,strlen(str));
                   ^^^^^^^^^^^

Dize hedef arabelleğin uzunluğundan büyükse, strncpy yine de kopyalar. Dizenin karakter sayısını, arabellek boyutu yerine kopyalanacak sayı olarak dayandırıyorsunuz. Bunu yapmanın doğru yolu aşağıdaki gibidir:

strncpy(buffer,str, sizeof(buff) - 1);
buffer[sizeof(buff) - 1] = '\0';

Bunun yaptığı, boş sonlandırma karakteri için tampon eksi gerçek boyutuna kopyalanan veri miktarını sınırlamaktır. Sonra arabellekteki son baytı null karaktere ek bir koruma olarak ayarladık. Bunun nedeni, strrn (str) <len - 1 ise strncpy sonlandırma null değeri de dahil olmak üzere n bayta kadar kopyalayacaktır. Değilse, null kopyalanmaz ve çökme senaryosuna sahip olursunuz çünkü artık ara belleğinizde sonlanmamış dize.

Bu yardımcı olur umarım.

DÜZENLEME: Daha fazla inceleme ve diğer girdilerden sonra, işlev için olası bir kodlama aşağıdaki gibidir:

int func (char *str)
  {
    char buffer[100];
    unsigned short size = sizeof(buffer);
    unsigned short len = strlen(str);

    if (len > size - 1) return(-1);
    memcpy(buffer, str, len + 1);
    buffer[size - 1] = '\0';
    return(0);
  }

Dizenin uzunluğunu zaten bildiğimizden, dizeyi str tarafından arabelleğe alınan konumdan kopyalamak için memcpy'yi kullanabiliriz. Strlen (3) için manuel sayfada (FreeBSD 9.3 sisteminde) aşağıdakilerin belirtildiğine dikkat edin:

 The strlen() function returns the number of characters that precede the
 terminating NUL character.  The strnlen() function returns either the
 same result as strlen() or maxlen, whichever is smaller.

Hangi dize uzunluğu null içermediği için yorumlamak. Bu yüzden len + 1 baytları null değerini içerecek şekilde kopyalarım ve test, <tampon boyutu - 2'nin uzunluğundan emin olmak için kontrol eder. null için.

DÜZENLEME: Görünüyor ki, erişim 0 ile başlarken bir şeyin boyutu 1 ile başlar, bu nedenle önceki -2 yanlıştır çünkü 98 bayttan büyük bir şey için bir hata döndürür, ancak> 99 bayt olmalıdır.

EDIT: İmzasız bir kısa hakkında cevap genellikle temsil edilebilir maksimum uzunluğu 65,535 karakter olduğu için doğru olsa da, gerçekten önemli değil, çünkü dize bundan daha uzunsa, değer sarılır. Bu 75.231 (0x000125DF) almak ve size 9695 (0x000025DF) veren ilk 16 bitin maskelenmesi gibi. Bununla ilgili gördüğüm tek sorun, uzunluk kontrolü kopyalamaya izin vereceği için 65,535'i geçen ilk 100 karakterdir, ancak her durumda dizenin yalnızca ilk 100 karakterine kadar kopyalar ve null dizeyi sonlandırır . Bu nedenle, sarma sorunu olsa bile, arabellek yine de taşmaz.

Bu, dizenin içeriğine ve ne için kullandığınıza bağlı olarak kendi başına bir güvenlik riski oluşturabilir veya etmeyebilir. İnsan tarafından okunabilen düz metinse, genellikle sorun yoktur. Sadece kesik bir dize alıyorsunuz. Ancak, URL veya hatta bir SQL komut dizisi gibi bir şey varsa, bir sorununuz olabilir.


2
Doğru, ama bu sorunun kapsamı dışında. Kod açıkça bir char işaretçisi geçirilen işlevi gösterir. Fonksiyonun kapsamı dışında, umursamıyoruz.
Daniel Rudy

"str'nin saklandığı tampon" - bu , sorun olan bir arabellek taşması değildir . Ve her cevap imzası verili kaçınılmaz olduğunu "sorun" vardır func... ve her diğer argümanlar olarak boş karakter sonlandırmalı dizeleri alır şimdiye kadar yazılmış C fonksiyonu. Girdinin NUL sonlandırılmaması olasılığını getirmek tamamen clueless.
Jim Balter

"bu sorunun kapsamının ötesinde" - ki bu ne yazık ki bazı insanların kavrama yeteneğinin ötesinde.
Jim Balter

"Sorun burada" - haklısınız, ancak yine de anahtar sorunu kaçırıyorsunuz, yani test ( len >= 100) bir değere karşı yapıldı, ancak kopyanın uzunluğuna farklı bir değer verildi ... bu KURU ilkesinin ihlalidir. Sadece çağırmak strncpy(buffer, str, len), arabellek taşması olasılığını önler ve strncpy(buffer,str,sizeof(buffer) - 1)burada daha yavaş bir eşdeğer olmasına rağmen ... daha az iş yapar memcpy(buffer, str, len).
Jim Balter

@JimBalter Sorunun kapsamı dışında, ama konuya giriyorum. Test tarafından kullanılan değerlerin ve strncpy'de kullanılan değerlerin iki farklı olduğunu anlıyorum. Bununla birlikte, genel kodlama uygulaması, kopya sınırının sizeof (tampon) - 1 olması gerektiğini, bu nedenle kopyadaki str uzunluğunun ne olduğu önemli değildir. strncpy bir null değerine ulaştığında veya n bayt kopyaladığında bayt kopyalamayı durdurur. Bir sonraki satır, arabellekteki son baytın boş bir karakter olacağını garanti eder. Kod güvenlidir, önceki ifademin yanında duruyorum.
Daniel Rudy

11

Kullansanız bile strncpy, kesmenin uzunluğu hala geçirilen dize işaretçisine bağlıdır. Bu dizenin ne kadar uzun olduğu hakkında hiçbir fikriniz yok (boş göstericinin işaretçiye göre konumu). Bu yüzden strlenyalnız çağırmak sizi savunmasızlığa açar. Daha güvenli olmak istiyorsanız kullanın strnlen(str, 100).

Tam kod düzeltildi:

int func(char *str) {
   char buffer[100];
   unsigned short len = strnlen(str, 100); // sizeof buffer

   if (len >= 100) {
     return -1;
   }

   strcpy(buffer, str); // this is safe since null terminator is less than 100th index
   return 0;
}

@ user3386109 strlenAyrıca ara belleğin sonundan da erişemez misiniz ?
Patrick Roberts

2
@ user3386109 işaret ettiğiniz şey orlp'nin cevabını benimki kadar geçersiz kılar. strnlenOrlp'nin öne sürdüğü herhalde doğruysa, neden sorunu çözmediğini göremiyorum.
Patrick Roberts

1
"Strnlen'in burada bir şey çözdüğünü sanmıyorum" - elbette öyle; taşmayı önler buffer. "str, ikisi de NUL olan 2 baytlık bir arabelleğe işaret edebildiğinden." - herhangi bir uygulamasında olduğu gibi, bu önemsizdir func. Buradaki soru, girdi NUL sonlandırılmadığından UB değil, arabellek taşmasıyla ilgilidir.
Jim Balter

1
"Strnlen'e iletilen ikinci parametre, ilk parametrenin işaret ettiği veya strnlenin değersiz olduğu nesnenin boyutu olmalıdır" - bu tam ve tamamen saçmalıktır. Eğer strnlen için ikinci argüman giriş dizesinin uzunluğu ise, strnlen strlen ile eşdeğerdir. Bu numarayı nasıl elde edersiniz ve eğer varsa, neden str [n] len'i çağırmanız gerekir? Strnlen bunun için değil.
Jim Balter

1
O OP'ın koduna denk değil çünkü bu cevap rağmen 1 kusurlu olduğu - strncpy boş karakter yastıkları ve NUL sonlandırmaz, strcpy boş karakterle sona erdiği oysa olup NUL-pad yapıyor, o does için, aksine sorunu çözmek gülünç, cahil yorumlar yukarıdaki.
Jim Balter

4

Sargı ile cevap doğru. Ama eğer sanmıyorum bir sorun var (len> = 100)

Len 100 olsaydı, 100 öğeyi kopyalardık ve sondaki \ 0 değerine sahip olmazdık. Bu açıkça, uygun sonlu dizeye bağlı diğer işlevlerin orijinal dizinin ötesine geçeceği anlamına gelir.

C'den gelen sorunlu dize IMHO çözülemez. Aramadan önce bazı sınırlarınız olsa iyi olur, ama bu bile yardımcı olmaz. Kontrol sınır yoktur ve bu nedenle tampon taşmaları her zaman olabilir ve ne yazık ki olacak ....


Dize sorunlu olan çözülebilir: Sadece uygun işlevlerini kullanın. I. e. ve arkadaş değil strncpy() , ve bellek ayırma işlevi strdup()ve arkadaş gibi . POSIX-2008 standardındadır, bu nedenle bazı tescilli sistemlerde mevcut olmasa da oldukça portatiftirler.
cmaster - reinstate monica

"uygun sonlu dizeye bağlı olarak herhangi başka bir işlev" - bufferbu işlev için yereldir ve başka bir yerde kullanılmaz. Gerçek bir programda, nasıl kullanıldığını incelemek zorundayız ... bazen NUL sonlandırması doğru değildir (strncpy'ın orijinal kullanımı UNIX'in 14 bayt dizin girişlerini (NUL dolgulu ve NUL sonlandırılmamış) oluşturmaktı. "C'den gelen sorunlu dize IMHO çözülemez" - C çok daha iyi bir teknolojinin ötesine geçen gawdawful bir dilken, yeterli disiplin kullanılırsa içine güvenli kod yazılabilir.
Jim Balter

Senin gözlemin bana yanlış yönlendirilmiş gibi geliyor. if (len >= 100)denetimin başarısız olduğu zaman değil , bu durum başarısız olduğunda koşul anlamına gelir; bu, uzunluk başarısızlık durumuna dahil edildiğinden, NUL sonlandırıcısı olmadan tam olarak 100 baytın kopyalandığı bir durum olmadığı anlamına gelir.
Patrick Roberts

@ cmaster. Bu durumda yanılıyorsunuz. Çözülemez, çünkü kişi her zaman sınırları aşabilir. Evet, tanımsız bir davranış ama bunu tamamen önlemenin bir yolu yok.
Friedrich

@Jim Balter. Sorun değil. Potansiyel olarak bu arabellek sınırları üzerine yazabilirim ve bu nedenle diğer bazı veri yapılarını bozmak her zaman mümkün olacaktır.
Friedrich

3

strlenBir kereden fazla çağırmayla ilgili güvenlik sorunlarının ötesinde, uzunluğu kesin olarak bilinen dizelerde dize yöntemleri kullanılmamalıdır [çoğu dize işlevi için, yalnızca kullanılması gereken çok dar bir durum vardır - maksimum uzunluk garanti edilebilir, ancak kesin uzunluk bilinmemektedir]. Giriş dizesinin uzunluğu ve çıkış arabelleğinin uzunluğu bilindikten sonra, bir bölgenin ne kadar büyük kopyalanması gerektiği ve ardından memcpy()söz konusu kopyayı gerçekte gerçekleştirmek için kullanılması gerekir. Sadece 1-3 baytlık bir dizeyi kopyalarken strcpydaha iyi performans göstermesi mümkün olsa da memcpy(), birçok platformda memcpy()daha büyük dizelerle uğraşırken iki kat daha hızlıdır.

Güvenlik performans maliyetiyle gelirdi bazı durumlar olsa da, bu güvenli yaklaşımdır bir durum da daha hızlı bir. Bazı durumlarda, girişleri besleyen kodun iyi davranacağından emin olabilirse ve kötü niyetli girişlere karşı korunma performansı engelleyecekse, garip davranan girişlere karşı güvenli olmayan kodlar yazmak makul olabilir. Dize uzunluklarının yalnızca bir kez kontrol edildiğinden emin olmak hem performansı hem de güvenliği geliştirir , ancak dize uzunluğunu manuel olarak izlerken bile güvenliği korumak için ek bir şey yapılabilir: sondaki boş olması beklenen her dize için, sondaki boşluğu açıkça yazın kaynak dizeye sahip olmasını beklemektense. Dolayısıyla, eğer bir kişi strdupeşdeğeri yazıyorsa :

char *strdupe(char const *src)
{
  size_t len = strlen(src);
  char *dest = malloc(len+1);
  // Calculation can't wrap if string is in valid-size memory block
  if (!dest) return (OUT_OF_MEMORY(),(char*)0); 
  // OUT_OF_MEMORY is expected to halt; the return guards if it doesn't
  memcpy(dest, src, len);      
  dest[len]=0;
  return dest;
}

Memcpy len+1baytları işlediyse son ifadenin genellikle atlanabileceğini , ancak başka bir iş parçacığının kaynak dizgiyi değiştirmesi gerektiğini unutmayın, sonuç NUL sonlandırılmamış bir hedef dize olabilir.


3
Bir kereden fazla çağrıstrlen yapmayla ilgili güvenlik sorunlarını açıklayabilir misiniz ?
Bogdan Alexandru

1
@BogdanAlexandru: Bir kişi, strlendöndürülen değere (muhtemelen ilk etapta çağırmanın sebebi) dayanarak bazı eylemler çağırıp yaptığında, tekrarlanan bir çağrı (1) her zaman ilkiyle aynı cevabı verecektir, bu durumda basitçe boşa harcanır veya (2) bazen (çünkü başka bir şey - belki de başka bir iş parçacığı - bu arada dizeyi değiştirdiğinden) farklı bir yanıt verebilir, bu durumda uzunlukla bazı şeyler yapan kod (ör. bir arabellek ayırmak) başka şeyler yapan (ara belleğe kopyalama) koddan farklı bir boyut alabilir.
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.