Tekninen artikkeli

Eristä PDF-kuvakoodekit työprosesseihin HotPDF:llä

HotPDF pystyy purkamaan kolme riskialtteinta PDF-kuvasuodatinta, DCTDecode-, JPXDecode- ja JBIG2Decode-muodot, erillisessä lyhytikäisessä työprosessissa sovelluksesi sisäisen purun sijaan. Tämän ominaisuuden kytkee päälle CodecIsolationMode, ja käytännön vaikutus on se, että virheellinen JPEG 2000 -koodivirta, joka aiemmin olisi kaatanut VCL-sovelluksesi, tappaa nyt kertakäyttöisen aliprosessin, kun taas isäntäprosessi raportoi tilakoodin ja jatkaa toimintaansa

Tämä ero on tärkein juuri niissä paikoissa, joista PDF-tiedostot todellisuudessa saapuvat: latauslomake, sähköpostiyhdyskäytävä, skannauslaite, kumppanin FTP-pudotuskansio. Et hallitse noita tavuja, ja kuvakoodekit ovat juuri se paikka, jossa historialliset vauriot syntyvät

Miksi yksi viallinen kuva kaataa koko sovelluksen?

Koska kuvakoodekki on se ainoa osa PDF-lukijaa, joka ajaa monimutkaista tilakonetta hyökkääjän hallitseman datan päällä lähes ilman jäljellä olevia rakenteellisia tarkistuksia. Siihen mennessä, kun tavut saavuttavat JPEG 2000- tai JBIG2-purkajan, ristiviitetaulukko on jäsennetty, objekti on ratkaistu, suodatinketju on purettu, ja jäljellä on raaka koodivirta, joka kertoo, kuinka monta laattaa, kuinka monta komponenttia, kuinka monta bittiä näytettä kohti. Väärä luku siinä kohdassa ei ole jäsennysvirhe. Se on väärä varauskoko tai lukualueen ylittävä indeksi tiukassa purkusilmukassa

Budjettirajat auttavat, ja niiden pitäisi jo olla käytössäsi. HotPDF rajaa laajenemisen DecodeBudgetBytes- ja DocumentDecodeBudgetBytes-asetuksilla, ja rajaa suodatinketjut DecodeFilterLimit- ja DecodePipelineDepthLimit-asetuksilla; näiden rajojen perustelut käydään läpi artikkelissa rajattu purku sisäkkäisille suodattimille ja PDF-pommeille. Mutta tavubudjetti vastaa vain yhteen kysymykseen, kuinka paljon tulostetta sallitaan. Se ei voi vastata siihen, mitä tapahtuu, kun purkaja kaatuu ennen kuin se tuottaa mitään tulostetta lainkaan. Käyttöoikeusrikkomus purkusilmukan sisällä ei ole käytäntörikkomus, josta voisit kieltäytyä; se on prosessitason tapahtuma, ja ainoa luotettava tapa hallita prosessitason tapahtumaa on eri prosessi

Mitä HotPDF eristää, ja mitä se ei eristä

HotPDF eristää täsmälleen kolme koodekkityyppiä, jotka on lueteltu arvoina hckDCT, hckJPX ja hckJBIG2 HPDFCodecIsolation-yksikössä. Kaikki muu, Flate, LZW, RunLength, ASCII85, CCITT, pysyy prosessin sisällä, koska nuo purkajat ovat riittävän yksinkertaisia rajattavaksi budjeteilla eivätkä ole se paikka, josta mielenkiintoiset virheet syntyvät

Siirtokerros on tarkoituksella kapea. Isäntä varaa yhden rajatun jaetun muistin kartoituksen, kirjoittaa kiinteän THPDFCodecSharedHeader-tietueen sekä pakatun syötteen ja mahdolliset JBIG2-globaalisegmentit, käynnistää työprosessin ja odottaa. Työprosessi kirjoittaa puretut pikselit takaisin samaan kartoitukseen ja asettaa tilasanan. Mitään putkiprotokollaa ei voi mennä epäsynkroniin, mitään sarjallistusmuotoa ei voi fuzzata, ja otsikko sisältää maagisen arvon ja version, joten yhteensopimaton työprosessibinääri hylätään sen sijaan, että se tulkittaisiin väärin

uses
  HPDFDoc, HPDFCodecIsolation;

var
  Pdf: THotPDF;
  Info: THPDFCodecWorkerInfo;
  Bmp: TBitmap;
