Biztonságos programozás: a versenyhelyzetek megelőzése

``Mallorynak egy lopott jelszóval sikerült bejelentkeznie egy fontos szerverre, amely Linuxot futtatott. A shell egy meglehetősen szigorú shell volt, de Mallory tudta, hogy hogyan okozzon gondot. Mallory feltelepített és futtatni kezdett egy egyszerű programot, amelynek volt egy nagyon furcsa szokása -- gyorsan hozogatott létre és törölt ki különböző symlink-eket a /tmp könyvtárban, processzek sokaságát felhasználva. Mallory programja sok olyan symlink-et hozott létre, majd törölt le, amely a /etc/passwd filera mutatott.

Ezen a fontos szerveren biztonsági elővigyázatosságból minden nap futtattak Tripwire-t -- pontosabban egy régebbi verziót, a 2.3.0-ást. A Tripwire egy olyan biztonsági program, amely detektálja, ha valaki piszkálta a fontos fileokat. Amikor a Tripwire futni kezdett, megpróbált létrehozni egy átmeneti filet (temp file) a /tmp könyvtárban, úgy ahogy azt számos másik program is teszi. A Tripwire megvizsgálta, és úgy találta, hogy nincs ``/tmp/twtempa19212'' file, így azt gondolta, hogy ez pont jó lesz neki átmeneti filenak. Csakhogy miután a Tripwire ellenőrzött, Mallory programja létrehozott egy symlinket ezen a néven. Ez nem véletlen baleset volt, Mallory programja úgy lett tervezve, hogy azokon a neveken hozzon létre symlink-eket, amelyet a Tripwire többnyire használ. Ezután a Tripwire megnyitotta a file-t és elkezdte bele írni az átmeneti információkat -- de ahelyett, hogy egy új, üres filet kezdett volna írni, felülírta a /etc/passwd filet. Ezután senki -még az adminisztrátorok sem- tudtak belépni a rendszerbe, mert a passwd file megsérült. Lehetett volna rosszabb is, hiszen Mallory bármelyik filet felül tudta volna írni, akár egy a szerveren tárolt fontos file-t is.A történet kitalált, a Mallory név megszokott név a támadóknak. Viszont a támadás, és a sebezhetőség ilyen jellegű kihasználása, nagyon is gyakori. A gond az, hogy a legtöbb program támadható a versenyhelyzet (race condition) névre hallgató biztonsági hiba kihasználása folytán.''

``A versenyhelyzet bemutatása

1. Egy triviális C állítás:

b = b + 1;

Nagyon egyszerűnek látszik, nem? Tegyük fel, hogy két szál (thread) futtatja ezt a kódot, ahol is a ``b'' változó megosztott a két szál között és a változó kezdőértéke ``5''.

2.) A kód végrehajtásának egy lehetséges módja:

(szál1) betölti a ``b''-t a szál1 egyik regiszterébe

(szál2) betölti a ``b''-t a szál2 egyik regiszterébe

(szál1) hozzáad ``1''-et a szál1 regiszteréhez, és így kiszámolja az értéket, ami ``6''

(szál2) hozzáad ``1''-et a szál2 regiszteréhez, és így kiszámolja az értéket, ami ``6''

(szál1) letárolja a regiszterben található értéket (6) a ``b''-be

(szál2) letárolja a regiszterben található értéket (6) a ``b''-be

5-tel indultunk, és mindegyik szál 1-et adott hozzá, majd a végén 6-ot kaptunk... nem a várt 7-et. A probléma az, hogy a két folyamat zavarta egymást, és ez okozta a rossz választ.''

Akit érdekel a biztonságos programozás, a race condition szituációk kiküszöbölése, az olvassa el a cikket az IBM DeveloperWorks oldalain itt.

Hozzászólások

...de az esély megvan ugye :)

Én ugy szoktam, hogy ha pl a programom neve hup, akkor megkrealja a /tmp/hup/ alkonyvtarat, a jogait joóol lekorlatozza, es abba dolgozik... Ha nem tudja megcsinálni a könyvtárat, akkor szol, de ha egyszer megcsinálta, akkor az ott van, és nem lehet ilyen néven odapiszkálni.

