Nem biztonságos C program írása

Címkék

Az informIT egy három részes cikksorozatot tett közzé a C-ben programozás buktatóiról. "A C programozási nyelv használatát gyakran okolják a nem biztonságos kód miatt. Ez nem teljesen jogos vád; az OpenBSD-hez hasonló projektek megmutatják, hogy lehetséges biztonságos C kódot írni."

A cikkek: Part 1: Error Checking, Part 2: Integer Issues, Part 3: Buffers and Strings

Hozzászólások

lol
Nem lehet a nyelv hibás, amíg valaki felelőtlen.
Általában nem a kés a hibás a gyilkosságban.

:) Ja, mert gondolkodni kell rajta, ezért nem biztonságos. Egyébként valóban lehet olyan progit írni benne ami nagyon jó támadási lehetőséget ad, de cserébe jóval gyorsabb mint azok a nyelvek amelyek nem használnak ilyen "nem biztonságos eszközöket". Szerintem a pointer a világ egyik legnagyobb ötlete, cserében jól el lehet vele szúrni a memóriakezelést pl. de mi másban írnának oprendszert pl? ASM az még vadabb, C++ az ugyanez ebből a szempontból a java meg nem erre való. Tlán még a fortran lenne jó, de az a nyelv meg annyira speecifikus, hogy emiatt alkalmatlan szerintem(matekra zseniális, jobb mint a C).

azért a C++-hoz képest sokminden újdonság és változás van, valójában szó sincs a C++ továbbfejlesztéséről, hanem inkább egy új nyelv tervezéséről, melyben sok C++ dolgot újragondoltak, újraterveztek, kihajítottak és sok beütése van a java, c#, python és hasonló nyelvekből. Egyszer utánaolvasgattam, meg kipróbálgattam és nekem tetszett.

\begin{half-offtopic}

na igen.. ezért szeretem a c++-t. ha akarom, olyan alacsony szintű, mint a c, ami jó, mert általában gyorsabb kódot lehet írni, viszont kurvára kell figyelni, mert egy kis apróság aztán ott a segfault, memory leak, vagy valami rosszabb.. ha meg akarom, különböző osztályokkal megoldom a memóriakezelést, ami ilyen szempontból nem annyira veszélyes, viszont lassabb.. mindez csak attól függ, hogy mennyire vagyok luta.. igazából szerintem a c++ talán mindenhova jó, bár kernelt azért nem írnék benne, azért az marad a c területe.. java-t se szeretem, a legidegesítőbb az hogy nincs delete, hanem a GC megeszi valamikor.. valyon emiatt van, hogy mindegyik javas program eszméletlen sok memóriát eszik? azureus is megeszik 600 megát, ha már fut egy ideje.. ehez képest, hogy a javac 1,3 GB memóriát evett, amikor éppen icedtea-t fordítottam, az má semmi. a pythont még szeretem, jó kis nyelv, bár mondjuk az egy más műfaj.. egyszerűbb programocskák számára nagyon jó, de én azért komolyabb programot nem írnék benne.. bár ez magánvélemény.

\end{half-offtopic}

I hate myself, because I'm not open-source.

Offtopic-ontopic:

És ha eleget szívtál a C++ hülyeségeivel, például a trágya template-ekkel (hm, itt-ott optimalizálok a sablonon, erre borul a teljes kód, mert nem "kompatiblis" visszafele, a sablon felhasználójának eddig nem kellett valamilyen függvénnyel, vagy operátorral rendelkeznie, és most igen, és mire ez kiderül, lemegy a nap), plusz hasonlók, akkor rájössz, hogy az Ada (2005) sokkal jobb választás...

ennyi erővel brainfuck a király ;) egyébként jók azok a template-ek, csak tudni kell velük bánni.. template-ek nélkül nem lenne stl sehol, azt meg nem tudom kinek van kedve minden tipushoz konténert írni.. vagy esetleg egy kis mágia a pointerekkel, hogy castoljuk void*-ba, aztán amikor kell, vissza? inkább template..

azt ada-t annyira nem ismerem, első ránézésre olyan, mint valami feltúrbózott pascal.. a pascalt meg nem szeretem..

I hate myself, because I'm not open-source.

A fordítási idejű hibákat preferálom, és nem szeretem, ha egy-egy hiba "hol van, hol nincs". Itt a sablon-szerződés modell hiányzik nagyon. Az adában sokkal több hiba kiderül már fordításkor, mint a C++ esetén.

