Standart kütüphanedeki hangi işlevlerden kaçınılmalıdır (kaçınılmalıdır)?


90

Stack Overflow'da bazı C işlevlerinin "eski" veya "kaçınılması gerektiğini" okudum. Lütfen bana bu tür işlevlerden ve nedenlerinden bazı örnekler verebilir misiniz?

Bu işlevlere hangi alternatifler var?

Bunları güvenli bir şekilde kullanabilir miyiz - herhangi bir iyi uygulama?


3
Bunun bilmenin çok iyi olduğuna inanıyorum. C'nin bazı işlevlerinden gerçekten kaçınılmalı ve yalnızca eğitim amaçlı kullanılmalıdır.
INS

@Felix, gelecekte tek bir (doğru olarak işaretlenmiş) yanıtı düzenlemek daha kolay olacaktır. Muhtemelen zamanla değişecek cevaplarla ilgili gerçekten havada asılı duruyor. Kim bilir, belki Jeff, önümüzdeki birkaç yıl içinde yanıtları güncel tutan kişilere bir "kapıcı" rozeti verecektir.
Tim Post

1
@Tim Gönderi: Tamam, yorumlarımı sileceğim.
Felix Kling

3
strncpy()Genel bir ikame olarak kullanmam strcpy()ve hiç kullanmam strncat()çünkü akla gelebilecek en sezgisel arayüze sahip - uzunluk parametresinin neyi belirttiğini biliyor musunuz?
Jonathan Leffler

2
Strncpy ve strncat kullanmak neredeyse her zaman bir hatadır. Kesinlikle strcpy ve strcat yerine kullanılmamalıdır !! scanf ve sprintf de onları nasıl kullanacağınızı biliyorsanız mükemmel bir şekilde kullanılabilirler ...
R .. GitHub BUZU DURDURMAK

Yanıtlar:


58

Kullanımdan Kaldırılmış İşlevler
Güvensiz
Böyle bir işlevin mükemmel bir örneği gets () ' dır, çünkü ona hedef arabelleğin ne kadar büyük olduğunu söylemenin bir yolu yoktur. Sonuç olarak, gets () kullanarak girişi okuyan herhangi bir programda bir arabellek taşması güvenlik açığı vardır . Benzer nedenlerden dolayı, bir kullanmalıdır strncpy () yerine strcpy () ve strncat () yerine strcat işlevinin () .

Yine de bazı örnekler, geçici dosyaların üzerine yazılmasıyla ilgili potansiyel güvenlik sorunları nedeniyle tmpfile () ve mktemp () işlevini içerir ve bunların yerini daha güvenli mkstemp () işlevi almıştır.

Yeniden Giren Olmayan
Diğer örnekler arasında , evresel olmayan (ve bu nedenle, iş parçacığı güvenli olması garanti edilmeyen) ve yeniden girişli getaddrinfo () ve freeaddrinfo () tarafından değiştirilen gethostbyaddr () ve gethostbyname () bulunur .

Burada bir model fark ediyor olabilirsiniz ... ya güvenlik eksikliği (muhtemelen imzaya, muhtemelen güvenli bir şekilde uygulamak için yeterli bilgiyi dahil etmemekle) ya da yeniden giriş yapılmaması yaygın kullanımdan kaldırma kaynaklarıdır.

Eski, Taşınabilir Olmayan
Diğer bazı işlevler, işlevselliği kopyaladıkları ve diğer varyantlar kadar taşınabilir olmadıkları için kullanımdan kaldırılır. Örneğin, bzero () , memset () lehine kullanımdan kaldırılmıştır .

İş Parçacığı Güvenliği ve Yeniden Giriş Gönderinizde
iş parçacığı güvenliği ve yeniden giriş hakkında soru sordunuz. Ufak bir fark var. Bir işlev, herhangi bir paylaşılan, değiştirilebilir durum kullanmıyorsa, evreseldir. Bu nedenle, örneğin, ihtiyaç duyduğu tüm bilgiler işleve aktarılırsa ve ihtiyaç duyulan tamponlar da işleve aktarılırsa (işleve yapılan tüm çağrılar tarafından paylaşılmak yerine), bu durumda evreseldir. Bu, farklı iş parçacığının bağımsız parametreler kullanarak kazara paylaşma durumunu riske atmayacağı anlamına gelir. Yeniden giriş, iplik güvenliğinden daha güçlü bir garantidir. Bir işlev, aynı anda birden çok evre tarafından kullanılabiliyorsa, iş parçacığı açısından güvenlidir. Bir işlev şu durumlarda güvenlidir:

  • Evreseldir (yani aramalar arasında herhangi bir durumu paylaşmaz) veya:
  • Evresel değildir, ancak paylaşılan durum için gerektiği gibi senkronizasyon / kilitleme kullanır.

