- cURL projesi, daha önce
strncpy() işlevini kaldırmasının ardından artık strcpy() kullanımını da kod tabanında tamamen yasakladı
strcpy() API’si basit olsa da buffer boyutu doğrulamasının ayrı düşmesi riski taşıdığı için, uzun vadeli bakım süreçlerinde güvenli değil
- Bunun yerine
curlx_strcopy() adlı yeni bir işlev eklendi; hedef buffer boyutunu ve dizge uzunluğunu parametre olarak alıp kopyalamanın mümkün olup olmadığını denetledikten sonra çalışıyor
- Bu işlev içeride
memcpy() kullanıyor ve null sonlandırma karakterinin işlenmesini de garanti ediyor
- Bu değişiklikle güvenlik ve kod tutarlılığı artırılırken, yapay zekanın hatalı zafiyet raporları üretmesi sorunu da azaltılabiliyor
strcpy kaldırılmasının arka planı
- cURL geçmişte tüm
strncpy() çağrılarını kaldırmıştı; bu işlevin sezgisel olmayan API’si, null sonlandırmayı garanti etmemesi ve gereksiz 0 doldurma sorunlarına dikkat çekmişti
- Kısmi dizge kopyalaması gereken durumlarda
memcpy() kullanılıp null sonlandırma doğrudan ele alınacak şekilde değiştirildi
strcpy() API’si basit olsa da buffer boyutunu açıkça belirtmediği için, bakım sırasında doğrulama kodu ile kopyalama çağrısının birbirinden ayrılma riski bulunuyor
- Kod onlarca yıl boyunca farklı geliştiriciler tarafından değiştirildiğinde, buffer boyutu doğrulamasının etkisiz hale gelme ihtimali var
Yeni dizge kopyalama işlevinin eklenmesi
- Bu riski önlemek için
curlx_strcopy() adlı alternatif bir işlev eklendi
- Parametre olarak hedef buffer, buffer boyutu, kaynak buffer ve kaynak dizge uzunluğunu alıyor
- Yalnızca hem kopyalama hem de null sonlandırma mümkün olduğunda
memcpy() ile çalışıyor
- Başarısız olursa hedef buffer’ı boş dizge olarak başlatıyor
- Bu işlev
strcpy()ye göre daha fazla parametre ve daha fazla kod gerektiriyor, ancak buffer doğrulamasını kopyalama işlemiyle sıkı biçimde bağlayarak güvenliği sağlıyor
- cURL kod tabanında
strcpy() kullanımı tamamen yasaklandı ve strncpy() gibi tamamen kaldırıldı
Uygulama ayrıntıları
- İşlev tanımı örneği şöyle:
void curlx_strcopy(char *dest, size_t dsize, const char *src, size_t slen)
{
DEBUGASSERT(slen < dsize);
if(slen < dsize) {
memcpy(dest, src, slen);
dest[slen] = 0;
}
else if(dsize)
dest[0] = 0;
}
DEBUGASSERT ile geliştirme sırasında hatalar erken aşamada tespit ediliyor; gerçek dağıtım ortamında ise her zaman başarılı olacak şekilde tasarlanmış
strcpy gibi dönüş değeri yok; bunun yerine test ve fuzzing aşamalarında hataları yakalama yaklaşımı benimsenmiş
Topluluk tepkisi
- Bazı geliştiriciler bunun
strcpy_s() (C11 Annex K) ile benzer olduğunu belirtse de, cURL hâlâ C89 standardını kullanıyor
- Başka görüşlerde ise dönüş değeri eklenmesi gerekliliği ya da buffer başarısızlığı durumundaki işleme yönteminin iyileştirilmesi önerildi
- Buna karşılık cURL tarafı, “İşlev her zaman başarılı olacak şekilde tasarlandığı için dönüş değeri gereksiz” açıklamasını yaptı
Yapay zeka ile ilgili ek etki
- Bu değişiklik sayesinde yapay zeka sohbet botlarının cURL kodunda
strcpy kullanımını yanlış tespit edip ‘zafiyetli’ diye işaretlemesi sorunu önlenebilir
- Ancak yazar, “yapay zeka başka sahte raporlar üretmeye devam edebilir” diyerek yapay zeka tabanlı kod analizinin sınırlarına değindi
5 yorum
strcpyyerinesnprintfkullanmak doğru olur. Koddastrcpyvarsa, onu yazan geliştiricinin adresini bulmak gerekir.Bu, 25 yıl önce bir oyun şirketinde çalışırken debug koduyla kullandığım yöntemdi; sadece
strcpyile sınırlı mı sanıyorsunuz. Release sürümünde ise hız artışı için bunlar yeniden gevşetilmiş halde servise alınırdı. Aslında oyun tarafı bellek çakışmalarına en hassas alanlardan biri olduğu için, çalışırken de son derece dikkatli ve tetikte olunurdu; hatta bellek debugger'ını da kendimiz yapıp kullanırdık. Ama bugün dönüp bakınca, meğer o şey garbage collection yapıyormuş. Tatlı bir anıymış doğrusu.Hata C4996
'strcpy': Bu işlev veya değişken güvenli olmayabilir. Bunun yerinestrcpy_skullanmayı düşünün. Kullanımdan kaldırma uyarısını devre dışı bırakmak için_CRT_SECURE_NO_WARNINGSkullanın. Ayrıntılar için çevrimiçi yardıma bakın.Hacker News görüşleri
strcpy()yalnızca güvenlik açısından değil, performans açısından da iyi değilEskiden, dizenin uzunluğu bilinmediğinde
strcpy()'nin verimli olduğu düşünülürdü; ancak gerçekte yapısı gereği bir seferde bir bayt kopyalar, bu yüzden CPU'nun dal tahmini yapması gerekir ve bu da verimsizdirstrcpy'nin scalar loop kullandığını görmedim. Acaba bu yalnızca ARM mimarisinde mi böyle diye merak ediyorumC'nin string rutinlerinin her biri büyük kısıtlamalara sahip olduğundan kullanışsız olduklarını düşündüm
Bu yüzden string pointer'ıyla birlikte ayrılan bellek boyutunu da kaydeden bir kütüphanenin kesinlikle gerekli olduğunu düşünüyorum
Örnek olarak bstring kütüphanesine bakılabilir
strncpy'nin ortaya çıkış nedeni sabit uzunluklu dosya adlarını kopyalamaktı. Ayrıntılı açıklama için bu StackOverflow yanıtına bakınstrncpyaslında sabit genişlikli string alanlarını işlemek için vardı. Örneğinchar username[20]gibi alanları NUL ile doldurmak için kullanılırdı. İlgili belge için string_copying.7 man sayfasına bakıncurlx_strcopy'nin başarı durumunu döndürmemesi garip geliyordest[0]kontrol edilebilir ama bu hata üretmeye çok açık ve sezgisel değilDEBUGASSERT(slen < dsize);geçerse bunu başarı sayıyorlar, ancak release build'de assert kaldırılabilir. Açık bir hata kodunun daha iyi olacağını düşünüyorumstrncpy()aslında null-terminated string için değil, sabit uzunluklu alanlar için yazılmıştıSorun, statik analiz araçlarının
strcpyyerinestrncpykullanılmasını önermesiyle başladı. Gerçek alternatiflersnprintfveyastrlcpyidistrlcpyBSD kökenli bir fonksiyondur, POSIX'te yoktur. Resmî öneristpecpyolsa da pratikte neredeyse hiç implementasyon yok. İlgili belgeye bakınstrncpy'nin null sonrasını doldurmasının nedeni, directory entry gibi sabit uzunluklu ad alanlarında verimli karşılaştırma yapabilmekti. ANSI C standardının gerekçe dokümanında da bu açıkça belirtiliyorBu API bana biraz Annex-K gibi geliyor. Hedef buffer boyutuna NUL alanı dahil ama kaynak boyutuna dahil değil
Bu durumda
memcpy'yi doğrudan kullanmanın daha iyi olduğunu düşünüyorumYazıdaki “
strcpy, AI'ın hatalı güvenlik açığı raporları üretmesi için bir yem” ifadesi dikkat çekiciydistrcpy'yi sorunlu diye işaretlemiyor; aynı zamanda mantık hataları içeren karmaşık ispatlar üretip bakımcıların bunları doğrulamak için uğraşmasına neden oluyor“Kontrolü kodun yakınında yap” ilkesi güzel ama verinin yaşam döngüsünün erken aşamalarında doğrulama yapmak gerektiğinde belirsizleşiyor
Rust'taki
Resulttipi gibi, “doğrulanmış veri” olduğunu tür üzerinden ayırt edebilmek güzel olurduResultyalnızca başarı/başarısızlık taşır; doğrulanmış bir durumu garanti etmez. Bunun yerine ancak doğrulama sürecinden geçerek üretilebilen ayrı bir tip daha iyi olur. Bu, “parse, don’t validate” felsefesidirBuffer boyutu ile string uzunluğu arasındaki off-by-one farkı korkunç bir kullanılabilirlik sorunu. İleride de hata üretmeye çok açık
Yeni önerilen string kopyalama fonksiyonu, kopyalama mümkün değilse hedef buffer'ı boşaltıp
voiddöndürüyorAncak böyle durumların hata olarak ele alınması ve buffer'a hiç dokunulmaması daha iyi olurdu. Yalnızca
DEBUGASSERTile engellemek güven vermiyorProjenin tamamlanmasını kutlarım. C/C++ tarafında da emek verilirse bellek güvenliği sağlanabilir
Ancak mobil ortamda grafikteki yazı boyutu çok küçük olduğundan okunabilirlik düşüyor
strcpy'yi kaldırmak tek başına kodu bellek güvenli yapmazDoğrudan C3 diline geçmek de iyi bir seçenek. C dilinin sözdizimini en az değişiklikle koruyup modern özellikler ekleyen bir proje olduğu için geçiş de kolay.