begin
  Pdf := THotPDF.Create(nil);
  try
    // Fail-closed: älä koskaan pura näitä koodekkeja prosessin sisällä
    Pdf.CodecIsolationMode := cimRequired;
    Pdf.CodecWorkerExecutable := 'HotPDFCodecWorker.exe';
    Pdf.CodecWorkerTimeoutMilliseconds := 5000;       // 1..600000
    Pdf.CodecWorkerMemoryLimitBytes := 268435456;     // 0 tai >= 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;

Jätä CodecWorkerExecutable tyhjäksi, niin HotPDF etsii työprosessin oman suoritettavan tiedostosi vierestä nimellä HotPDFCodecWorker.exe hakemistosta ParamStr(0). Aseta se eksplisiittisesti, kun käyttöönottosi sijoittaa työprosessin muualle; arvo laajennetaan ExpandFileName-funktiolla, joten suhteellinen polku ratkaistaan nykyisen hakemiston, ei sovelluksen hakemiston, suhteen, mikä on harvoin toivottua palvelussa

Automaattinen vai pakollinen: kumman virheen valitset?

THPDFCodecIsolationMode-tyypin kolme arvoa koodaavat kolme eri vastausta yhteen kysymykseen: mitä pitäisi tapahtua, kun työprosessi ei voi käynnistyä lainkaan. cimDisabled ohittaa eristyksen kokonaan ja purkaa prosessin sisällä, mikä vastaa käytäntöä ennen versiota 3.x. Oletusarvo cimAutomatic yrittää käyttää työprosessia ja palaa hiljaisesti prosessin sisäiseen purkuun, kun työprosessin suoritettava tiedosto puuttuu tai ei käynnisty, mikä raportoidaan tilana cwsUnavailable. cimRequired kieltäytyy tästä varajärjestelystä: käyttökelvoton työprosessi merkitsee purun käsitellyksi ja epäonnistuneeksi, joten mikään epäluotettava koodivirta ei koskaan saavuta osoiteavaruuttasi

Valitse uhkamallin, ei mukavuuden perusteella. Työpöytäkatseluohjelmalle, joka avaa käyttäjän levyllä jo olevia asiakirjoja, cimAutomatic riittää hyvin, koska puuttuva työprosessi taantuu klassiseen käyttäytymiseen sen sijaan, että tuote hajoaisi. Internetistä tiedostoja jäsentävän vastaanottopalvelun tulisi käyttää cimRequired-tilaa, koska käyttöönottovirhe, joka hiljaa pudottaa eristyskerroksen, on juuri sellainen taantuma, jota kukaan ei huomaa ennen kuin sillä on merkitystä. Huomaa epäsymmetria: vain cwsUnavailable laukaisee varajärjestelyn. Työprosessi, joka käynnistyi ja sitten kaatui, aikakatkaistiin tai osui rajaan, on purkuvirhe molemmissa tiloissa, ei koskaan hiljainen uudelleenyritys prosessin sisällä

Tuloksen lukeminen THPDFCodecWorkerStatus-arvosta

GetLastCodecWorkerInfo palauttaa viimeisimmän eristetyn purun tuloksen, ja tilaenumeraatio on riittävän tarkka ohjaamaan todellisia operatiivisia päätöksiä sen sijaan, että se olisi vain yleisluonteinen "kuva epäonnistui" -lokirivi. Arvot ovat cwsNotRun, cwsSucceeded, cwsUnavailable, cwsLaunchFailed, cwsTimedOut, cwsCrashed, cwsDecodeFailed, cwsProtocolError ja cwsOutputLimit

Käsittele niitä kolmena ryhmänä. Käyttöönotto-ongelmat ovat cwsUnavailable ja cwsLaunchFailed: joku toimitti sovelluksen ilman työprosessia, tai virustorjuntaohjelma estää prosessin luomisen. Asiakirjaongelmat ovat cwsDecodeFailed ja cwsOutputLimit: tiedosto on virheellinen tai suurempi kuin käytäntösi sallii, ja sen hylkääminen on oikea vastaus. Kiinnostava ryhmä on cwsTimedOut ja cwsCrashed, koska ne ovat tapahtumia, jotka aiemmin olisivat jumiuttaneet tai kaataneet isäntäprosessin. Kun näin käy, mukana tulevat ProcessId-, ExitCode- ja ElapsedMilliseconds-kentät antavat riittävästi tietoa korreloitavaksi Windows Error Reporting -merkinnän kanssa ja päätettäväksi, onko yksittäinen asiakastiedosto poikkeuksellinen vai onko joku tutkimassa järjestelmääsi