Genel olarak, Tek UNIX Spesifikasyonu ve IEEE 1003.1'de (yani "POSIX"), evresel olması garanti edilmeyen herhangi bir işlevin iş parçacığı için güvenli olduğu garanti edilmez. Bu nedenle, başka bir deyişle, yalnızca evresel olması garanti edilen işlevler, çok iş parçacıklı uygulamalarda taşınabilir olarak kullanılabilir (harici kilitleme olmadan). Ancak bu, bu standartların uygulamalarının evresel olmayan bir işlevi evreleri güvenli hale getirmeyi seçemeyeceği anlamına gelmez. Örneğin, Linux, iş parçacığı güvenliğinin garantisini (Tek UNIX Belirtiminin ötesinde) eklemek için sık sık evresel olmayan işlevlere eşitleme ekler.

Dizeler (ve Genel olarak Bellek Tamponları)
Dizeler / dizilerle ilgili bazı temel kusurlar olup olmadığını da sordunuz. Bazıları durumun böyle olduğunu iddia edebilir, ancak ben hayır, dilde temel bir kusur olmadığını iddia ediyorum. C ve C ++, bir dizinin uzunluğunu / kapasitesini ayrı ayrı geçirmenizi gerektirir (diğer bazı dillerde olduğu gibi bu bir ".length" özelliği değildir). Bu aslında bir kusur değil. Herhangi bir C ve C ++ geliştiricisi, uzunluğu gerektiği yerde parametre olarak ileterek doğru kodu yazabilir. Sorun, bu bilgiyi gerektiren birkaç API'nin bunu bir parametre olarak belirtememesidir. Veya bazı MAX_BUFFER_SIZE sabitinin kullanılacağı varsayıldı. Bu tür API'ler artık kullanımdan kaldırılmış ve dizi / arabellek / dize boyutlarının belirtilmesine izin veren alternatif API'ler ile değiştirilmiştir.

Scanf (Son Sorunuzun Cevabında)
Şahsen, C ++ iostreams kitaplığını (std :: cin, std :: cout, << ve >> operatörleri, std :: getline, std :: istringstream, std :: ostringstream kullanıyorum , vb.), bu yüzden genellikle bununla ilgilenmiyorum. Ben saf C kullanmak zorunda olsaydı, olsa da, ben şahsen sadece kullanmak istiyorsunuz fgetc () veya getchar () ile kombinasyon halinde strtol () , strtoul () Ben büyük bir hayranı değilim, çünkü elle vs. ve ayrıştırma şeyler varargs veya biçim dizeleri. Bununla birlikte, bildiğim kadarıyla , [f] scanf () , [f] printf () ile ilgili bir sorun yok dedi., vb. biçim dizelerini kendiniz oluşturduğunuz sürece, rasgele biçim dizelerini asla iletmezsiniz veya kullanıcı girdisinin biçim dizeleri olarak kullanılmasına izin vermezsiniz ve uygun olduğunda <inttypes.h> içinde tanımlanan biçimlendirme makrolarını kullanırsınız . (Not, snprintf () , sprintf () yerine kullanılmalıdır , ancak bunun, biçim dizgilerinin kullanılmaması değil, hedef arabelleğin boyutunun belirlenememesiyle ilgisi vardır). Ayrıca, C ++ 'da boost :: format'ın varargs olmadan printf benzeri biçimlendirme sağladığını da belirtmeliyim.


4
"Kullanımdan kaldırıldı", C ++ Standardı tartışılırken belirli bir anlamı olan güçlü bir kelimedir. Bu anlamda, gets (), strcpy () vb. Kullanımdan kaldırılmış değildir.

4
"C standardı tarafından kullanımdan kaldırıldı", "Michael Aaron Safyan tarafından reddedildi" ve "ne hakkında konuştuklarını umarım bilen bilinmeyen kişilerce reddedildi" [alıntı gerekli] "arasında ayrım yaptığınız sürece. Soru olan ikinci iki uygun, yani yaklaşık değil, Cı standart hakkında tarzı tercih edilen kodlayıcı. Ama Neil gibi ben de, ifadelerinizin ilk anlamı ima etme niyetinde olmadığını fark etmeden önce iki kez ele almam gerekiyordu.
Steve Jessop

11
strncpygenel olarak da kaçınılmalıdır. Çoğu programcının yaptığını varsaydığı şeyi yapmaz. Sonlandırmayı garanti etmez (arabellek taşmalarına neden olur) ve daha kısa dizeleri (bazı durumlarda muhtemelen düşürücü performans) doldurur.
Adrian McCarthy

2
@Adrian: Size katılıyorum - ne strncpy()daha da kötüsü strncat()n-az değişkenler için mantıklı bir ikame değildir.
Jonathan Leffler