Mondjuk a sablonok között van némi különbség, a C++ esetén Turing-teljes, de az Adánál szerintem nem, legalábbis nem tudok elképzelni a fentiekkel azonos kódot mondjuk faktoriális kiszámítására. Mérget persze nem vennék rá.

A válaszom meg kb. olyan volt, mint amire válaszoltam :)

Én voltam. Ada 2005 fordító van fenn, és nyugi, az eddigi tananyag egy-az egyben megmaradhat, hiszen a régi kódok továbbra is működnek. Csak nálam a gyakon ada 2005 lesz inkább, mivel az alapok azonosak a két verzióban. Pointerek, taszkok, ahol eltérések lehetnek már az eltén megkövetelt anyagban.

A $ROOT/etc/zorp/policy.py tartalmazza, hogy milyen zónák vannak, milyen szolgáltatások mehetnek oda be és onnan ki, az authentikációs beállításokat, a NAT-olást, a Service-ek által ténylegesen használt osztályokat. Ehhez egy csomó modult be kell húzni. Az osztályok származtatás révén beállítják a végleges tulajdonságokat, illetve esetleg felülírják az egyes alapértelmezett viselkedéssel bíró tagfüggvényeket. Ez utóbbiakat a Zorp futása során meghívja. Az, hogy végülis ezek mind hogyan néznek ki, beállítás kérdése (amit az üzemeltető határoz meg), ezért mondtam, hogy konfig, nem tudok arról, hogy más körülmények között is használatos lenne a Python kód. De javíts ki, ha tévedek :)

valyon emiatt van, hogy mindegyik javas program eszméletlen sok memóriát eszik?

Igen, emiatt. A szemetgyujtes eroforrasigenyes folyamat, amig van szabad memoria, nem biztos, hogy megeri lefuttatni...

A jvm-et meg lehet parameterezni, be lehet allitani a maximalis heap meretet, stb.

----------------------
"ONE OF THESE DAYS I'M GOING TO CUT YOU INTO LITTLE PIECES!!!$E$%#$#%^*^"

Azért az túlzás, hogy minden, ami java zabálja a memóriát. Gondolok itt mobiltelefonokra, blu-ray lejátszókra. Desktopon a jvm hajlamos kihasználni a rendelkezésre álló (bőséges) memóriát, gyorsítva a futáson. Másrészt java-ban hajlamosak a fejlesztők nagyvonalúan bánni a memóriával az alkalmazás fejlesztésekor. De jó jvm-mel és jó java kóddal nincsenek horribilis különbségek egy natív és egy jvm-ben futó alkalmazás között. Sőt, ha éppenséggel sok kicsi memóriadarabkára van szükség, a java kevesebbel is beérheti, mert nem töredezik, mint a c++ heap és a memóriafoglalás gyorsabb is, mert nem kell a helyet megkeresni.
Ettől függetlenül a java igazi erőssége az, hogy egy üzleti alkalmazást, információs rendszert hamar le lehet vele fejleszteni más nyelvekhez képest. És a mérnökhónaphoz képest pár giga ram és a gyors proci bagó. És a végén úgyis az adatbázis lesz a szűk keresztmetszet.

egyetértek, viszont azt is be kell látni (persze ez minden nyelvre igaz, de a JAVA-ra egy kicsit igazabb), hogy a szakértelem hiányában igazán takony kódot lehet írni, és akkor persze hogy zabálja az erőforást. Ellentétben más nyelvekkel a JAVA-ban már egy "hello word 2" szintű programhoz is "bonyolultabb" osztálydiagrammot kell megérteni.

Minden nyelv ami nem szorít korlátok közé szabadságot ad a csiszolt, tisztességesen megírt és a gány kódok készítésében is.

Ezt olvasom a Part1 elején:
A lot of languages these days include some mechanism for throwing exceptions. Exceptions are generally a bad idea.

Asszem a Part2-t már el sem olvasom. Ugyanis a kivételkezelés nagyon jó dolog. Elég gyakran előfordul, hogy valamilyen rutint csak hosszas előkészületek (pl. file megnyitása + olvasása) után lehet meghívni, de az előkészületek elbukhatnak, és akkor ki kellene lépni az adott blokkból. C-ben ezt kizárólag goto-val lehet helyettesíteni. Mivel a goto-t nem szeressük, én rendszerint egy

do {
} while(0)

blokkot használok, amiből hiba esetén break-kel ki lehet lépni. De mindenre ez sem jó, mert ha van benne egy switch, vagy egy másik while blokk, akkor azon belülről már nem lehet kiugrani. Szóval jók azok az exception-ök.

