PDF nije dokument koji samo otvorite. To je mali program koji pokre?ete. Svaki ugra?eni font je interpreter temeljen na stogu koji ?eka znakovne nizove, svaka slika je dekoder koji se hrani poljima ?irine, visine i dubine bita koje je datoteka odabrala, a svaki tok dolazi umotan u filtre ?ije je parametre datoteka postavila. Nijedan od tih brojeva nije va?. Do?li su od onoga tko je proizveo datoteku, ?to je na stvarnom poslu ra?un kupca ili privitak od nepoznatog po?iljatelja. Dekoderi koji te bajtove pretvaraju u piksele i glifove su napada?ka povr?ina, a parser koji tu vjeruje svom ulazu je samo jednu lo?e oblikovanu datoteku udaljen od ru?enja ili ne?eg goreg
PDFlibPas je pro?ao kroz fazu u?vr??ivanja koja je cijelu putanju dekodiranja tretirala kao neprijateljsku, kroz programe fontova (TrueType, Type1, CFF i CMap tablice), dekodere slika (PNG, GIF, TIFF, JBIG2 i CCITT Grupa 3 i Grupa 4) i filtre tokova (LZW, ASCII85 i Flate prediktore). Ono ?to slijedi je pet klasa nedostataka koje je zatvorio, od kojih je svaka utemeljena na specifi?nom pona?anju Delphi-ju koje ih je omogu?ilo. Oni su ispravljeni u trenutnim izdanjima, a isti se oblici ponavljaju u bilo kojem Pascal kodu koji analizira nepouzdane ulaze
Prelijevanje cijelog broja koje rezultira premalim međuspremnikom
Klasi?ni memorijski sigurnosni bug u dekoderu slika je umno?ak dimenzija koji se omotava. Dekoder ?ita ?irinu, visinu, broj komponenti i dubinu bita, mno?i ih kako bi odredio veli?inu svog izlaza, dodjeljuje toliko bajtova, a zatim zapisuje sliku u njezinim stvarnim dimenzijama. Ako se mno?enje vr?i u 32-bitnoj aritmetici, umno?ak se mo?e omotati na malu vrijednost ?ak i kada je svaki pojedina?ni faktor u razumnom rasponu, tako da dodjela uspijeva, ispada daleko premala, a dekodiranje izlazi izvan njezinih granica. To je CWE-190, prelijevanje cijelog broja, ?to vodi do pisanja izvan granica hrpe (CWE-787) korak kasnije
Zajedni?ka putanja slike ve? je ograni?avala svaku dimenziju na 65535; samostalni dekoderi nisu svi naslijedili to ograni?enje. Izraz row-bytes-times-height kao ?to je ByteCount * FHeight, ili izraz po pikselu kao ?to je FWidth * Components * BitDepth, je 32-bitni umno?ak u Delphi-ju kada su oba operanda 32-bitni cijeli brojevi, bez obzira na to koliko je ?iroka varijabla kojoj dodjeljujete rezultat. ?irina i visina od 60000 su razumne za veliko skeniranje, ali njihov umno?ak u bajtovima prekora?uje ozna?eni 32-bitni raspon i duljina ispada mala. Isti je problem ?ivio u koraku prediktora ZLib, BitsPerComponent * Colors * Columns
Ispravak je u?initi barem jedan operand tipom Int64 kako bi se cijeli izraz procijenio u 64 bita, zatim ga usporediti s MaxInt i odbiti datoteku prije ponovnog su?avanja radi pozivanja SetLength
// Reject before allocating, not after writing.
// Evaluate the product in Int64 so it cannot wrap at 32 bits.
RowBytes := (Int64(FWidth) * Components * BitDepth + 7) div 8;
if (RowBytes <= 0) or (RowBytes * FHeight > MaxInt) then
Exit; // hostile or unsupportable dimensions; refuse the image
SetLength(Buffer, RowBytes * FHeight);
Ono ?to ovo ?ini problemom u Delphi-ju, a ne op?im problemom, jest tiho su?avanje. Dodjeljivanje pre?irokog izraza u 32-bitno odredi?te je legalna pretvorba o kojoj kompajler prema zadanim postavkama ne?e upozoriti, a provjera raspona ne hvata omotavanje koje se dogodi prije nego ?to se vrijednost uop?e upotrijebi kao indeks. Ostavite umno?ak na 32 bita i jezik vam tiho daje duljinu koja la?e o tome koliko ?e memorije dekodiranje dotaknuti
Tip polja koji onemogućuje aktiviranje zaštite
TIFF datoteka je lanac direktorija slikovnih datoteka, od kojih svaki nosi pomak bajta sljede?eg. Zlonamjerna datoteka mo?e usmjeriti taj lanac natrag na sebe, a ?ita? koji prolazi kroz njega bez uvjeta zaustavljanja vrti se zauvijek. To je CWE-835, beskona?na petlja vo?ena ulazom pod kontrolom napada?a, a obrana je broja? koji se zaustavlja ?im prije?e granicu koju nijedna legitimna datoteka ne bi dosegla
Broja? stranica bio je deklariran kao Word, koji u Delphi-ju dr?i vrijednosti od 0 do 65535. Petlja je nosila za?titu od zaustavljanja u obliku "zaustavi kada broj stranica prije?e 65535", ?to se ?ini to?nim dok ne primijetite da operand i prag dijele gornju granicu. Word nikada mo?e biti ve?i od 65535, pa je usporedba strukturno uvijek neto?na: kada broja? dosegne 65535, sljede?e pove?anje ga vra?a na 0, za?tita nikada ne vidi vrijednost iznad gornje granice, a kru?ni lanac IFD-a dr?i ?ita? u beskona?nom vrtnju
Ispravak je bio pro?iriti polje kako bi za?tita mogla izraziti vrijednost koju broja? stvarno mo?e dr?ati. S poljem TPDFTIFF.FPageCount deklariranim kao Integer, ista usporedba FPageCount > 65535 postaje dosti?na, petlja se prekida, a javno svojstvo PageCount promijenilo je tip kako bi odgovaralo bez prekidanja bilo kojeg pozivatelja. Kad god provjera granica ima oblik Value > MaxValueOfType(Value), a operand je ve? tipiziran to?no na taj maksimum, uvjet je konstantno neto?an: pro?irite tip ili testirajte jednakost s maksimumom kako bi se mogao aktivirati
Provjera raspona isključena na kritičnoj stazi
S uklju?enom provjerom raspona, Delphi ume?e provjeru granica na svaki indeks niza i znakovnog niza, ?to je razlika izme?u indeksa izvan raspona koji podi?e uhvatljivu pogre?ku ERangeError i tog istog indeksa koji ?ita ili pi?e memoriju koja ne pripada strukturi. Vru?e putanje je ponekad isklju?uju lokalnom direktivom {$R-}, ?to je obranjivo sve dok indeksi ne prestanu biti pouzdani
Pristupnik popisu na koji se oslanjaju interpreteri fontova, TPDFlibStringList.Get, je upravo takva putanja. Na Windowsima se kompajlira s isklju?enom provjerom raspona i izravno indeksira svoju pozadinsku pohranu, tako da indeks izvan raspona nije pogre?ka ve? sirovi pristup memoriji. To je u redu kada je indeks uvijek valjan, a prestaje biti u redu unutar CFF ili Type2 charstring interpretera, gdje indeks mo?e do?i iz datoteke. Charstring koji izbacuje operand s praznog stoga proizvodi indeks minus jedan; identifikator glifa pomaknut za jedan u odnosu na broj glifova indeksira jedno mjesto iza kraja. S isklju?enom provjerom raspona, oba postaju stvarni pristup izvan granica umjesto uhvatljive iznimke, a budu?i da utori dr?e vrijednosti AnsiString s brojenjem referenci, zalutalo ?itanje tako?er mo?e pokvariti broj referenci niza
U?vr??ivanje nije ponovno uklju?ilo provjeru raspona za vru?u putanju. Najprije je u?inilo indekse dokazivo valjanima: prije uzimanja vrha stoga operanda interpreter provjerava da stog nije prazan, a svaka za?tita indeksa napisana je kao strogo manje od u odnosu na broj, umjesto manje ili jednako ?to dopu?ta odstupanje za jedan. Direktiva pomi?e odgovornost za granice s kompajlera na vas, a validacija koju je uklonila mora se ru?no vratiti na svakoj ulaznoj to?ki
Neograničena rekurzija u interpretatoru charstringa
Type2 charstring mo?e pozvati podrutinu, a podrutina je i sama charstring koji mo?e pozvati drugu, tako da operatori poziva lokalne i globalne podrutine dopu?taju datoteci da odlu?i koliko duboko ide. Podrutina koja poziva samu sebe, izravno ili kroz ciklus, rekurzira bez kraja dok se stog ne iscrpi i proces ne umre. To je CWE-674, nekontrolirana rekurzija
Type1 interpreter se ve? ?titio od toga. Nosio je broja? dubine poziva i gornju granicu, PLType1MaxCallDepth, i odbijao se spustiti dublje, ?to odra?ava ograni?enje dubine koje sama Type1 specifikacija navodi. Type2 interpreter, dodan kasnije i strukturno sli?an, nije nosio istu za?titu, pa ru?no izra?eni font s podrutinom koja poziva vlastiti broj ide ravno kroz nedostaju?u provjeru u prekora?enje stoga
// The shape of the Type1 guard the Type2 path was missing.
// Track depth across nested calls and refuse to recurse past it.
Inc(CallDepth);
if CallDepth > PLType1MaxCallDepth then
Exit; // hostile self-referential subroutine; stop descending
// ... interpret the subroutine, then Dec(CallDepth) on the way out
Ispravak je bio dati putanji Type2 istu ograni?enu dubinu koju je ve? imao njegov brat Type1. Svaki rekurzivni spust preko strukture pod kontrolom napada?a, bilo da se radi o podrutinama fonta, ugnije??enom nizu ili lancu unakrsnih referenci, treba gornju granicu dubine koju ulaz ne mo?e podi?i
Neinicijalizirana memorija koja curi u izlaz
Najsuptilniji nedostatak propu?tao je sadr?aj hrpe u de?ifrirani izlaz, a uzrok je svojstvo SetLength koje je lako zaboraviti. Kada pove?ate AnsiString pomo?u SetLength, Delphi dodjeljuje bajtove, ali ih ne postavlja na nulu, tako da novo podru?je dr?i sve ?to je prethodno bilo u toj memoriji hrpe. Ako se svaki bajt naknadno zapi?e, to nikada nije va?no; ako putanja ostavi dio spremnika nezapisanim i zatim ga vrati kao podatke, ti ustajali bajtovi izlaze s rezultatom. To je CWE-457, kori?tenje neinicijalizirane memorije, a kada rezultat prije?e granicu povjerenja, to postaje curenje informacija
AES-CBC putanja de?ifriranja pogodila je to?no ovo. Izlazni spremnik bio je veli?ine odre?ene pomo?u SetLength i de?ifrator je obra?ivao ?ifrirani tekst po jedan blok od 16 bajtova. Kada duljina ?ifriranog teksta nije bila vi?ekratnik broja 16, a to je duljina koju napada? mo?e odabrati, zavr?ni djelomi?ni blok nikada nije bio zapisan, pa su ti kona?ni bajtovi zadr?ali sadr?aj hrpe koji je SetLength ostavio iza sebe i spremnik je vra?en kao de?ifrirani ?ist tekst objekta dokumenta. Lijek su dvije za?tite, i nijedna sama nije dovoljna: ulazna to?ka de?ifriranja sada odbija bilo koji ?ifrirani tekst ?ija duljina nije vi?ekratnik veli?ine bloka, a kao osiguranje izlaz se prije upotrebe bri?e pomo?u FillChar tako da bilo koja putanja koja ne uspije zapisati regiju vra?a nule umjesto ostataka hrpe
Što vam ostavlja prolaz
Pet nedostataka su razli?iti bugovi, ali se rimuju. ?irina cijelog broja koja omotava umno?ak, tip polja koji fiksira za?titu na konstantno neto?no, provjera raspona isklju?ena tamo gdje indeksi vi?e nisu sigurni, rekurzija bez poda i spremnik koji jezik odbija postaviti na nulu. U svakom od njih Delphi je u?inio to?no ono ?to definira, jer jezik vam daje aritmetiku koja se omotava, su?avanje koje je tiho, provjere raspona koje mo?ete isklju?iti, rekurziju bez ugra?enog ograni?enja i alokaciju koja se ne inicijalizira. To je ugovor, a Pascal parser ga ispunjava ru?nim preuzimanjem kontrole nad ?etirima stvarima na svakoj granici koju datoteka kontrolira: ?irinom cijelog broja, provjerom raspona, dubinom rekurzije i inicijalizacijom spremnika
Ovi nedostaci su zatvoreni u trenutnim izdanjima PDFlibPas-a, pogona za Delphi i C++Builder. Ako se va? rad tako?er doti?e na?ina na koji datoteka tvrdi da je za?ti?ena, prate?e bilje?ke o reviziji ?ifriranja i dopu?tenja te o preflightu za PDF/A i PDF/UA pokrivaju analiti?ku stranu istog parsera, a sve se to isporu?uje unutar softvera PDFlibPas Delphi PDF Library zajedno s API-jima za u?itavanje, prikazivanje i potpisivanje koji su pokriveni drugdje na ovom blogu