4
Bu yanıt, "strcpy'yi strncpy ile değiştirin - nedenini bilmiyorum ama Microsoft bana öyle diyor." strncpy hiçbir zaman strcpy'nin güvenli bir versiyonu olarak tasarlanmadı! Görünüşe göre çok daha güvensiz. Bkz. Strlcpy ve strlcat neden güvensiz kabul edilir? .
Lundin

24

Bir kez daha insanlar mantra benzeri, str işlevlerinin "n" sürümünün güvenli sürümler olduğu şeklindeki gülünç iddiayı tekrar ediyorlar.

Eğer amaçlandıkları şey buysa, dizeleri her zaman null sonlandırırlar.

İşlevlerin "n" sürümleri, sıfır sonlandırıcının yalnızca dizge alanı doldurmuyorsa gerekli olduğu sabit uzunluklu alanlarda (eski dosya sistemlerindeki dizin girişleri gibi) kullanılmak üzere yazılmıştır. Bu, aynı zamanda, işlevlerin, yalnızca yerine koyma olarak kullanıldığında anlamsız bir şekilde verimsiz olan garip yan etkilere sahip olmasının nedenidir - örneğin strncpy () alın:

Eğer s2 ile gösterilen dizi n bayttan daha kısa bir dizeyse, tümünde n bayt yazılana kadar s1 ile gösterilen dizideki kopyaya boş baytlar eklenir.

Dosya adlarını işlemek için ayrılan tamponlar tipik olarak 4kbyte olduğundan bu, performansta büyük bir bozulmaya yol açabilir.

"Sözde" güvenli sürümler istiyorsanız, dizeleri her zaman geçersiz kılan ve yan etkileri olmayan kendi strl yordamlarınızı (strlcpy, strlcat vb.) Edinin veya yazın. Lütfen bunların dizeyi sessizce kısaltabilecekleri için gerçekten güvenli olmadıklarını unutmayın - bu, herhangi bir gerçek dünya programında nadiren en iyi eylemdir. Bunun uygun olduğu durumlar vardır, ancak bunun felaket sonuçlara yol açabileceği birçok durum da vardır (örneğin, tıbbi reçetelerin yazdırılması).


1
Haklısın strncpy(), ama yanılıyorsun strncat(). strncat()sabit uzunluklu alanlarla kullanılmak üzere tasarlanmamıştır - aslında strcat(), birleştirilmiş karakter sayısını sınırlandıracak şekilde tasarlanmıştır . Bu bir "güvenli olarak kullanmak oldukça kolaydır strcat()birden concatenations yapıyor ve daha kolay bir" güvenli olarak kullanmak için zaman tamponu içinde kalan alanın izleyerek " strcpy()hedef tampon ilk karakteri ayarlayarak (" '\0'önce çağırarak). strncat() her zaman hedef dizeyi sonlandırır ve fazladan '\0'URL'ler yazmaz .
kafeterya

2
@caf - evet ama strncat (), bir parametre olarak kopyalanacak maksimum uzunluğu aldığından - hedef arabelleğin uzunluğunu değil - tamamen yararsızdır. Arabellek taşmasını önlemek için geçerli hedef dizgesinin uzunluğunu bilmeniz gerekir ve eğer kaynak dizesini sadece strlcat () yerine neden strncat () kullanırsınız - ki bu da onu hedef uzunluğunu yeniden hesaplamalıdır - dest dizesinin sonu.
Yağ çubuğu

Bu yine de strncat(), hedefi her zaman iptal etmediğini ve her ikisi de yanlış olan sabit uzunluklu alanlarla kullanılmak üzere tasarlandığını ima ettiğiniz gerçeğini değiştirmez .
kafeterya

2
@chrisharris: strncat()kaynak dizginin uzunluğuna bakılmaksızın düzgün çalışacak strcat(), ancak çalışmayacaktır. Buradaki sorun strlcat(), bunun standart bir C işlevi olmamasıdır.
David Thornley

@caf - strncat () hakkında haklısınız. Aslında hiç kullanmadım çünkü - yukarıda belirttiğim gibi - tamamen işe yaramaz. Bu nedenle yine de kaçınılmalıdır.
Yağ çubuğu

19

Buradaki birkaç cevap strncat()over kullanmanızı önerir strcat(); Bundan strncat()(ve strncpy()) kaçınılması gerektiğini öneririm . Doğru kullanımı zorlaştıran ve hatalara yol açan sorunları vardır:

  • length parametresi strncat(), hedef arabelleğin boyutundan ziyade hedefe kopyalanabilen maksimum karakter sayısı ile ilgilidir (ancak tam olarak değil - 3. noktaya bakın). Bu strncat(), özellikle hedefe birden çok öğe birleştirilecekse, olması gerekenden daha zor hale getirir .
  • sonucun kesilip kesilmediğini belirlemek zor olabilir (önemli olabilir veya olmayabilir)
  • Bir defaya mahsus bir hata yapmak kolaydır. C99 standart belirttiği gibi, "Böylece, dizideki sona maksimum karakter sayısı ile gösterilen s1ise strlen(s1)+n+1bir arama için" o bakışlar gibistrncat( s1, s2, n)