(Meg lehet nézni nyílt forrású kódokat, pl. OGG, vagy MP3 codec-et, és ott is használják a gonosz goto-t kivétel kezelésre.)

> de az előkészületek elbukhatnak, és akkor ki kellene lépni az adott blokkból.

http://www.nicemice.net/cexcept/


    Try {
        /* Maybe a Throw statement will be executed    */
        /* (maybe within a deeply nested function):    */
        /* Throw e;                                    */
        /* If so, execution jumps to the catch clause. */
    }
    Catch (e) {
        /* An exception was thrown and copied into e.  */
    }

Azért néha jól jön, nekem képanalízisnél volt olyan, hogy már fogalmam sincs hanyadik beágyazott ciklusból kellett kiugrani, hogy hobbá megvan amit akartam (azt hiszem a 6-egymásba ágyazott cilkus volt). Na onnan hogy lépsz ki? De azt aláírom, minden megírható goto nélkül, cask lehet hogy 3*-olyan hosszú lesz.

Ilyen esetben a goto hasznalata szerintem nem olyan egbekialto bun. Altalaban en azt nem szeretem, amikor olyan kodot latok, hogy van 345 db if, es mindegyik mogott ott a goto. Na attol a falra maszok! :)
--
Bárki aki aritmetikai módszerekkel akar előállítani egy véletlen számot, az a bűn állapotában leledzik.

csak gondolkodni is kell, ami sokaknak nem megy

Ezt rossz helyre írtad.
A "return"-ös megoldás talán rosszabb, mint a goto. Képzlejünk el egy egyszerű esetet, amikor pl. egy fájlt kell folyamatosan olvasni, ellenőrizni az olvasott dolgokat, és azokkal valamilyen bonyolultabb műveletet csinálni. Hova is kellene return, vagy kivétel?
Pl.:
  - megynitom a fájlt - elbukhat, az előzőleg foglalt változókat föl kell szabadítani, esetleg hibaüzenetet (vagy debug üzenetet) kiírni, majd return
  - olvasunk pár bájtot - elbukhat, az előzőleg foglalt változókat föl kell szabadítani, esetleg hibaüzenetet (vagy debug üzenetet) kiírni, majd return
  - az olvasás sikeres volt, de érvénytelen adatot olvastunk - az előzőleg foglalt változókat föl kell szabadítani, esetleg hibaüzenetet (vagy debug üzenetet) kiírni, majd return
...
  Vagyis return előtt mindig el kell végezni ugyanazt a takarítást. Sokkal egyszerűbb, ha ezt a takarító részt egyszer írjuk csak meg, és "goto"-val mindig odaugrunk, ha hiba volt. Már csak azért is, mert ha a takarító kódnak valamiért változnia kell (időközben foglatunk egy újabb változónak helyet, és azt is föl kell szabadítani), akkor csak egy helyen kell azt változtatni.

Nem mondom, hogy a "goto" szép dolog. Csak azt, hogy a "return" nem olyan rugalmas, mint a "goto". (Pl. a "do - while(0)" blokk kifejezetten szép dolog, csak szintén nem rugalmas.) Illetve azt, hogy a más nyelvekben meglévő kivétel kezelés szép is, és rugalmas is. És lehet programot írni kivételkezelés nélkül is, de ha a C is tudna ilyet, akkor én biztosan használnám. (A cexcept.h-t majd kipróbálom.)

Konkretizaljuk a peldat, konfig fajl parsolasa.

0) Meghivom az int parse_config(conf *conf, char *cfg) fuggvenyt.
1) megnyitom a fajlt. Mivel az open/fopen egybol NULL-lal ter vissza ha gaz van, nincs mit felszabaditani - error ertekkel, vagy epp errno-val visszaterni, nincs tobb teendo.
2) elokeszitjuk a deklaralt puffereket: lefoglaljuk, kinullazzuk.
3) olvasunk 1 byte-ot: az osszes file-olvaso fuggveny az olvasott byte-k szamaval ter vissza, illetve hibakoddal (0, -1), ha gaz van. Ez esetben a fajlt lezarjuk (ez felszabaditja az illeto strukturat stream nyitas eseten), a puffereket felszabaditjuk (itt johet jol egy #define), majd visszaterunk ugyancsak hibakoddal.
4) addig olvasunk, amig hibara nem szaladunk, vagy sorvegjelet nem talalunk. Ha megvan a sorvegjel, elkuldjuk tokenizalasra, strcmp, if szerkezetek, strukturakitoltes - igazabol komoly hiba nem lehet.
5) kinullazzuk a puffert, es megyunk tovabb a fajlban
6) fajlvege eseten lezarjuk a fajlt. Olvasas eseten ez nem tud sikertelen lenni, tovabbi teendo nincsen.
7) pufferek felszabaditasa, OK jel visszaadasa.

