HotPDF gali dekoduoti tris rizikingiausius PDF paveikslėlių filtrus, DCTDecode, JPXDecode ir JBIG2Decode, atskirame trumpai gyvuojančiame darbiniame procese vietoje jūsų aplikacijos. Savybė, kuri tai įjungia, yra CodecIsolationMode, o praktinis poveikis toks, kad netaisyklingas JPEG 2000 kodų srautas, kuris anksčiau būtų sugriovęs jūsų VCL aplikaciją, dabar sunaikina vienkartinį antrinį procesą, o pagrindinis procesas praneša būsenos kodą ir tęsia darbą
Šis skirtumas svarbiausias vietose, iš kurių PDF dokumentai iš tikrųjų atkeliauja: įkėlimo forma, pašto tinklalaidė, skenavimo įrenginys, partnerio FTP katalogas. Jūs nekontroliuojate tų baitų, o paveikslėlių kodekai yra ten, kur gyvena istorinė žala
Kodėl vienas blogas paveikslėlis sugriauna visą aplikaciją?
Todėl, kad paveikslėlio kodekas yra ta vienintelė PDF skaityklės dalis, kuri vykdo sudėtingą būsenų mašiną per užpuoliko kontroliuojamus duomenis, beveik neturėdama struktūrinių patikrų, kuriomis galėtų remtis. Kol baitai pasiekia JPEG 2000 ar JBIG2 dekoderį, kryžminių nuorodų lentelė jau išanalizuota, objektas jau išspręstas, filtrų grandinė jau išvyniota, ir belieka neapdorotas kodų srautas, kuris nurodo, kiek plytelių, kiek komponentų, kiek bitų vienam pikseliui. Neteisingas skaičius čia nėra sintaksės klaida. Tai bloga išskyrimo apimtis arba už ribų esantis indeksas glaudžiame dekodavimo cikle
Biudžeto ribos padeda, ir jos jums jau turėtų būti. HotPDF riboja išplėtimą su DecodeBudgetBytes ir DocumentDecodeBudgetBytes, o filtrų grandines riboja su DecodeFilterLimit ir DecodePipelineDepthLimit; loginis pagrindas šioms riboms aprašytas straipsnyje apie ribotą dekodavimą įdėtiems filtrams ir PDF bomboms. Tačiau baitų biudžetas atsako tik į vieną klausimą – kiek išvesties leidžiama. Jis negali atsakyti, kas nutinka, kai dekoderis sugenda dar neišvedus jokios išvesties. Prieigos pažeidimas dekodavimo cikle nėra politikos pažeidimas, kurį galite atmesti; tai proceso lygio įvykis, o vienintelis patikimas tokio įvykio suvaldymas yra kitas procesas
Ką HotPDF izoliuoja, o ko ne
HotPDF izoliuoja lygiai tris kodekų rūšis, išvardytas kaip hckDCT, hckJPX ir hckJBIG2 HPDFCodecIsolation vienete. Viskas kita – Flate, LZW, RunLength, ASCII85, CCITT – lieka procese, nes tie dekoderiai pakankamai paprasti riboti biudžetais ir ne iš jų kyla įdomūs gedimai
Perdavimo kanalas sąmoningai siauras. Pagrindinis procesas paskirsto vieną ribotą bendros atminties atvaizdavimą, įrašo fiksuotą THPDFCodecSharedHeader antraštę kartu su suglaudinta įvestimi ir bet kokiais JBIG2 globaliais segmentais, paleidžia darbinį procesą ir laukia. Darbinis procesas įrašo dekoduotus pikselius atgal į tą patį atvaizdavimą ir nustato būsenos žodį. Nėra kanalų protokolo, galinčio išsiderinti, nėra serializavimo formato, kurį būtų galima fuzz'inti, o antraštė turi magišką reikšmę ir versiją, todėl neatitinkantis darbinio proceso dvejetainis failas yra atmetamas, o ne klaidingai perskaitomas
uses
HPDFDoc, HPDFCodecIsolation;
var
Pdf: THotPDF;
Info: THPDFCodecWorkerInfo;
Bmp: TBitmap;
begin
Pdf := THotPDF.Create(nil);
try
// Griežtai uždaryta: šių kodekų niekada nedekoduoti procese
Pdf.CodecIsolationMode := cimRequired;
Pdf.CodecWorkerExecutable := 'HotPDFCodecWorker.exe';
Pdf.CodecWorkerTimeoutMilliseconds := 5000; // 1..600000
Pdf.CodecWorkerMemoryLimitBytes := 268435456; // 0 arba >= 64 MiB
Pdf.DecodeBudgetBytes := 134217728;
if Pdf.LoadFromFile('untrusted-upload.pdf') = 1 then
if Pdf.GetLoadedImageCount > 0 then
begin
Bmp := Pdf.ExtractLoadedImage(0);
try
if Pdf.GetLastCodecWorkerInfo(Info) then
LogCodecOutcome(Info);
finally
Bmp.Free;
end;
end;
finally
Pdf.Free;
end;
end;
Palikite CodecWorkerExecutable tuščią, ir HotPDF suras darbinį procesą šalia jūsų paties vykdomojo failo, kaip HotPDFCodecWorker.exe ParamStr(0) kataloge. Nustatykite jį aiškiai, kai jūsų diegimas darbinį procesą patalpina kitur; reikšmė išplečiama per ExpandFileName, todėl santykinis kelias išsprendžiamas pagal esamą katalogą, o ne pagal aplikacijos katalogą, o tai retai yra tai, ko norite paslaugoje
Automatinis ar privalomas: kurį gedimą pasirenkate?
Trys THPDFCodecIsolationMode reikšmės koduoja tris skirtingus atsakymus į vieną klausimą – kas turėtų nutikti, kai darbinis procesas visiškai negali veikti. cimDisabled praleidžia izoliaciją visiškai ir dekoduoja procese, pagal ankstesnio nei 3.x elgesį. cimAutomatic, numatytoji reikšmė, bando darbinį procesą ir tyliai grįžta prie dekodavimo procese, kai darbinio proceso vykdomasis failas nerastas arba nepaleidžiamas, o tai registruojama kaip cwsUnavailable būsena. cimRequired atsisako tokio grįžimo: nepasiekiamas darbinis procesas pažymi dekodavimą kaip apdorotą ir nepavykusį, todėl joks nepatikimas kodų srautas niekada nepasiekia jūsų adresų erdvės
Rinkitės pagal grėsmių modelį, ne pagal patogumą. Stalinei peržiūros programai, atveriančiai dokumentus, kuriuos naudotojas jau turi diske, tinka cimAutomatic, kai trūkstamas darbinis procesas nusileidžia iki klasikinės elgsenos vietoje produkto sugadinimo. Priėmimo tarnyba, analizuojanti failus iš interneto, turėtų veikti su cimRequired, nes diegimo klaida, kuri tyliai pašalina izoliacijos sluoksnį, yra būtent tokia regresija, kurios niekas nepastebi, kol tai nesvarbu. Atkreipkite dėmesį į asimetriją: tik cwsUnavailable sukelia grįžimą. Darbinis procesas, kuris paleistas ir vėliau sudužo, viršijo laiko limitą arba pasiekė ribą, abiejuose režimuose yra dekodavimo nesėkmė, niekada ne tylus bandymas iš naujo procese
Nuosprendžio skaitymas iš THPDFCodecWorkerStatus
GetLastCodecWorkerInfo grąžina paskutinio izoliuoto dekodavimo rezultatą, o būsenų išvardijimas pakankamai konkretus, kad varytų realius eksploatacinius sprendimus, o ne bendrą įrašą „paveikslėlis nepavyko". Reikšmės yra cwsNotRun, cwsSucceeded, cwsUnavailable, cwsLaunchFailed, cwsTimedOut, cwsCrashed, cwsDecodeFailed, cwsProtocolError ir cwsOutputLimit
Traktuokite jas kaip tris grupes. Diegimo problemos yra cwsUnavailable ir cwsLaunchFailed: kažkas išleido be darbinio proceso, arba antivirusinis produktas blokuoja proceso kūrimą. Dokumento problemos yra cwsDecodeFailed ir cwsOutputLimit: failas netaisyklingas arba didesnis nei leidžia jūsų politika, ir jo atmetimas yra teisingas atsakymas. Įdomiausia grupė yra cwsTimedOut ir cwsCrashed, nes tai įvykiai, kurie anksčiau būtų užkabinę arba nužudę pagrindinį procesą. Kai taip nutinka, kartu einantys ProcessId, ExitCode ir ElapsedMilliseconds laukai suteikia pakankamai informacijos susieti su „Windows Error Reporting" įrašu ir nuspręsti, ar vienas kliento failas patologinis, ar kažkas jus tikrina
procedure LogCodecOutcome(const Info: THPDFCodecWorkerInfo);
begin
case Info.Status of
cwsSucceeded:
; // nieko pranešti
cwsUnavailable, cwsLaunchFailed:
Alert('Codec worker not deployed: ' + Info.ErrorMessage);
cwsTimedOut, cwsCrashed:
Quarantine(Format('pid %d exit %d after %d ms',
[Info.ProcessId, Info.ExitCode, Info.ElapsedMilliseconds]));
else
RejectDocument(Info.ErrorMessage);
end;
end;
Ribos, kurios iš tikrųjų veikia
Trys atskiros ribos taikomos kiekvienam izoliuotam dekodavimui, ir žinojimas, kuri iš jų suveikė, sutaupo pusdienį spėliojimo. CodecWorkerTimeoutMilliseconds numatytoji reikšmė yra 10 000, ir ji patikrinama pagal 1–600 000 intervalą; reikšmė už jo ribų sukelia klaidą, o ne tyliai apkarpoma. CodecWorkerMemoryLimitBytes numatytoji reikšmė yra 536 870 912 baitų, ir ji turi būti arba nulis, reiškiantis be ribos, arba mažiausiai 67 108 864 baitai, nes mažesnė riba negali sutalpinti realistiško dekoderio darbinio rinkinio ir atmes kiekvieną dokumentą. Atminties riba vykdoma per „Windows Job Object" su nužudymo uždarant semantika, todėl darbinis procesas žūva kartu su darbo objektu, net jei pagrindinis procesas nutraukiamas staigiai
Trečia riba yra išvesties riba, ir ji išvedama, o ne konfigūruojama. HotPDF apskaičiuoja reikiamus baitus iš prašomos srities arba iš numatomos paveikslėlio geometrijos, kaip plotį kartą aukštį kartą tris 24 bitų išvesčiai, tada apkerpa šią reikšmę žemyn iki DecodeBudgetBytes, kai biudžetas nustatytas. Dekoderis, kuris praneša tikėtiną antraštę, o tada bando išvesti daug daugiau pikselių, nei leidžia geometrija, sustabdomas pačio atvaizdavimo, o pagrindinis procesas mato cwsOutputLimit. Būtent todėl izoliacijos sluoksnis ir dekodavimo biudžetas papildo vienas kitą: biudžetas apibrėžia, kokio dydžio paveikslėlis leidžiamas, o izoliacijos riba užtikrina, kad melas apie tą dydį negalėtų virsti rašymu už ribų jūsų procese
Kur tai telpa į sustiprintą priėmimo kelią
Proceso izoliacija yra išorinis sluoksnis gynybos grandinės, prasidedančios daug anksčiau. Struktūrinės ribos atmeta netikėtinus dokumentus analizavimo metu. Filtrų biudžetai riboja plėtimąsi. Izoliacija suvaldo tai, kas išgyvena abu. Dokumentams, pasiekiantiems paveikslėlio sluoksnį, verta žinoti, kurį kodeką iš tikrųjų naudojate, nes JPXDecode apdorojimas ir JBIG2 simbolių žodynai turi labai skirtingus gedimo profilius, o JBIG2 ypač turi kelių puslapių globalius segmentus, kuriuos naivus izoliavimas pagal paveikslėlį sugadintų
Kaina sąžininga ir verta pasakyti: proceso paleidimas kiekvienam izoliuotam paveikslėliui prideda milisekundes, ir dokumentas su šimtais nuskaitytų puslapių tai pajus. Įvertinkite tai pagal tai, ką jis duoda. Paketiniame konverteryje, veikiančiame be priežiūros per naktį, pralaidumo praradimas nematomas, o griūties suvaldymas yra visa esmė. Interaktyvioje peržiūros programoje, atveriančioje dokumentus, kuriais naudotojas jau pasitiki, cimDisabled arba cimAutomatic yra pagrįstas numatytasis pasirinkimas. Režimas yra paprasta savybė, todėl niekas netrukdo pasirinkti pagal dokumentų klasę vykdymo metu
HotPDF pristato izoliacijos sluoksnį, dekodavimo biudžetus ir struktūrinio analizatoriaus ribas kaip vieną natyvų VCL komponentą Delphi ir C++Builder, be jokios išorinės vykdymo aplinkos, kurią reikėtų diegti, be paties darbinio proceso vykdomojo failo. Pilna API dokumentacija ir bandomoji versija pateikta HotPDF Delphi PDF komponento puslapyje