strncpy()sezgisel bir şekilde kullanmaya çalıştığınız hatalara neden olabilecek bir sorunu da vardır - hedefin boş olarak sonlandırıldığını garanti etmez. '\0'Arabellek son konumuna kendiniz bırakarak (en azından belirli durumlarda) bu köşe durumunu özellikle hallettiğinizden emin olmanız gerektiğinden emin olmak için .

OpenBSD gibi bir şey kullanmanızı öneririm strlcat()ve strlcpy()(bazı insanların bu işlevleri sevmediğini bilsem de; güvenli kullanımının strncat()/ ' den çok daha kolay olduğuna inanıyorum strncpy()).

İşte Todd Miller ve Theo de Raadt'ın strncat()ve ile ilgili sorunlar hakkında söylediklerinden biraz strncpy():

Karşılaşılan çeşitli sorunlar vardır strncpy()ve strncat()güvenli sürümleri olarak kullanılır strcpy()ve strcat(). Her iki işlev de NUL sonlandırma ve uzunluk parametresini, deneyimli programcıların bile kafasını karıştıran farklı ve sezgisel olmayan yollarla ele alır. Ayrıca, kesimin ne zaman gerçekleştiğini algılamanın kolay bir yolunu da sağlamazlar. ... Tüm bu sorunlar arasında, uzunluk parametrelerinin neden olduğu kafa karışıklığı ve ilgili NUL sonlandırma sorunu en önemlisidir. Potansiyel güvenlik açıkları için OpenBSD kaynak ağacını denetlenmiş zaman biz yaygın kötüye bulundu strncpy()ve strncat(). Bunların hepsi istismar edilebilir güvenlik açıklarına neden olmamakla birlikte , güvenli dizi işlemlerini kullanma strncpy()ve bu işlemlerde kuralların strncat()büyük ölçüde yanlış anlaşıldığını açıkça ortaya koydular.

OpenBSD'nin güvenlik denetimi, bu işlevlerdeki hataların "yaygın" olduğunu buldu. Aksine gets(), bu işlevler olabilir güvenle kullanılabilir, fakat arayüz kafa karıştırıcı çünkü pratikte doğru kullanımı unintuitive ve zor sorunların çoğunu orada olmak. Microsoft'un da analiz yaptığını biliyorum (ancak verilerinin ne kadarını yayınlamış olabileceğini bilmiyorum) ve sonuç olarak yasakladı (veya en azından kesinlikle tavsiye etmedi - 'yasak' mutlak olmayabilir) strncat()ve kullanımı strncpy()(diğer işlevlerin yanı sıra).

Daha fazla bilgi içeren bazı bağlantılar:


1
Dizelerin uzunluğunu dizenin dışında tutmak çok daha iyidir. Bunun gibi, iki dizgeyi (-with-length) güvenli bir şekilde birleştirmek basit bir hesaplama meselesi haline gelir (gerekli tampon boyutunu elde etmek için) olası bir yeniden tahsis ve a memmove(). (Peki, memcpy()dizeler bağımsız olduğunda normal durumda kullanabilirsiniz .)
Donal Fellows

1
İkinci noktanız tamamen yanlış - strncat() her zaman hedef dizeyi sonlandırır.
kafe

1
İkinci noktanız yanlış strncat(). Ancak, strncpy()başka sorunları olan için doğrudur . strncat()için makul bir ikamedir strcat(), ancak strncpy()makul bir ikame değildir strcpy().
David Thornley

2
caf ve David strncat()her zaman boşa gitmeyen iddiam konusunda% 100 haklılar . Ben davranışlarını kafa karıştırıcı strncat()ve strncpy()(- ... önemli şekillerde farklı onlar benzer davranışlar ima isimler var, ama aslında davrandıklarını onlar ediyoruz fonksiyonları önlemek için başka bir sebep) biraz. Cevabımı düzeltmek ve ek bilgi eklemek için değiştirdim.
Michael Burr

1
char str[N] = ""; strncat(str, "long string", sizeof(str));N yeterince büyük değilse bunun bir arabellek taşması olduğuna dikkat edin. strncat()Fonksiyon kötüye çok kolaydır; kullanılmamalıdır. strncat()Güvenli bir şekilde kullanabiliyorsanız , kullanmış olabilirsiniz memmove()veya memcpy()onun yerine (ve bunlar daha verimli olacaktır).
Jonathan Leffler

9

Asla kullanılmaması gereken standart kütüphane işlevleri :

setjmp.h

  • setjmp(). Bununla birlikte longjmp(), bu işlevlerin kullanımı inanılmaz derecede tehlikeli olarak kabul edilmektedir: Spagetti programlamaya yol açarlar, çok sayıda tanımlanmamış davranış biçimiyle birlikte gelirler, program ortamında yığında depolanan değerleri etkilemek gibi istenmeyen yan etkilere neden olabilirler. Referanslar: MISRA-C: 2012 kural 21.4, CERT C MSC22-C .
  • longjmp(). Bakın setjmp().

stdio.h

  • gets(). İşlev, tasarım gereği güvensiz olduğu için C dilinden (C11 uyarınca) kaldırılmıştır. İşlev, C99'da zaten geçersiz olarak işaretlenmişti. fgets()Bunun yerine kullanın . Referanslar: ISO 9899: 2011 K.3.5.4.1, ayrıca bakınız not 404.

stdlib.h

  • atoi()fonksiyonlar ailesi. Bunların herhangi bir hata işleme özelliği yoktur, ancak her hata oluştuğunda tanımsız davranışa neden olurlar. Fonksiyon strtol()ailesiyle değiştirilebilen tamamen gereksiz fonksiyonlar. Referanslar: MISRA-C: 2012 kural 21.7.

string.h

  • strncat(). Genellikle kötüye kullanılan garip bir arayüze sahiptir. Çoğunlukla gereksiz bir işlevdir. Ayrıca için açıklamalara bakın strncpy().
  • strncpy(). Bu işlevin amacı hiçbir zaman daha güvenli bir versiyon olmak değildi strcpy(). Tek amacı, Unix sistemlerinde her zaman eski bir dizgi biçimini ele almaktı ve standart kitaplığa dahil edilmesi bilinen bir hatadır. Bu işlev tehlikelidir çünkü dizeyi boş sonlandırma olmadan bırakabilir ve programcıların bunu sıklıkla yanlış kullandığı bilinmektedir. Referanslar: strlcpy ve strlcat neden güvensiz kabul edilir? .

Dikkatli kullanılması gereken standart kitaplık işlevleri:

assert.h

  • assert(). Genel giderlerle birlikte gelir ve genellikle üretim kodunda kullanılmamalıdır. Hataları görüntüleyen, ancak tüm programı kapatması gerekmeyen uygulamaya özgü bir hata işleyici kullanmak daha iyidir.

signal.h

stdarg.h

  • va_arg()fonksiyonlar ailesi. Bir C programında değişken uzunluklu fonksiyonların varlığı neredeyse her zaman kötü program tasarımının bir göstergesidir. Çok özel gereksinimleriniz olmadıkça kaçınılmalıdır.

stdio.h
Genel olarak, tüm bu kitaplık, çok sayıda kötü tanımlanmış davranış ve kötü tip güvenliği ile birlikte geldiğinden, üretim kodu için önerilmez .

  • fflush(). Çıkış akışları için mükemmel derecede iyi. Giriş akışları için kullanılırsa tanımsız davranışı çağırır.
  • gets_s(). gets()C11 sınır kontrol arayüzüne dahil edilen güvenli sürüm . Bunun fgets()yerine C standart tavsiyesine göre kullanılması tercih edilir . Referanslar: ISO 9899: 2011 K.3.5.4.1.
  • printf()fonksiyonlar ailesi. Çok sayıda tanımlanmamış davranış ve zayıf tip güvenliği ile gelen kaynak ağır işlevler. sprintf()ayrıca güvenlik açıkları vardır. Üretim kodunda bu işlevlerden kaçınılmalıdır. Referanslar: MISRA-C: 2012 kural 21.6.
  • scanf()fonksiyonlar ailesi. Hakkındaki açıklamalara bakın printf(). Ayrıca, - scanf()doğru kullanılmadığı takdirde arabellek taşmalarına karşı savunmasızdır. fgets()mümkün olduğunda kullanılması tercih edilir. Referanslar: CERT C INT05-C , MISRA -C: 2012 kural 21.6.
  • tmpfile()fonksiyonlar ailesi. Çeşitli güvenlik açığı sorunları ile birlikte gelir. Kaynaklar: CERT C FIO21-C .

stdlib.h

  • malloc()fonksiyonlar ailesi. Barındırılan sistemlerde kullanım için mükemmel derecede iyi, ancak C90'daki iyi bilinen sorunların farkında olun ve bu nedenle sonucu vermeyin . malloc()Fonksiyonların aile uygulamalarını müstakil içinde asla kullanılmamalıdır. Referanslar: MISRA-C: 2012 kural 21.3.

    Ayrıca realloc(), sonucunu eski işaretçinin üzerine yazmanız durumunda tehlikeli olduğunu unutmayın realloc(). İşlevin başarısız olması durumunda bir sızıntı yaratırsınız.

  • system(). Çok fazla ek yük ile birlikte gelir ve taşınabilir olmasına rağmen, bunun yerine sisteme özgü API işlevlerini kullanmak genellikle daha iyidir. Çeşitli kötü tanımlanmış davranışlarla birlikte gelir. Kaynaklar: CERT C ENV33-C .

string.h

  • strcat(). İçin açıklamalara bakın strcpy().
  • strcpy(). Kopyalanacak verilerin boyutu bilinmediği veya hedef arabelleğinden daha büyük olmadığı sürece, kullanımı mükemmel derecede iyidir. Gelen veri boyutunun kontrolü yapılmazsa, arabellek taşmaları olabilir. Bu strcpy()kendi başına değil, arayan uygulamanın hatasıdır - bu strcpy()güvenli değildir, çoğunlukla Microsoft tarafından yaratılan bir efsanedir .
  • strtok(). Arayan dizesini değiştirir ve dahili durum değişkenlerini kullanır, bu da onu çok iş parçacıklı bir ortamda güvensiz hale getirebilir.

Durum derleme zamanında çözülebilirse, static_assert()yerine tavsiye etmenizi öneririm assert(). Ayrıca, sprintf()neredeyse her zaman değiştirilebilir, snprintf()bu biraz daha güvenlidir.
user694733

1
strtok()Bir A işlevinde kullanılması , (a) işlevin, strtok()A onu kullanırken kullanan başka bir işlevi çağıramayacağı ve (b) A'yı çağırdığında A'yı çağıran hiçbir işlevin kullanılamayacağı anlamına gelir strtok(). Başka bir deyişle, kullanmak strtok()çağrı zincirini zehirler; strtok()diğer kullanıcıların strtok()kitaplık kodunu çağırmasını önlemek için kullandığı belgelendirmesi gerektiğinden kitaplık kodunda güvenle kullanılamaz .
Jonathan Leffler

strncpyBir sıfır yazarken kullanmak için uygun bir fonksiyondur yastıklı bir sonlandırmalı dizgenin veya boyutu hedef olarak büyük gibi en az bir sıfır dolgulu tampon ya da alınan veriler ile dize tamponu. Sıfır yastıklı tamponlar çok yaygın değildir, ancak tam olarak anlaşılmaz da değildir.
supercat

7

Bazı insanlar iddia ediyorum strcpyve strcatlehine kaçınılmalıdır strncpyve strncat. Bence bu biraz öznel.

Kullanıcı girdileriyle uğraşırken kesinlikle kaçınılmalıdır - burada şüphesiz.

Kullanıcıdan "uzak" kodda, sadece tamponların yeterince uzun olduğunu bildiğinizdestrcpy ve strcatbiraz daha verimli olabilir çünkü nkuzenlerine geçişi hesaplamak gereksiz olabilir.


4
Ve eğer mevcutsa, daha iyisi strlcatve strlcpy'n' versiyonu hedef dizgenin NULL sonlandırılmasını garanti etmediğinden.
Dan Andreatta

Bana erken optimizasyon gibi geliyor. Ancak IIRC, gerekirse nul karakterleri kullanarak strncpy()tam olarak nbayt yazacaktır . Dan olarak, güvenli bir sürüm kullanmak en iyi seçim IMO'dur.
Bastien Léonard

@Dan, bu işlevler maalesef standart değil ve bu nedenle bu tartışmaya dahil değil
Eli Bendersky

@Eli: ama strncat()doğru ve güvenli kullanımının zor olabileceği (ve kaçınılması gereken) konu üzerinde.
Michael Burr

@Michael: Doğru ve güvenli kullanmanın zor olduğunu söyleyemem.
Dikkatle,

6

Önlemek

  • strtok çok iş parçacıklı programlar için iş parçacığı güvenli olmadığı için.
  • gets arabellek taşmasına neden olabileceğinden

2
Bunlar biraz farklı durumlardır. Programınızın çok iş parçacıklı olmadığını biliyorsanız veya ona erişimi bir şekilde kilitlerseniz, ancak gets güvenli bir şekilde kullanamıyorsanız, strtok'u güvenle kullanabilirsiniz.
jcoder

9
İle ilgili sorun, strtok()iş parçacığı güvenliğinin biraz ötesine geçer - kodunuzun kullanırken çağırabileceği hiçbir işlevin aynı strtok()zamanda onu kullanmayacağından emin olmadığınız sürece, tek iş parçacıklı bir programda bile güvenli değildir (ya da strtok()durumu bozarlar senden). Aslında, çok iş parçacıklı platformları hedefleyen çoğu derleyici, statik verileri strtok()için iş parçacığı yerel depolamasını kullanarak iş parçacığının gittiği sürece olası sorunlarıyla ilgilenir strtok(). Ancak bu yine de siz (aynı iş parçacığı içindeyken) onu kullanan diğer işlevlerin sorununu çözmez.
Michael Burr

Gerçekten de, pek çok sorundan muzdarip olduğu için kimseyi strtok kullanmaya teşvik etmek istemediğim için şimdi susacağım. Güvenli bir şekilde kullanmak mümkünken, güvenli bir şekilde kullanmak imkansız olduğu için , gets'tan farklı olduğunu belirtmek istedim .
jcoder

@Jimmy: Genellikle standart olmayan kitaplık uzantıları vardır veya kendiniz yazabilirsiniz. Buradaki en büyük sorun, strtok()çalışmak için tam olarak bir arabellek tutmasıdır, bu nedenle çoğu iyi değiştirmeler, bir arabellek değerini etrafta tutmayı ve geçirmeyi gerektirir.
David Thornley

strcspnihtiyacınız olan şeylerin çoğunu yapar - bir sonraki simge ayırıcıyı bulun. Onun mantıklı bir varyantını yeniden uygulayabilirsiniz strtok.
2014

5

Muhtemelen adının önerebileceği strncpy()genel amaçlı bir ikame olmadığını tekrar eklemeye değer strcpy(). Boş sonlandırıcıya ihtiyaç duymayan sabit uzunluklu alanlar için tasarlanmıştır (başlangıçta UNIX dizin girişleriyle kullanılmak üzere tasarlanmıştır, ancak şifreleme anahtarı alanları gibi şeyler için yararlı olabilir).

Bu kullanım için, ancak, kolay strncat()bir yedek olarak strcpy():

if (dest_size > 0)
{
    dest[0] = '\0';
    strncat(dest, source, dest_size - 1);
}

( ifTest dest_sizekesinlikle sıfırdan farklı olduğunu bildiğiniz yaygın durumda bırakılabilir ).


5

Ayrıca Microsoft'un yasaklanmış API listesine bakın . Bunlar, genellikle kötüye kullanıldıkları ve güvenlik sorunlarına yol açtıkları için Microsoft kodundan yasaklanan API'lerdir (burada zaten listelenenlerin çoğu dahil).

Hepsine katılmayabilirsiniz, ancak hepsi dikkate değer. Kötüye kullanımı bir dizi güvenlik hatasına yol açtığında listeye bir API eklerler.


2

NUL ile sonlandırılmış dizelerle ilgilenen hemen hemen tüm işlevler potansiyel olarak güvensizdir. Dış dünyadan veri alıyorsanız ve bunları str * () işlevleri aracılığıyla işliyorsanız, kendinizi felakete hazırlarsınız


2

Sprintf'i unutmayın - birçok sorunun nedeni budur. Bu doğrudur çünkü alternatif, snprintf'in bazen kodunuzu aktarılamaz hale getirebilecek farklı uygulamaları vardır.

  1. linux: http://linux.die.net/man/3/snprintf

  2. pencereler: http://msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

1 (linux) durumunda, dönüş değeri, tüm tamponu depolamak için gereken veri miktarıdır (verilen tamponun boyutundan küçükse, çıktı kesildi)

2 (Windows) durumunda, çıktının kesilmesi durumunda dönüş değeri negatif bir sayıdır.

Genel olarak şu olmayan işlevlerden kaçınmalısınız:

  1. arabellek taşması güvenli (burada birçok işlevden zaten bahsedilmiştir)

  2. iş parçacığı güvenli / evresel değil (örneğin strtok)

Her işlevin kılavuzunda, güvenli, eşitleme, zaman uyumsuz, iş parçacığı, arabellek, hatalar gibi anahtar sözcükleri aramanız gerekir.


2
_sprintf()standart snprintf()IIRC gelmeden önce Microsoft tarafından oluşturulmuştur . ´StringCbPrintf () ´ buna oldukça benzer snprintf().
Bastien Léonard

Kullanabileceğiniz sprintfbazı durumlarda güvenle nasılsa: sprintf(buffer,"%10s",input);sınırlar kopyalanan nb if (10 bayt bufferolan char buffer[11]veriler kadar rüzgar olabilir dahi kısaltılacak güvenlidir.
Jean-François Fabre

2

scanfGüvenle kullanmak çok zordur . Öğesinin iyi kullanımı scanfarabellek taşmalarını önleyebilir, ancak istenen türe uymayan sayıları okurken tanımlanmamış davranışlara karşı hala savunmasızsınız. Birçok durumda, fgetskendi kendini ayrıştırma ardından (kullanarak sscanf, strchrvb) daha iyi bir seçenektir.

Ama " scanfher zaman kaçının" demezdim . scanfkullanımları vardır. Örnek olarak, char10 bayt uzunluğunda bir dizideki kullanıcı girişini okumak istediğinizi varsayalım. Varsa sondaki yeni satırı kaldırmak istiyorsunuz. Kullanıcı bir satırsonu satırından önce 9 karakterden fazla girerse, ilk 9 karakteri arabellekte saklamak ve bir sonraki satıra kadar her şeyi atmak istersiniz. Yapabilirsin:

char buf[10];
scanf("%9[^\n]%*[^\n]", buf));
getchar();

Bu deyime alıştığınızda, daha kısa ve bazı açılardan daha temiz:

char buf[10];
if (fgets(buf, sizeof buf, stdin) != NULL) {
    char *nl;
    if ((nl = strrchr(buf, '\n')) == NULL) {
        int c;
        while ((c = getchar()) != EOF && c != '\n') {
            ;
        }
    } else {
        *nl = 0;
    }
}

0

Tüm dizgi kopyalama / taşıma senaryolarında - strcat (), strncat (), strcpy (), strncpy (), vb. - birkaç basit buluşsal yöntem uygulanırsa işler çok daha iyi ( daha güvenli ) gider :

   1. Her zaman NUL-doldur veri eklemeden önce arabellek (ler) iniz.
   2. Karakter tamponlarını bir makro sabiti ile [SIZE + 1] olarak bildirin.

Örneğin, verilen:

#define   BUFSIZE   10
char      Buffer[BUFSIZE+1] = { 0x00 };  /* The compiler NUL-fills the rest */

aşağıdaki gibi kodu kullanabiliriz:

memset(Buffer,0x00,sizeof(Buffer));
strncpy(Buffer,BUFSIZE,"12345678901234567890");

nispeten güvenli. Memset (), Buffer'ı derleme zamanında başlatmış olsak da strncpy () 'den önce görünmelidir, çünkü işlevimiz çağrılmadan önce diğer kodun içine yerleştirilen çöpü bilmiyoruz. Strncpy () "1234567890" kopyalanan verileri keser ve olacak değil bunu boş karakter sonlandırabilir. Bununla birlikte, tüm arabelleği - BUFSIZE yerine sizeof (Buffer) - NUL ile doldurduğumuz için, yazmalarımızı BUFSIZE kullanarak kısıtladığımız sürece, yine de son bir "kapsam dışı" NUL sonlandırması garanti edilir. sizeof (Buffer) yerine sabit.

Buffer ve BUFSIZE aynı şekilde snprintf () için iyi çalışır:

memset(Buffer,0x00,sizeof(Buffer));
if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) {
    /* Do some error-handling */
}   /* If using MFC, you need if(... < 0), instead */

