Bár én lennék ilyen okos!

Azt mondja a clang-tidy

warning: Call to function 'strcpy' is insecure
as it does not provide bounding of the memory buffer.
Replace unbounded copy functions with analogous functions
that support length arguments such as 'strlcpy'.
CWE-119 [clang-analyzer-security.insecureAPI.strcpy]
    strcpy (pd->st.naplcode, "ERR");

Mondjuk igaza is van: ki tudja, futás alatt esetleg meleg lesz és az a string literál esetleg megnyúlik, és úgy már nem fér bele az output mezőbe...

Hozzászólások

Szerkesztve: 2022. 01. 25., k – 21:59

Vicces ez a hupos WYSIWTF editor, hogy SQL szintaxisukent ismerte fel a code snippetet. :D

Szerkesztve: 2022. 01. 25., k – 22:37

Igen, ez az átka, amikor Tapicskoló Tamás édespiciegyetlenfiát átengedik kettessel C programozásból, hogy már lassan annyi warningot kell kiírni ezekre az esetekre, mint a mikrósütő leírásába, hogy ne rakj be macskát szárítani, de ha lehet, a konnektorba se nyúlj bele, mielőtt üzembe helyezed.

Az angol konnektoron van is kapcsoló, amit elvileg illik a készülék bedugása után felkapcsolni. Ha esetleg hozzáérnél a félig bedugott csatlakozó lábához. A csatlakozóban meg biztosíték, hogy a vezeték ne legyen túlterhető (a kismegszakító a falban levő vezetékhez van méretezve, nem ahhoz, amit bedugsz). Lehet fokozni a biztonságot (csak cserébe fél kilós bumszli szarok a dugók, amiből mindig csak egy adott irányba tud kijönni a madzag, fejjel lefele nem megy be).

2014 után jött képbe az strlcpy. Addig csak strncpy volt. Nagy különbség, hogy az strncpy bár nem írt túl a sizeof(dstbuf) területen, ellenben ha a string kitöltötte, akkor nem tett az utlsó dstpozícióba '\0' karaktert, ezáltal később egy tetszőleges string felolvasás már képes volt túlolvasni a pufferen, ezzel okozva információszivárgást.

Strlcpy és dstbuffer méret megadás helyett általános rossz gyakorlat az iskolapadban elsajátított strcpy. Egyszerű, de borzasztó sok sérülékenység forrása.

(Rust szerencsére az elejétől száműzte az ilyen veszélyes cuccokat, még az strlcpy sem fér bele.)

Tehát oda jutottunk, hogy manapság kókányolnak, hozzánemértők vallják magukat programozónak. Én nem vagyok programozó, de ha úgy hozza az élet, hogy programot írok, pl. egy műszer firmware-ét, akkor gyengébb pillanataimban szoktam gondolkodni is egy-egy megoldás kidolgozása kapcsán. Alázattal kell a problémához állni, végig kell diszkutálni egy csomó esetet, mert rossz programot azzal a hozzáállással lehet írni, hogy azt mondjuk, ezt egy idomított majom is meg tudja csinálni. Valóban? Extrém input paraméterek esetén is? Speciális esetekben is? Akkor is, ha valamit nem atomikusan kezelünk, s becsap közben egy olyan megszakítás, amelyik szintén piszkálja azt az izét?

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

a mai általános elfogadott "programozó" már nem ismeri azt a szót, hogy "megszakítás". Egy egyszerű színválasztó is az npm install color-wheel -el kezdődik, majd az egysoros import és mégegysoros apply -al oldja meg. Az, hogy a háttérben mi történik, miért kell egy color-wheel -hez 150mb egyéb library, az már nem érdekes.

Ha valami rossz, keres egy másik 150 megás színválasztó lib -et amit gond nélkül ránt be az előző mellé (törölni nem meri, mert fél, hogy odaver valaminek a hiánya)

// Happy debugging, suckers
#define true (rand() > 10)

"Első ránézésre jó ötletnek tűnt." Utólagos bölcsességel visszatekintve talán ilyesmit alkotnánk:


size_t strlimcpy(char *dst, const char *src, size_t dstlen) {
    if (dstlen<1) return 0;
    size_t maxlen= dstlen-1;
    size_t srclen= strnlen(src, maxlen);
    size_t cpylen= srclen<=maxlen? srclen: maxlen;
    if (cpylen>0) memcpy(dst, src, cpylen);
    dst[cpylen]= '\0';
    return cpylen;
}

Igen, a dilatációra egy programozónak is gondolnia kell. ;)

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

NevemTeve, szerintem nem jó, amit írtál. Mi az a cpylen? Nem srclen? Javítsd ki, ezért nem válaszban írok!

tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Jogos;)
Kifogásnak felhozhatom, hogy esetleg valaki belejavít, és kicseréli strlen-re. Például a strlcpy ezt csinálja: veszi a strlen(src)-t, (akkor is, ha sokkal hosszabb mint a dstlen), és azt is adja vissza. Ami jogos, mert esetleg valakit épp az érdekel, viszont problémás, mert esetleg valakit épp az nem érdekel.
Szerk: érdekesség: a strnlen-nek kicsit rossz a karmája, pl1 pl2

Nem, ez egy másik termék, amiben a 'hátha valakinek éppen nincs ilyen' kategóriában szerepel jópár függvény implementációja (vö: gnulib). Mivel szinte mindenkinek van a libc-ben 'strnlen'-je, valószínűleg sosem használta/tesztelte senki, arra már nem is emlékszem, hogy én miért próbáltam ki.

