(delsi) buffer overflow - prunik

Jaroslav Gratz xgratz01 na stud.fee.vutbr.cz
Neděle Srpen 6 03:41:38 CEST 2000


On Sat, 5 Aug 2000, Pavel Kankovsky wrote:

> Na segmentech to sice nastavit lze, ale na strankach nikoli. Jenze
> segmentovany model pameti se dneska uz nepouziva ani ve Windows. 

Kontrola segmentů je povinná a nedá se v chráněném režimu vypnout
(narozdíl od stránkování), takže nezbývá, než segmenty používat. Problém
je spíš v tom, jak se nastavují limity na segmenty. Nevím přesně jak to
dělá linux, ale současná praxe je asi taková:

|----------------instrukční segment-------------------|
                     |---------datový segment---------|
                                  |---stack segment---|

Prostě v rámci jednoho procesu se segmenty překrývají a je možné
beztrestně přepsat zásobník z datového segmentu a spustit kód na
zásobníku.

> Mozna az vyroste nova generace programatoru, u kterych nebude slovo
> "segment" vyvolavat traumaticke vzpominky na MS-DOS a 16-bitova
> Wokna...

Starý rozhodně nejsem, ale tohle si velmi dobře pamatuji...

> Nevim jak "obecne asi vsechny", ale gcc to dela pouze v pripade, ze jsou
> pouzity vnorene funkce, coz je jeho vlastni rozsireni jazyka (na zasobnik 
> se pak ulozi "trampolina", coz je maly kus kodu umoznujici vnorenou
> funkci volat jako by nebyla vnorena a pritom zajistit, aby vedela, kde se 
> na zasobniku nachazi zaznam nadrazene funkce).

Špatné řešení. Generovat za běhu programu dynamicky nějaký kód na zásobník
a ten potom spouštět je nesystémové a nepřehledné. Zásobník potom musí být
spustitelný se všemi důsledky, co z toho vyplývají. Když už měli vývojáři
gcc potřebu použít dynamicky generovaný kód, měli to udělat jinde než na
zásobníku.

> Neni mi z toho jasne, jak by se delalo, aby se predavane parametry dostaly
> na zasobnik, kdyz ten ma byt "pouze pro cteni". Uz vubec nechapu, kam
> chcete ukladat automaticke promenne, ktere se take dnes davaji na
> zasobnik. Krome toho nevim, jak byste chtel resit situaci, kdy ma program
> vice nez jeden zasobnik (coz napr. potrebuji programy pouzivajici interne
> nejaky druh paralelismu, napr. multithreaded programy).

Nešlo mi o to udělat zásobník pouze ke čtení, ale odělit datový segment od
segmentu zásobníku. Prostě úplně jednoduchý model paměti:

|-instrukční segment-|-----datový segment----|----stack segment----|
                     |-datový segment pouze pro čtení (není nutný)-|

Parametry by se na zásobník předávaly tak jako doteď, buď
push [promenna] nebo mov [ebp+n],reg. Čtení parametrů mov reg,[ebp+n].
Nepřínáší to žádnou režii navíc, protože registr ebp používá implicitně
segmentový registr ss. Při použití jiného než ebp registru je potřeba dát
před instrukci jednobajtový prefix pro změnu segmentu. Pro automatické
proměnné platí to samé a vytvořit více zásobníků není žádný problém.

Tímto se prakticky znemožní, aby proces mohl modifikovat zásobník. Pokud
není chyba v překladači, tak nevymyslíte v Cčku žádnou konstrukci, která
by skončila přepsáním zásobníku, protože typ proměnných které se budou na
zásobník ukládat je znám už v průběhu překladu a i kdyby nebyl, tak to
vůbec nevadí, protože nejdřív se ukládají proměnné a pak teprve návratová
adresa. Vlastní proměnné procesu jsou v datovém segmentu a tam už se s
nimi může dít cokoliv, ale nemůže to za žádných okolnostní (to znamená, že
jsem nic nevymyslel :)) skončit přepsáním zásobníku.