szerintem ez megoldja a problémát, nemde? :) Főleg akkor van ez így, ha root-ként a fut a progi, de egyébként is...

üdv

otto

Ha tudják, hogy így csinálod, akkor az adott könyvtárban, és nem a /tmp-ben kell generálgatni a fájlokat :)

A kérdés érdekes: mikor lehetek biztos abban, hogy az adott fájlt _én_ hoztam létre?

Mondjuk egy S_ISREG(), esetleg egy S_ISLINK() makró kilövi a symlink-es problémát...

> Ha tudják, hogy így csinálod, akkor az adott könyvtárban, és nem a /tmp-ben kell generálgatni a fájlokat :)

Igen ott kellene, de mivel annak a jogait lekorlátozom, ezért nem tudnak belépni, nemhogy még kreálni :) Azért teszem alkönyvtárba, mert a /tmp jogait nem akarjuk piszkálni, de a /tmp/hup simán lehet chmod 000, és a root cronja használhatja...

Ha az mkdir() függvényhívás, vagy shell szkriptből mkdir parancs (-p opció nélkül!) sikeres volt, akkor tiéd a pálya és jó vagy, de mi van, ha nem volt sikeres, mert az adott könyvtár már létezik? Akkor mással kell próbálkoznod. Mindezt lehetőleg véletlenszerű, kiszámíthatatlan módon a DoS támadások elkerülése végett. A legtöbb program számára elvárás, hogy egyszerre több felhasználó, illetve egy felhasználó több példányban is futtatni tudja, anélkül, hogy bekavarnának egymásnak.

Mindezt szerencsére nem kell neked leprogramoznod, mert létezik már okosan megírt mkstemp() és mkdtemp() függvény, valamint mktemp parancssori progi shell szkriptből történő használatra. Lásd man 3 mkstemp, man 3 mkdtemp, man 1 mktemp, és mellesleg a man 3 mktemp végén a hatalmas figyelmeztetést hogy az viszont miért elavult.

A fenti megoldás a 600/700 jogot is okosan beállítja, ugye sokan bután állítják be, fájl vagy könyvtár létrehozása és _utána_ chmod, amikor is van egy kis időablak, amíg másvalaki beléphet a könyvtárba vagy megnyithatja a fájlt és utána korlátlanul olvashatja, a chmod ezen már nem segít. Szóval a létrehozás pillanatában kell a megfelelő jogokat adni a fájlra.

Szóval tanulság: először ismerni kell a problémát, utána pedig ismerni kell a megoldására már létező eszközöket, saját változatok hegesztése helyett.

Megjegyzendő továbbá, hogy sok esetben érdemes megfontolni /tmp helyett valami más könyvtár használatát, ahova csak az adott felhasználó írhat. Például ha egy többfelhasználós rendszeren írok egy szkriptet root-ként, amiről tudom, hogy csak a root fogja futtatni, és egyszerre csak egy példányban, akkor ahelyett, hogy az mktemp-et nekiállnék okosan használni, simán rakhatom mondjuk /root vagy /var/lib/progineve vagy akármi ilyesmi alá is az ideiglenes fájlt.

Az S_ISREG() és társaival vigyázni kell. Ha fstat(), vagyis megnyitott fájlra hívod meg, akkor oké. Ha viszont fájlnévvel operálsz, akkor vigyázni kell a stat() és lstat() közti óriási különbségre (előbbi követi a szimlinkeket és a célfájlt statolja, utóbbi magát a szimlinket).

De ugye nem feledkeztünk meg arról, hogy az egésznek az elején kell egy-két apróság:

umask 077

LANG=C ; export LANG (hogy ne etessenek meg pl egy félre*szott nyelvi beállítással)

és csak ez után jöhet az a nyomorult

mkdir /tmp/hup.$$ || { echo Anyád! >&2 ; exit 99 ; }