Snprintf () özellikle BUFIZE-1 karakterleri yazsa da ve ardından NUL yazsa da, bu güvenli bir şekilde çalışır. Bu yüzden, Buffer'ın sonunda gereksiz bir NUL baytını "boşa harcıyoruz" ... oldukça küçük bir bellek maliyeti karşılığında hem arabellek taşmasını hem de sonlandırılmamış dizgi koşullarını önlüyoruz.

Strcat () ve strncat () üzerindeki çağrım daha katı: onları kullanmayın. Strcat () 'i güvenli bir şekilde kullanmak zordur ve strncat () için API o kadar sezgiseldir ki, onu doğru şekilde kullanmak için gereken çaba herhangi bir faydayı ortadan kaldırır. Aşağıdaki hizmeti öneriyorum:

#define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source)

Bir strcat () drop-in oluşturmak cazip gelebilir, ancak bu iyi bir fikir değil:

#define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source)

çünkü target bir işaretçi olabilir (bu nedenle sizeof () ihtiyacımız olan bilgiyi döndürmez). Kodunuzda strcat () örnekleri için iyi bir "evrensel" çözümüm yok.

"StrFunc () bilinçli" programcılardan sıklıkla karşılaştığım bir sorun, strlen () kullanarak arabellek taşmalarına karşı koruma girişimidir. İçeriğin NUL ile sonlandırılacağı garanti edilirse bu sorun değildir. Aksi takdirde, strlen () 'in kendisi, korumaya çalıştığınız "sorunlu" koda ulaşmadan önce bir arabellek aşımı hatasına (genellikle bir bölümleme ihlaline veya başka bir çekirdek dökümü durumuna yol açar) neden olabilir.


-2

atoi iş parçacığı için güvenli değildir. Bunun yerine, man sayfasındaki öneri başına strtol kullanıyorum.


5
Bu, belirli bir uygulama için geçerli gibi görünüyor. İş strtol()parçacığı açısından güvenli ve atoi()olmayacak bir neden yok .
David Thornley

2
Strtol kullanma tavsiyesinin iplik güvenliği ile ilgisi yoktur, ancak hata işleme ile ilgilidir. Bu bilgiyi ikisinden de hangi sayfa sayfasından aldığınızdan emin değilim - Aşağıda herhangi bir öneri bulamıyorum man atoi(yine de olmalı).
Lundin
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.