Jol lathato, hogy osszesen a vegen kivul 1x kell puffereket felszabaditani - amikor olvasunk, es hibara szaladunk. Nyilvan azt a 2-3 sort ki lehet sporolni egy goto-val - de tul sok ertelme nincsen.
Persze ehhez jol kell tudni tervezni, es nem mindent a main fuggvenybol intezni - ez ugyanis problemassa teheti a feladat megoldasat.

En meg azt szoktam csinalni, (vessetek mokusok koze) hogy a puffereket a stackre hozom letre (alloca), igy ha nem szabaditom fel, sincs tul nagy gond, mert a stack automatikusan megsemmisul a return hatasara. Nyilvan tul nagy pufferekkel nem lehet dolgozni - de azok mas megkozelitest is kovetelnek. Azonban parszor tiz-husz karakter tarolasara ez tokeletes megoldas.
hibakoddal visszateres.

--


()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

exception kezelesre szerintem nyugodtan lehet goto-t hasznalni (szer.: marmint c-ben). jobb mint ossze-vissza obfuszkalni a kodot csak azert, hogy ne hasznaljunk goto-t (mert akkor nem lehet szep struktogrammot rajzolni a programhoz, vagy nem is tudom mi a pontos ok).

- Use the Source Luke ! -

Hát kerüld, és hidd azt, hogy jobb, átláthatóbb és szebb kódot írsz ettől... ;) Meg hidd, hogy ha goto kerül a kódba, akkor az rossz, átláthatatlan és csúnya lesz... ;)

Zsiráf

p.s.: amúgy ez a 'c64-en elment' ez szerinted mit jelent, mire érv? Vagy ez egy rejtett 'maradiság - haladóság' ellentét jelzése volt?

az OpenBSD-hez hasonló projektek megmutatják, hogy lehetséges biztonságos C kódot írni.

ololol

Jah, ott Hunger János névvel mutatkoztam be valami csajnak (és nem mellesled kollégátokként, mint Szintézis Zrt-s munkatárs, úgyhogy ne csodálkozz, ha jhunger@szintezis.hu címre mennek a spamek ;)), a web konferencián meg Kenyér Oszkár regisztrációval voltam. :)

Igazából Adolf a keresztnevem... ;P

Elolvastam a visszatérési értékek ellenőrzéséről szóló részt. Elég semmitmondó. Persze a klasszikus példa, hogy ellenőrizni kell a malloc visszatérését. Nomost látom a git forrásában (L.T. kódja), hogy a malloc hívások egy részénél nincs ellenőrizve a NULL visszatérés. Gondoljon ki-ki, amire akar.

Ekég ritka az olyan program, mint a git, amit tiszta C-ben írnak. Én is alapvetően C-t írok, amit C++-ként fordítok. És persze használok osztályokat, kivételeket. Az, hogy a kivételek erőforrásigényesek volnának, az éppenséggel lehet, de nem szempont.
--
CCC3


Java code, in particular, is often littered with try...catch blocks that do nothing but discard errors, but even in this case the exception mechanism has served a purpose—it has made the programmer aware that he isn't safely handling error conditions.

Ez a rész rávilágít, hogy a Jáva programozók többsége rosszul használja a kivételkezelést. A mondat második fele arra utal, hogy a szerző is a többséghez tartozik (vagy tartozna, ha programozna).

Konkrétan azzal van baj, hogy a kivételkezelésben sokkal gyakoribb az az eset, amikor a kivételet tovább kell adni, semmint, hogy el kellene kapni. Mégis a Jáva programok (és Jáva oktató tananyagok) tele vannak olyan rossz példákkal, amik elkapnak kivételeket, amikkel nem tudnak mit kezdeni ahelyett, hogy továbbadnák, és végső esetben hagynák a programot normálisan elszállni.
--
CCC3

"OpenBSD introduced strlcat, which is similar to strncat but returns the sum of both inputs. If the result of the function is greater than the third argument, truncation has occurred. This function can be found in the libc of each member of the BSD family (including Darwin/OS X), but not in glibc—because it's "inefficient BSD crap," according to the glibc maintainer."

csak nem mr tarball volt ez az idiota?

--
When in doubt, use brute force.

Jó nyelv a C de szerintem akkor is ez a legjobb: Malbolge ;)

---
Debian "lenny", 2.6.24-amd64