stb. Nyilván ha az ember nem shell-lel csinálja, akkor is hasonló lépések kellenek (umask(); setlocale();mkdir() )

Pont azért van az mktemp az mkdir helyett hogy többek közt az umaskkal ne kelljen szórakoznod, és hogy ne csak egy könyvtárnevet próbálj ki, hanem sokat. Gondolom, ha foglalt az adott könyvtár, akkor nem kilépést vársz a programtól, hanem akkor is normális működést, valami más könyvtár alatt, nemde?

A LANG mit tud itt bekavarni? Szerintem az ég-világon semmit.

Szerintem itt a "megtiltja magának" alatt arra gondolt whitehawk, hogy miután a megfelelő capability-t eldobta, utána már ő is csak olymódon fér a fájlokhoz, mint bármely más halandó, nem 0 uidű user, és a 0 uid is csak egy lesz a sok közül...

>A LANG mit tud itt bekavarni? Szerintem az ég-világon semmit.

pl.:

#LANG=C

#date

Wed Oct 13 08:45:36 CEST 2004

#LANG=hu_HU

#date

2004. okt. 13., szerda, 08.45.40 CEST

#

Vedd észre, hogy a napok, hónapok neveit másként írja ki... én már szívtam meg ilyen miatt :(

> Ha az lstat azt adja, hogy az a tiéd, akkor azt aligha tudta más elhappolni előled.

Te itt erősen építesz valamire, amire nem kéne: konkrétan arra, hogy a fájl az egy sticky-bites könyvtárban van. Ami bizony nem mindenütt alapértelmezés. (Tekintve, hogy a példában /etc/password van, ez ugyanúgy lehet Linux, mint pl. HP-UX is - itt speciel a /tmp védelme a rendszergazdára van bízva, aki ezt vagy elfelejti, vagy nem.)

> Pont azért van az mktemp az mkdir helyett hogy többek közt az umaskkal ne kelljen szórakoznod

Ez igaz lehet, ha _csak_ Linux alatt futó programot akarsz írni. Pl. az én FreeBSD-men mktemp ugyan van, ezzel szemben -p opciója nincs. Tudtommal nagyon sok kereskedelmi UNIX-ban egyáltalán nincs mktemp. No most. Biztonságos programot akarunk írni, de nem egy hátrány ha nem feledkezünk meg arról, hogy az a program holnap már lehet, hogy egy másik rendszeren kell hogy fusson. (És nem kell feltétlenül arra gondolni, hogy "Már miért cserélnénk le a tökéletes Linuxunkat?", hisz mint a "Kihull a GNOME a Slackware-ből" cikkben foglaltak is jelzik, néha még Linux-LInux között is vannak programozói szemmel hatalmas különbségek. Hát még ha véletlenül *BSD-re, HP-UX-ra netán Solarisra kell a programot átvinni. Szóval:

1) óvatosan azzal az mktemp -p -vel

2) egy korrekt umask beállítása az elején sose árt

3) a LANG-ot még már elmagyarázta más :-) (OK, biztonsági problémát okozó LANG beállítást most nem tudok, de szoftverhibát okozhat, és pl. biztonsági problémát okozó TERM-ről (asszem ncurses + ftp? kapcsán) már volt szó a világtörténelemben.

4) Én egyébként a programtól igenis kilépést várok foglalt könyvtár esetén, ugyanis normális esetben ilyen könyvtárnak ott nem kellene lennie, az abnormális esetet viszont minél hamarabb észre kell venni. Tudom, így a dolog DOS-olható -> node ez abnormális eset.

Oké, tudom mi az a LANG, de azt nem értem, hogy mi köze a biztonságos átmeneti fájl létrehozásához.

Ha azt akarod, hogy a szkripted tuti pont úgy fusson, ahogy akartad, akkor a LANG csak egy a tucatnyi változó közül, amit az elején be kell állítanod.

info libc

- Macro: int O_EXCL

If both `O_CREAT' and `O_EXCL' are set, then `open' fails if the

specified file already exists. This is guaranteed to never

clobber an existing file.

Zsiraf