Dlaczego strlcpy i strlcat są uważane za niebezpieczne?

80

Rozumiem to strlcpyi strlcatzostały zaprojektowane jako bezpieczne zamienniki dla strncpyi strncat. Jednak niektórzy ludzie nadal uważają, że są niepewni i po prostu powodują inny rodzaj problemu .

Czy ktoś może podać przykład, jak użycie strlcpyor strlcat(tj. Funkcji, która zawsze kończy swoje łańcuchy o wartości null) może prowadzić do problemów z bezpieczeństwem?

Ulrich Drepper i James Antill twierdzą, że to prawda, ale nigdy nie podawaj przykładów ani nie wyjaśniaj tej kwestii.

Anonimowy
źródło

Odpowiedzi:

112

Po pierwsze, strlcpynigdy nie był pomyślany jako bezpieczna wersja strncpy(i strncpynigdy nie był pomyślany jako bezpieczna wersja strcpy). Te dwie funkcje są zupełnie niezwiązane. strncpyjest funkcją, która w ogóle nie ma związku z napisami C (tj. łańcuchami zakończonymi znakiem null). Fakt, że ma str...przedrostek w nazwie, jest tylko historyczną pomyłką. Historia i cel programu strncpysą dobrze znane i dobrze udokumentowane. Jest to funkcja stworzona do pracy z tak zwanymi łańcuchami o stałej szerokości (nie z łańcuchami C) używanymi w niektórych historycznych wersjach systemu plików Unix. Niektórzy programiści są dziś zdezorientowani przez jego nazwę i zakładają, że strncpyma ona w jakiś sposób służyć jako funkcja kopiowania łańcuchów C o ograniczonej długości („bezpieczne” rodzeństwostrcpy), co w rzeczywistości jest kompletnym nonsensem i prowadzi do złej praktyki programistycznej. Biblioteka standardowa C w swojej obecnej formie nie ma żadnej funkcji do kopiowania łańcuchów C o ograniczonej długości. To jest miejsce, w którym strlcpypasuje. strlcpyTo rzeczywiście prawdziwa funkcja kopiowania o ograniczonej długości, stworzona do pracy z napisami C. strlcpypoprawnie robi wszystko, co powinna robić funkcja kopiowania o ograniczonej długości. Jedyną krytyką, jaką można do niego skierować, jest to, że niestety nie jest to standard.

Po drugie, strncatz drugiej strony, jest to rzeczywiście funkcja, która działa ze stringami C i wykonuje konkatenację o ograniczonej długości (jest rzeczywiście „bezpiecznym” rodzeństwem strcat). Aby prawidłowo korzystać z tej funkcji programista musi zachować szczególną ostrożność, ponieważ parametr rozmiaru, który ta funkcja akceptuje, nie jest tak naprawdę wielkością bufora, który otrzymuje wynik, ale raczej rozmiarem jego pozostałej części (także jest liczony niejawnie). Może to być mylące, ponieważ aby powiązać ten rozmiar z rozmiarem bufora, programista musi pamiętać o wykonaniu dodatkowych obliczeń, które są często używane do krytykowania pliku strncat.strlcatzałatwia te kwestie, zmieniając interfejs tak, aby nie były potrzebne żadne dodatkowe obliczenia (przynajmniej w kodzie wywołującym). Ponownie, jedyną podstawą, na której można to krytykować, jest to, że funkcja nie jest standardowa. Ponadto funkcje z strcatgrupy są czymś, czego nie zobaczysz w profesjonalnym kodzie bardzo często ze względu na ograniczoną użyteczność samej idei konkatenacji ciągów opartej na ponownym skanowaniu.

Jeśli chodzi o to, jak te funkcje mogą prowadzić do problemów z bezpieczeństwem ... Po prostu nie mogą. Nie mogą prowadzić do problemów bezpieczeństwa w większym stopniu niż sam język C może „prowadzić do problemów bezpieczeństwa”. Widzisz, od dłuższego czasu panowało przekonanie, że język C ++ musi podążać w kierunku rozwijania się w dziwny smak Javy. Ten sentyment czasami przenika również do domeny języka C, co skutkuje raczej nieświadomą i wymuszoną krytyką cech języka C i funkcji standardowej biblioteki C. Podejrzewam, że w tym przypadku możemy mieć do czynienia z czymś takim, chociaż mam nadzieję, że tak naprawdę nie jest tak źle.

