Tehnički članak

Izolacija PDF kodeka slika u radnim procesima uz HotPDF

HotPDF može dekodirati tri najrizičnija PDF filtra za slike, DCTDecode, JPXDecode i JBIG2Decode, unutar zasebnog kratkotrajnog radnog procesa umjesto unutar vaše aplikacije. Svojstvo koje to uključuje je CodecIsolationMode, a praktičan učinak je da neispravan JPEG 2000 kodni tok koji bi inače srušio vašu VCL aplikaciju sada ruši potrošni podređeni proces, dok domaćinski proces prijavljuje statusni kod i nastavlja dalje

Ta razlika najviše dolazi do izražaja upravo ondje odakle PDF-ovi stvarno stižu: obrazac za prijenos datoteka, poštanski pristupnik, uređaj za skeniranje, FTP mapa partnera. Te bajtove ne kontrolirate, a upravo su kodeci za slike mjesto gdje se povijesno događala najveća šteta

Zašto jedna loša slika sruši cijelu aplikaciju?

Zato što je kodek za slike jedini dio PDF čitača koji pokreće složeni automat stanja nad podacima koje kontrolira napadač, uz gotovo nikakve preostale strukturne provjere na koje bi se mogao osloniti. Dok bajtovi stignu do JPEG 2000 ili JBIG2 dekodera, tablica unakrsnih referenci već je parsirana, objekt je razriješen, lanac filtara je odmotan, a ostaje sirovi kodni tok koji navodi koliko ima pločica, koliko komponenti, koliko bitova po uzorku. Pogrešan broj ondje nije pogreška parsiranja. To je loša veličina alokacije ili indeks izvan raspona unutar uske petlje dekodiranja

Proračuni pomažu, i trebali biste ih već imati. HotPDF ograničava širenje pomoću DecodeBudgetBytes i DocumentDecodeBudgetBytes, a lance filtara ograničava pomoću DecodeFilterLimit i DecodePipelineDepthLimit; obrazloženje tih ograničenja opisano je u članku ograničeno dekodiranje za ugniježđene filtre i PDF bombe. No proračun bajtova odgovara na samo jedno pitanje, koliko je izlaza dopušteno. Ne može odgovoriti na pitanje što se događa kada dekoder pukne prije nego što uopće proizvede ikakav izlaz. Kršenje pristupa memoriji unutar petlje dekodiranja nije kršenje pravila koje možete odbiti, to je događaj na razini procesa, a jedino pouzdano suzdržavanje događaja na razini procesa jest drugi proces

Što HotPDF izolira, a što ne

HotPDF izolira točno tri vrste kodeka, nabrojane kao hckDCT, hckJPX i hckJBIG2 u jedinici HPDFCodecIsolation. Sve ostalo, Flate, LZW, RunLength, ASCII85, CCITT, ostaje unutar procesa, jer su ti dekoderi dovoljno jednostavni da ih se ograniči proračunima i nisu mjesto odakle dolaze zanimljivi kvarovi

Prijenos je namjerno uzak. Domaćinski proces alocira jedno ograničeno preslikavanje dijeljene memorije, upisuje fiksni THPDFCodecSharedHeader zajedno s komprimiranim ulazom i svim globalnim JBIG2 segmentima, pokreće radni proces i čeka. Radni proces upisuje dekodirane piksele natrag u isto preslikavanje i postavlja statusnu riječ. Nema protokola s cjevovodom koji bi mogao izgubiti sinkronizaciju, nema formata serijalizacije koji bi se mogao fuzzati, a zaglavlje nosi magičnu vrijednost i verziju, pa se neusklađena binarna datoteka radnog procesa odbija umjesto da se pogrešno pročita

uses
  HPDFDoc, HPDFCodecIsolation;

var
  Pdf: THotPDF;
  Info: THPDFCodecWorkerInfo;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    // Fail-closed: nikada ne dekodiraj ove kodeke unutar procesa
    Pdf.CodecIsolationMode := cimRequired;
    Pdf.CodecWorkerExecutable := 'HotPDFCodecWorker.exe';
    Pdf.CodecWorkerTimeoutMilliseconds := 5000;       // 1..600000
    Pdf.CodecWorkerMemoryLimitBytes := 268435456;     // 0 ili >= 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;

Ostavite li CodecWorkerExecutable praznim, HotPDF pronalazi radni proces pored vaše vlastite izvršne datoteke, kao HotPDFCodecWorker.exe u direktoriju od ParamStr(0). Postavite ga eksplicitno kada vaša implementacija smješta radni proces negdje drugdje; vrijednost se proširuje kroz ExpandFileName, pa se relativna putanja razrješava u odnosu na trenutni direktorij, a ne na direktorij aplikacije, što rijetko želite kod servisa

Automatski ili obavezno: koji kvar preferirate?

Tri vrijednosti tipa THPDFCodecIsolationMode kodiraju tri različita odgovora na jedno pitanje, što se treba dogoditi kada radni proces uopće ne može biti pokrenut. cimDisabled potpuno preskače izolaciju i dekodira unutar procesa, ponašanje iz doba prije verzije 3.x. cimAutomatic, zadana vrijednost, pokušava pokrenuti radni proces i tiho se vraća na dekodiranje unutar procesa kada izvršna datoteka radnog procesa nedostaje ili se ne može pokrenuti, što se prijavljuje kao status cwsUnavailable. cimRequired odbija taj fallback: nedostupan radni proces označava dekodiranje kao obrađeno i neuspjelo, tako da nijedan nepouzdani kodni tok nikada ne dopire do adresnog prostora vaše aplikacije