procedure LogCodecOutcome(const Info: THPDFCodecWorkerInfo);
begin
  case Info.Status of
    cwsSucceeded:
      ; // ei raportoitavaa
    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;

Rajat, jotka todella sitovat

Jokaiseen eristettyyn purkuun sovelletaan kolmea erillistä kattoa, ja tieto siitä, mikä niistä laukesi, säästää iltapäivän arvailulta. CodecWorkerTimeoutMilliseconds on oletuksena 10 000 ja se validoidaan välille 1–600 000; arvo tuon ulkopuolella nostaa poikkeuksen sen sijaan, että se rajattaisiin hiljaa. CodecWorkerMemoryLimitBytes on oletuksena 536 870 912 tavua ja sen täytyy olla joko nolla, mikä tarkoittaa ei rajaa, tai vähintään 67 108 864 tavua, koska pienempi katto ei voi pitää sisällään realistista purkajan työjoukkoa ja epäonnistuisi jokaisessa asiakirjassa. Muistikattoa valvoo Windowsin Job Object -olio kill-on-close-semantiikalla, joten työprosessi kuolee työn mukana, vaikka isäntä lopetettaisiin äkillisesti

Kolmas katto on tulostusraja, ja se johdetaan sen sijaan, että se määritettäisiin. HotPDF laskee tarvittavat tavut pyydetystä alueesta tai odotetusta kuvageometriasta, leveys kertaa korkeus kertaa kolme 24-bittiselle tulosteelle, ja rajaa sitten tuon arvon alaspäin arvoon DecodeBudgetBytes, kun budjetti on asetettu. Purkaja, joka ilmoittaa uskottavan otsikon ja yrittää sitten tuottaa huomattavasti enemmän pikseleitä kuin geometria sallii, pysäytetään itse kartoituksen toimesta, ja isäntä näkee tilan cwsOutputLimit. Tästä syystä eristyskerros ja purkubudjetti täydentävät toisiaan: budjetti määrittää, kuinka suuri kuva saa olla, ja eristysraja varmistaa, ettei valhe tuosta koosta voi muuttua prosessisi rajojen ulkopuoliseksi kirjoitukseksi

Mihin tämä sopii vahvistetussa vastaanottopolussa

Prosessieristys on uloin kerros puolustusketjussa, joka alkaa paljon aiemmin. Rakenteelliset rajat hylkäävät epäuskottavat asiakirjat jo jäsennysvaiheessa. Suodatinbudjetit rajaavat laajenemisen. Eristys pidättelee sen, mikä selviää molemmista. Kuvakerrokseen asti eteneville asiakirjoille kannattaa tietää, mitä koodekkia todellisuudessa käytetään, sillä JPXDecode-käsittelyllä ja JBIG2-symbolisanakirjoilla on hyvin erilaiset virheprofiilit, ja etenkin JBIG2 sisältää sivujen välisiä globaalisegmenttejä, jotka naiivi kuvakohtainen hiekkalaatikko rikkoisi

Kustannus on rehellinen ja kannattaa mainita: prosessin käynnistäminen jokaista eristettyä kuvaa kohti lisää millisekunteja, ja satoja skannattuja sivuja sisältävä asiakirja tuntee sen. Vertaa sitä siihen, mitä sillä saa. Erän muuntimessa, joka pyörii valvomattomana yön yli, suorituskykytappio on näkymätön ja kaatumisten hallinta on koko pointti. Interaktiivisessa katseluohjelmassa, joka avaa käyttäjän jo luottamia asiakirjoja, cimDisabled tai cimAutomatic on järkevä oletus. Tila on tavallinen ominaisuus, joten mikään ei estä valitsemasta sitä asiakirjaluokittain ajon aikana

HotPDF toimittaa eristyskerroksen, purkubudjetit ja rakenteelliset jäsentimen rajat yhtenä natiivina VCL-komponenttina Delphille ja C++Builderille, ilman ulkoista ajoympäristöä käyttöönotettavaksi työprosessin suoritettavan tiedoston lisäksi. Täydellinen API-dokumentaatio ja kokeiluversio löytyvät sivulta HotPDF Delphi PDF component page