Mrówka
źródło
9
Nie do końca się z tym zgadzam. Byłoby miło, gdyby strlcpyi strlcatzgłosił jakiś rodzaj błędu, gdyby zderzył się z limitem rozmiaru bufora docelowego. Chociaż możesz sprawdzić zwracaną długość, aby to sprawdzić, nie jest to oczywiste. Ale myślę, że to drobna krytyka. Argument „zachęcają do używania ciągów C, więc są źli” jest głupi.
Omnifarious
7
„jak te funkcje mogą prowadzić do problemów z bezpieczeństwem” - fwiw Myślę, że problem polega na tym, że niektóre funkcje C są trudniejsze w użyciu niż inne. Niektórzy ludzie mają błędne przekonanie, że istnieje specjalny próg trudności, poniżej którego funkcja jest „bezpieczna”, a powyżej której jest „niepewna”. Tacy ludzie mają również zazwyczaj przekonanie, że strcpyjest powyżej progu, a zatem są „niepewne”, a ich preferowana funkcja kopiowania ciągów znaków (czy jest strlcpy, strcpy_sczy nawet strncpy) jest poniżej progu, a zatem jest „bezpieczna”.
Steve Jessop
31
Istnieje wiele powodów, dla których nie lubisz strlcpy / strlcat, ale nie podajesz żadnego z nich. Dyskusja o C ++ i Javie jest nieistotna. Ta odpowiedź po prostu nie jest pomocna w kwestii, której dotyczyło pytanie.
John Ripley,
24
@John Ripley: Po pierwsze, nie mówię o żadnym z nich po prostu dlatego, że nie znam żadnych powodów, dla których miałbym nie lubić strlcpy/strlcat. Można „nie lubić” ogólnej koncepcji łańcucha zakończonego zerem, ale nie o to chodzi. Jeśli znasz „wiele powodów, by nie lubić strlcpy/strlcat”, prawdopodobnie powinieneś napisać własną odpowiedź, zamiast oczekiwać, że będę w stanie czytać w myślach kogoś innego.
AnT
8
@John Ripley: Po drugie, pytanie odnosiło się konkretnie do niektórych rzekomych „problemów z bezpieczeństwem” strlcpy/strlcat. Chociaż wydaje mi się, że rozumiem, o co w tym chodzi, osobiście odmawiam uznania tego za „problemy z bezpieczeństwem” w obrębie tradycyjnego języka C, jaki znam. To stwierdziłem w mojej odpowiedzi.
AnT
31

Krytyka Ulricha opiera się na idei, że obcięcie ciągu znaków, które nie zostało wykryte przez program, może prowadzić do problemów z bezpieczeństwem, z powodu nieprawidłowej logiki. Dlatego, aby być bezpiecznym, musisz sprawdzić, czy nie ma obcięcia. Aby to zrobić dla konkatenacji ciągów znaków, oznacza to, że wykonujesz sprawdzenie według następującego wzoru:

Teraz strlcatsprawnie robi to sprawdzenie, czy programista pamięta o sprawdzeniu wyniku - abyś mógł bezpiecznie z niego korzystać:

Ulrich chodzi o to, że skoro musisz mieć destleni sourcelenwokół (lub przeliczyć je, co strlcatskutecznie robi), równie dobrze możesz po prostu użyć bardziej wydajnego memcpy:

(W powyższym kodzie dest_maxlenjest to maksymalna długość łańcucha, który może być przechowywany dest- o jeden mniej niż rozmiar destbufora. dest_bufferlenTo pełny rozmiar dest buffer).