Birajte prema modelu prijetnji, a ne prema praktičnosti. Desktop preglednik koji otvara dokumente koje korisnik već ima na disku sasvim je u redu s cimAutomatic, gdje nedostajući radni proces degradira na klasično ponašanje umjesto da sruši proizvod. Servis za prihvat koji parsira datoteke s interneta trebao bi koristiti cimRequired, jer je pogreška u implementaciji koja tiho ukloni sloj izolacije upravo ona vrsta regresije koju nitko ne primijeti dok ne postane važna. Uočite asimetriju: samo cwsUnavailable pokreće fallback. Radni proces koji se pokrenuo i zatim srušio, istekao ili dosegnuo ograničenje predstavlja neuspjeh dekodiranja u oba načina rada, nikada tihi ponovni pokušaj unutar procesa

Čitanje presude iz THPDFCodecWorkerStatus

GetLastCodecWorkerInfo vraća ishod posljednjeg izoliranog dekodiranja, a enumeracija statusa dovoljno je precizna da omogući stvarne operativne odluke, umjesto generičkog zapisa u dnevniku poput „slika nije uspjela”. Vrijednosti su cwsNotRun, cwsSucceeded, cwsUnavailable, cwsLaunchFailed, cwsTimedOut, cwsCrashed, cwsDecodeFailed, cwsProtocolError i cwsOutputLimit

Promatrajte ih kao tri skupine. Problemi s implementacijom su cwsUnavailable i cwsLaunchFailed: netko je isporučio bez radnog procesa, ili antivirusni proizvod blokira stvaranje procesa. Problemi s dokumentom su cwsDecodeFailed i cwsOutputLimit: datoteka je neispravna ili veća nego što vaša politika dopušta, a njezino odbijanje ispravan je odgovor. Zanimljiva skupina je cwsTimedOut i cwsCrashed, jer su to događaji koji bi ranije zamrznuli ili srušili domaćinski proces. Kada se to dogodi, prateća polja ProcessId, ExitCode i ElapsedMilliseconds daju vam dovoljno podataka za povezivanje sa zapisom u Windows Error Reporting sustavu i odluku je li jedna korisnička datoteka patološka ili vas netko sondira

procedure LogCodecOutcome(const Info: THPDFCodecWorkerInfo);
begin
  case Info.Status of
    cwsSucceeded:
      ; // nema se što prijaviti
    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;

Ograničenja koja stvarno vežu

Tri odvojena gornja ograničenja vrijede za svako izolirano dekodiranje, a znati koje je od njih okinulo štedi vam popodne nagađanja. CodecWorkerTimeoutMilliseconds zadano iznosi 10.000 i validira se u rasponu od 1 do 600.000; vrijednost izvan tog raspona izaziva iznimku umjesto tihog ograničavanja. CodecWorkerMemoryLimitBytes zadano iznosi 536.870.912 bajtova i mora biti ili nula, što znači bez ograničenja, ili najmanje 67.108.864 bajtova, jer manje ograničenje ne bi moglo obuhvatiti realan radni skup dekodera i svaki bi dokument propao. Ograničenje memorije provodi se putem Windows Job Objecta sa semantikom kill-on-close, pa radni proces umire zajedno s jobom čak i ako je domaćinski proces naglo prekinut

Treće gornje ograničenje je ograničenje izlaza, i ono se izvodi, a ne konfigurira. HotPDF izračunava potrebne bajtove iz tražene regije, ili iz očekivane geometrije slike, kao širinu puta visinu puta tri za 24-bitni izlaz, a zatim tu vrijednost ograničava na DecodeBudgetBytes kada je proračun postavljen. Dekoder koji prijavi uvjerljivo zaglavlje, a zatim pokuša ispisati mnogo više piksela nego što geometrija dopušta, zaustavlja samo preslikavanje, a domaćinski proces vidi cwsOutputLimit. Zato su sloj izolacije i proračun dekodiranja komplementarni: proračun definira koliko velika slika smije biti, a granica izolacije osigurava da laž o toj veličini ne može postati upis izvan granica u vašem procesu

Gdje se ovo uklapa u ojačanu putanju prihvata

Izolacija procesa vanjski je sloj obrambenog lanca koji počinje mnogo ranije. Strukturna ograničenja odbacuju neuvjerljive dokumente još u trenutku parsiranja. Proračuni filtara ograničavaju širenje. Izolacija obuhvaća ono što preživi oba koraka. Za dokumente koji dosegnu sloj slika, vrijedi znati koji kodek zapravo koristite, jer rukovanje JPXDecode formatom i rječnici JBIG2 simbola imaju vrlo različite profile kvarova, a JBIG2 posebno nosi globalne segmente koji se protežu preko više stranica, što bi naivni sandbox po pojedinačnoj slici prekinuo

Cijena je iskrena i vrijedi je izreći: pokretanje procesa za svaku izoliranu sliku dodaje milisekunde, a dokument sa stotinama skeniranih stranica to će osjetiti. Usporedite to s onim što dobivate zauzvrat. Kod skupnog pretvarača koji radi bez nadzora preko noći, gubitak propusnosti nevidljiv je, a suzdržavanje rušenja jest cijela poanta. Kod interaktivnog preglednika koji otvara dokumente kojima korisnik već vjeruje, cimDisabled ili cimAutomatic razumna je zadana vrijednost. Način rada obično je svojstvo, pa vas ništa ne sprječava da birate po klasi dokumenta tijekom izvođenja

HotPDF isporučuje sloj izolacije, proračune dekodiranja i strukturna ograničenja parsera kao jednu izvornu VCL komponentu za Delphi i C++Builder, bez potrebe za dodatnim runtime okruženjem osim same izvršne datoteke radnog procesa. Potpuna API dokumentacija i probna verzija dostupne su na stranici HotPDF Delphi PDF komponente