Tegyük fel, hogy a pd->st.naplcode egy 4 byte méretű inicializálatlan buffer. Pár hónap múlva jön valaki, és észreveszi, hogy a kódbázisban 157-szer ismétlődik az "ERR" string, ezért kiemeli egy konstansba. Persze azt már nem írja mellé, hogy a konstans nem lehet 4 byte-nál hosszabb. Aztán megint eltelik pár hónap, megint jön valaki, és átírja a konstanst "ERROR"-ra. Leteszteli a kódot, minden működik ahogy kell, ezért az új verzió kiadásra kerül. Aztán jön a meglepi, hogy néha picit több adat jön vissza az ERROR-nál.

Ki volt a hibás? Az első fejlesztő, aki okosabb akart lenni a clang-tidy-nál és csak azért is strcpy-t használt strlcpy helyett, a második fejlesztő, aki nem dokumentálta a konstansra vonatkozó megkötéseket, vagy a harmadik fejlesztő, aki nem nézte át a konstans 157 felhasználását, hogy sehol nem okoz gondot a string módosítása?

Jogos kérdések ezek. Tulajdonképpen azt is kérdezhetnénk, hogy lehet-e "elronthatatlan" programot írni. Ha azt nem is lehet, arra megkérhetjük a derék kollégákat, hogy amikor belerondítanak a programba, akkor futassák a statikus analizert. Ha nem is ezt a clang-tidy-t, hanem mondjuk a `gcc -fanalyzer -O2 -Wminden`-t vagy akár a `clang -O2 -Wminden`-t.

Ez  a `gcc -fanalyzer` olyanokat tud [ezt most emlékezetből írom, tévedhetek is a részletekben], hogy az sprintf-ben a %d szekvenciákat kielemzi, összeveti a paraméterek megengedett értéktartományával, és annak alapján szól, hogy az esetleg nem fér bele a bufferbe, hogy pl.:

if (i>=10 && i<=100) sprint(buff3byte, "%02d", i);

Igazán szépek ezek a toolok!

A gyakorlatban sokszor más a felállás. Egy élő példa a múltból, sok millió rekord feldolgozása esetén:

A specifikáció 50 mezős, változó mezőhosszúságú rekordról szól. A maximális rekordméret 2000.

Az adatokat több program kezeli sorban. Én ülök középen az egyik programommal.

Egyszerű dolog, hogy felveszek 2001 hosszú buffert, mert abba pont belefér a 2000 karakter. Használhatom az strcpy(), gets() és egyéb ördögtől való függvényt is. Mert az a legegyszerűbb.

Ha bármi félresiklik, akkor nem védelem és ellenőrzés kell, hanem a sorban az előttem levő programot kell javítani, mert nem teljesíti a specifikációt.

Ugyanitt előfordult, hogy eddig nem specifikált extra karaktereket adtam hozzá a rekordhoz. Ez sem bonyolult: A maximális hossz +5 = 2005 lett a specifikációban.

Ilyen a világ, csak olvasni kell tudni. Hasonló példa (ha nem tévedek) az MPEG2 formátum, amely a feldolgozáshoz szükséges buffer méretét is megadja. Aztán vagy leszarod, vagy működik.

Ha bármi félresiklik, akkor nem védelem és ellenőrzés kell, hanem a sorban az előttem levő programot kell javítani, mert nem teljesíti a specifikációt.

Ebből az igaz, hogy az előző elcseszett szoftver a specifikációja szerint kell hogy működjön. Ellenben a te programodnak ekkor

  1. vagy szó nélkül helyesen végrehajtja a hosszabb sort is
  2. vagy észlelhető hibaüzenet mentén ignorálja a hibás sort és a további beérkezőkre helyes eredményt ad tovább.

De hogy sunyin más memóriaterületre ráfutva tovább dolgozik, és onnantól előfordulhat, hogy a későbbi helyes inputra is már hibás eredményeket ad, ez a legrondább a lehetőségek közül.

Ha már van specifikáció

  1. Naplófájlt dolgoztam fel, de csak a sor elejéről indexeltem a dátumot. A fejlesztők néha az egész weboldalt beleputtyantották egy-egy sorba. Megérdeklődtem, hogy így mi lesz a maximális sorrhossz. Első válasz: Ja, azt nem korlátoztuk le? Másnapra megérkezett a korrekt második válasz is. Egy link egy MS oldalra, ahol kb. ez állt: A string maximális mérete megegyezik a diszk méretével. Csak a diszk méretét elfelejtették közölni. ;)
  2. Ha vertikális adatstruktúra is jelen van, akkor nem ignorálhatsz egy-egy sort.  Viszont a kiterjedtebb ellenőrzés néha meglepetéseket tartogathat.

Íme, egy újabb, életből ellesett példa. Változó mezőhosszúságú rekordokkal dolgozunk. Minden mező hossza és karakterkészlete ellenőrzött. Így néz ki egy cím:

...|Kossuth|utca|2|... további mezők, ami nem cím

Aztán jött egy karakterkészlet ellenőrzésen megbukott rekord is:

...|Kossuth|utca|2|Balra a b|uszme|gá|lló mell|ett bek|anyarodva 2|0 lépé|...

Álláspontom szerint ilyenkor a feldolgozást nem lehet elvégezni, hanem a kibocsátó szoftvert kell először kijavítani. Utána az adatokat.

És pont azért, amit írtál: hátha "a későbbi helyes inputra is már hibás eredményeket ad".

Mert  buszmegálló mellett jobbra kell kanyarodni és nem balra! :-D