kawiarnia
źródło
13
Czytelność kodu Dreppera jest zła. Dzięki strlcpy (lub dowolnej funkcji str) wiem bezpośrednio, że kopiuję łańcuch C zakończony 0. Dzięki memcpyniej może być dowolny typ pamięci i mam dodatkowego wymiaru, aby sprawdzić, próbując zrozumieć kod. Miałem starszą aplikację do debugowania, w której wszystko zostało zrobione z memcpy, to była prawdziwa PITA do poprawienia. Po przeniesieniu do dedykowanej funkcji String jest znacznie łatwiejszy do odczytania (i szybszy, ponieważ wiele niepotrzebnych strlenmożna usunąć).
Patrick Schlüter
3
@domen: ponieważ rozmiar do skopiowania jest już znany, więc memcpy()jest wystarczający (i potencjalnie bardziej wydajny niż strcpy()).
kawiarnia
1
Cóż, mylące jest posiadanie tego w operacjach na łańcuchach. O ile wiem, efektywność zależy od wdrożenia i nie jest ustandaryzowana.
domenę
8
@domen: memcpy() to operacja na łańcuchu - w końcu została zadeklarowana w <string.h>.
kawiarnia
2
@domen Zgadzam się, że istnieje możliwość nieporozumień, ale rzeczywistość jest taka, że ​​praca z napisami C i tak działa w zasadzie z pamięcią surową. Zapewne wszystkim byłoby lepiej, gdyby ludzie w ogóle przestali myśleć o C jako o „łańcuchach” (w odróżnieniu od innych ciągłych fragmentów pamięci).
mtraceur
19

Kiedy ludzie mówią „ strcpy()jest niebezpieczny, użyj strncpy()zamiast tego” (lub podobne stwierdzenia dotyczące strcat()itp., Ale zamierzam się strcpy()tutaj skupić), mają na myśli, że nie ma żadnych ograniczeń strcpy(). Dlatego zbyt długi ciąg spowoduje przepełnienie bufora. Mają rację. Użycie strncpy()w tym przypadku zapobiegnie przepełnieniu bufora.

Wydaje mi się, że to strncpy()naprawdę nie naprawia błędów: rozwiązuje problem, którego dobry programista może łatwo uniknąć.

Jako programista C musisz znać rozmiar docelowy, zanim zaczniesz kopiować łańcuchy. Takie jest założenie strncpy()i strlcpy()ostatnie parametry: dostarczasz im ten rozmiar. Możesz również poznać rozmiar źródła przed skopiowaniem ciągów. Następnie, jeśli miejsce docelowe nie jest wystarczająco duże, nie dzwoństrcpy() . Albo zmień przydział bufora, albo zrób coś innego.

Dlaczego mi się nie podoba strncpy()?

  • strncpy() w większości przypadków jest złym rozwiązaniem: Twój ciąg zostanie obcięty bez powiadomienia - wolałbym napisać dodatkowy kod, aby rozwiązać ten problem samodzielnie, a następnie podjąć działania, które chcę wykonać, zamiast pozwolić, aby jakaś funkcja zdecydowała o tym ja o tym, co robić.
  • strncpy()jest bardzo nieefektywny. Zapisuje do każdego bajtu w buforze docelowym. Nie potrzebujesz tych tysięcy '\0'na końcu celu.
  • Nie zapisuje zakończenia, '\0'jeśli miejsce docelowe nie jest wystarczająco duże. Więc i tak musisz to zrobić sam. Złożoność tego nie jest warta zachodu.

Teraz dochodzimy do strlcpy(). Zmiany w stosunku strncpy()do tego poprawiają, ale nie jestem pewien, czy specyficzne zachowanie strl*uzasadnia ich istnienie: są one zbyt szczegółowe. Nadal musisz znać rozmiar docelowy. Jest bardziej wydajna niż strncpy()dlatego, że niekoniecznie zapisuje do każdego bajtu w miejscu docelowym. Ale to rozwiązuje problem, który można rozwiązać wykonując: *((char *)mempcpy(dst, src, n)) = 0;.

Nie sądzę, żeby ktokolwiek to powiedział strlcpy()lub strlcat()mógł prowadzić do problemów z bezpieczeństwem, co oni (i ja) mówią, że mogą powodować błędy, na przykład, gdy spodziewasz się, że zamiast jego części zostanie napisany pełny ciąg.