> Nicmene hlavni problem je ten, ze si asi myslite, ze jediny mozny skodlivy
> "buffer overflow" je ten, ktery prepise na zasobniku navratovou adresu (?
> nebo i neco jineho). To zdaleka neni pravda (i kdyz vetsina exploitu dela
> zrovna tohle). Predstavte si napr. nasledujici kusy kodu (predpokladam
> BUNO, ze promenne jsou ulozeny tak, ze preteceni b[] ovlivni promenne
> deklarovane pozdeji):
> 
> 1.
>      char b[...]; uid_t u = getuid();
>      /* nebezpecna manipulace s b[] */
>      if (!u) /* neco, co normalni uzivatel nesmi */

To máte pravdu, tady by to nepomohlo. Ale přepsání zásobníku a
překrývající se segmenty jsou mnohem nebezpečnější, protože dávají
útočníkovi možnost spustit jeho vlastní kód.

> Nikoli. Pres zasobnik se (aspon ve vetsine implmentaci jazyka typu C) nic
> nevraci: navratove hodnoty jsou predavany v registrech prip. -- specialne
> pokud se nevejde do vyhrazenych registru, protoze je to napr. velka
> struktura -- se ukladaji na misto urcene nejakym ukazatelem, ktery je
> predan jako skryty parametr.

Tím lépe, tohle by fungovalo i kdyby se datový a stack segment
nepřekrývaly a nebylo by potřeba nic nového vymýšlet.

> Samotne hlidani mezi segmentu nic nestoji, ale zmena segmentoveho registru
> je docela draha operace (i kdyz je to porad levnejsi nez uplny context
> switch s vysypanim TLB). Pouziti neobvykleho seg registru pro urcitou
> operaci taky neni zadarmo (jednobajtovy prefix), ale to uz by asi bylo
> snesitelne.

Změnit segmentový registr by nebylo potřeba prakticky nikdy, protože 386
má čtyři volně použitelné seg. registry ds,es,fs,gs a to je víc, než máme
segmentů. A co se týká prefixů na změnu segmentu, tak ty je nutné používat
pouze při přístupu k zásobníku přes jiný než ebp registr. Prakticky žádné
ztráty výkonu.

> > Result: ochrana proti buffer overflow exploitum by udelat sla, ale musely
> > by se dost radikalne predelat prekladace, vsechno znovu prelozit a stare
> > binarky by samozrejme nemusely fungovat. Do toho se nikomu nechce.
> 
> To sice mozna ano, ale bylo by to jeste radikalnejsi, nez si
> predstavujete. A je dost dobre mozne, ze vynalozene usili by se dalo
> investovat podstatne efektivneji.

Vzhledem k tomu, kolik existuje buffer overflow exploitů, se mi to zdá
celkem důležité (ale to je pouze můj názor). A lidé jsou omylní a stále
budou přibývat nové exploity. Kdyby se využívaly možnosti ochrany paměti
které procesor nabízí (jako že za současného stavu vzhledem ke kódu který
generuje gcc to není možné), tak by se jejich výskyt dost omezil.
Já mám prostě špatný pocit z toho, že se dá spustit cokoliv na zásobníku a
přepsat zásobník z datového segmentu, i když by to tak být nemuselo.
Připomíná mi to časy MS-DOSu, kdy žádná ochrana paměti neexistovala.

Nicméně nemyslím si, že by úpravy v překladači musely být až tak rozsáhlé
(hlavně by se musely předělat ty trampolíny). Spíš to bude asi v tom, že
segmentový model paměti nemají všechny procesory a vývojářum gcc se nechce
pouštět do něčeho, co by se využilo jenom na Intelech. Takže si asi budu
muset stáhnout zdrojáky a pustit se do toho :).

Jaroslav Gratz



Další informace o konferenci Linux