Głównym problemem jest tutaj: ile bajtów skopiować? Programista musi o tym wiedzieć, a jeśli tego nie zrobi, strncpy()albo strlcpy()nie będzie go uratować.

strlcpy()i strlcat()nie są standardowe, ani ISO C, ani POSIX. Dlatego ich użycie w programach przenośnych jest niemożliwe. W rzeczywistości strlcat()ma dwa różne warianty: implementacja Solaris różni się od innych dla przypadków skrajnych o długości 0. To sprawia, że ​​jest jeszcze mniej użyteczna niż w innym przypadku.

Alok Singhal
źródło
3
strlcpyjest szybszy niż memcpyna wielu architekturach, zwłaszcza jeśli memcpykopiuje niepotrzebne dane końcowe. strlcpyzwraca również ilość utraconych danych, co może pozwolić na szybsze odzyskanie danych przy mniejszej ilości kodu.
1
@jbcreix: Chodzi mi o to, że nie powinno być żadnych danych do pominięcia, naw moim wywołaniu memcpy jest dokładna liczba bajtów do zapisania, więc wydajność również nie stanowi większego problemu.
Alok Singhal
5
A jak masz to n? Jedyne n, które możesz znać z góry, to rozmiar bufora. Oczywiście, jeśli sugerujesz ponowne zaimplementowanie za strlcpykażdym razem, gdy tego potrzebujesz, memcpyi strlento też jest w porządku, ale po co kończyć na strlcpy, nie potrzebujesz też memcpyfunkcji, możesz skopiować bajty jeden po drugim. Implementacja referencyjna w normalnym przypadku wykonuje pętlę tylko raz, co jest lepsze dla większości architektur. Ale nawet jeśli najlepsza implementacja używa strlen+ memcpy, to nadal nie ma powodu, aby nie musieć ponownie implementować bezpiecznego strcpy.
1
Alok, w przypadku strlcpy, gdy przeglądasz ciąg wejściowy tylko raz w dobrym przypadku (który powinien być większością) i dwa razy w złym przypadku, za pomocą swojego strlen+ memcpyprzechodzisz nad nim zawsze dwa razy. Jeśli to robi różnicę w praktyce, to inna historia.
Patrick Schlüter
4
"* ((char *) mempcpy (dst, src, n)) = 0;" - Prawidłowo - Tak, oczywiście poprawnie, nie. Nie
udało się
12

Myślę, że Ulrich i inni uważają, że da to fałszywe poczucie bezpieczeństwa. Przypadkowe obcięcie ciągów może mieć wpływ na bezpieczeństwo innych części kodu (na przykład, jeśli ścieżka systemu plików zostanie obcięta, program może nie wykonywać operacji na zamierzonym pliku).

jamesdlin
źródło
8
Na przykład klient poczty e-mail może skrócić nazwę pliku załącznika wiadomości e-mail z malware.exe.jpgdo malware.exe.
Chris Peterson
1
@ChrisPeterson Dlatego dobry programista zawsze sprawdza zwracane wartości, aby w przypadku funkcji strl * wiedzieć, czy dane zostały obcięte i odpowiednio działać.
Tom Lint
„Ulrich i inni myślą, że da to fałszywe poczucie bezpieczeństwa…” - Lol… w międzyczasie Ulrich i przyjaciele regularnie pojawiają się w BugTraq i Full Disclosure z ich jednorazowymi wydarzeniami. Powinni używać bezpieczniejszych funkcji i unikać większości swoich problemów. Wtedy mogą zacząć mówić innym, jak napisać bezpieczniejszy kod ...
jww
7

Istnieją dwa „problemy” związane z używaniem funkcji strl:

  1. Musisz sprawdzić zwracane wartości, aby uniknąć obcięcia.

Autorzy szkiców standardu c1x i Drepper twierdzą, że programiści nie sprawdzą zwracanej wartości. Drepper mówi, że powinniśmy w jakiś sposób znać długość i używać memcpy i całkowicie unikać funkcji łańcuchowych. Komitet standardów twierdzi, że bezpieczny strcpy powinien zwracać wartość niezerową po obcięciu, chyba że _TRUNCATEflaga stanowi inaczej . Chodzi o to, że ludzie częściej używają if (strncpy_s (...)).

  1. Nie można używać na elementach innych niż stringi.

Niektórzy uważają, że funkcje łańcuchowe nigdy nie powinny się zawieszać, nawet po podaniu fałszywych danych. Wpływa to na standardowe funkcje, takie jak strlen, które w normalnych warunkach ulegają awarii. Nowy standard będzie zawierał wiele takich funkcji. Testy mają oczywiście spadek wydajności.

Zaletą proponowanych funkcji standardowych jest to, że możesz wiedzieć, ile danych przegapiłeś dzięki funkcjom strl .


źródło
należy pamiętać, że strncpy_snie jest to bezpieczna wersja, strncpyale w zasadzie strlcpyzamiennik.
6

Nie sądzę strlcpyi uważam, że strlcatjest to niebezpieczne, a przynajmniej nie jest to powód, dla którego nie są one zawarte w glibc - w końcu glibc zawiera strncpy, a nawet strcpy.

Krytyka, jaką otrzymali, była taka, że ​​są rzekomo nieskuteczni, a nie niepewni .

Według dokumentu Secure Portability autorstwa Damiena Millera:

Strlcpy i strlcat API poprawnie sprawdzają granice bufora docelowego, kończą nul we wszystkich przypadkach i zwracają długość ciągu źródłowego, umożliwiając wykrycie obcięcia. To API zostało przyjęte przez większość nowoczesnych systemów operacyjnych i wiele samodzielnych pakietów oprogramowania, w tym OpenBSD (skąd pochodzi), Sun Solaris, FreeBSD, NetBSD, jądro Linuksa, rsync i projekt GNOME. Godnym uwagi wyjątkiem jest standardowa biblioteka GNU C, glibc [12], której opiekun stanowczo odmawia włączenia tych ulepszonych API, nazywając je „strasznie nieefektywnymi bzdurami BSD”[4], pomimo wcześniejszych dowodów na to, że są one szybsze w większości przypadków niż API, które zastępują [13]. W rezultacie ponad 100 pakietów oprogramowania obecnych w drzewie portów OpenBSD utrzymuje własne zamienniki strlcpy i / lub strlcat lub równoważne API - nie jest to idealny stan rzeczy.

Dlatego nie są dostępne w glibc, ale nie jest prawdą, że nie są dostępne w systemie Linux. Są dostępne w systemie Linux w libbsd:

Są spakowane w Debianie i Ubuntu oraz innych dystrybucjach. Możesz też po prostu pobrać kopię i użyć w swoim projekcie - jest krótki i na licencji:

rsp
źródło
3

Bezpieczeństwo nie jest wartością logiczną. Funkcje C nie są całkowicie „bezpieczne” lub „niepewne”, „bezpieczne” lub „niebezpieczne”. W przypadku nieprawidłowego użycia prosta operacja przypisania w C może być „niepewna”. strlcpy () i strlcat () mogą być używane bezpiecznie (bezpiecznie) tak samo, jak strcpy () i strcat () mogą być bezpiecznie używane, gdy programista zapewnia niezbędne gwarancje prawidłowego użycia.

Głównym punktem z wszystkich tych funkcji ciągów C, standard i nie-tak-standard, to poziom, do którego one bezpieczne / bezpieczne użytkowanie łatwa . strcpy () i strcat () nie są trywialne w bezpiecznym użyciu; dowodzi tego liczba razy, kiedy programiści C popełnili błąd przez lata i pojawiły się paskudne luki i exploity. strlcpy () i strlcat () oraz strncpy () i strncat (), strncpy_s () i strncat_s (), są nieco łatwiejsze w użyciu, ale nadal nie są trywialne. Czy są niebezpieczne / niepewne? Nie więcej niż memcpy (), gdy jest używane nieprawidłowo.

rswindell
źródło