Technický článek

Bezpečné přehrávání nedůvěryhodných EMF a WMF v HotXLS

Pracovní sešit Excelu může nést obrázky EMF a WMF a konvenční způsob, jak jeden nakreslit, je podat bajtový stream metafile playeru operačního systému. To je rozhodnutí, které stojí za přímý pohled: metasoubor je serializovaný příkazový stream pro grafické API a přehrání jednoho znamená pustit soubor, který dorazil e-mailem, řídit grafický ovladač. HotXLS volí druhou cestu. XLSDecodeVectorScene parsuje metasoubor sama, validuje hlavičku, každou velikost záznamu, deklarovaný celkový počet záznamů a přesné umístění záznamu konce souboru, zcela odmítá escape záznamy a vrací TXLSVectorScene primitivních kreslicích příkazů, které backendy Canvas a SVG přehrávají vlastním kódem. Přehrávání ovladačem se neúčastní v žádném okamžiku

HotXLS parsuje nedůvěryhodné bajty EMF a WMF z listů pomocí XLSDecodeVectorScene do seznamu příkazů TXLSVectorScene namísto přehrávání GDI metasouborů
HotXLS parsuje metasoubor sama a vrací primitivní příkazy pro přehrávání Canvas a SVG; konvenční cesta spouští bajtový stream na grafickém stacku

Obchod je pokrytí za uzavření. Whitelist příkazů orientovaný na obdélníky nezreprodukuje každý metasoubor, který může návrhář vytvořit, takže scéna hlásí, kolik kreslicích záznamů nedokázala reprezentovat, a volající rozhodne, co s tím. Pro serverový proces renderující dokumenty, které nevytvořil, je ten obchod správně naruby

Proč je přehrávání metasouborů špatné pro nedůvěryhodný vstup?

Protože formát není obrázek, je to program. Stream záznamů EMF manipuluje se zásobníkem stavů device context, alokuje a vybírá objekty z tabulky handle a může nést escape záznamy, jejichž obsah je předáván ovladači zařízení. Jeho přehrání procvičí cesty v grafickém stacku platformy napsané v domnění, že metasoubor přišel od spolupracující aplikace na témže stroji. Když je vstupem příloha tabulky, to domnění je pryč a žádná pečlivost uvnitř tabulkové knihovny nepomůže, protože knihovna není komponentou, která parsuje

Toto je též úvaha, která řídí vrstvu kontejneru. Pracovní sešit je ZIP archiv a HotXLS validuje jeho centrální adresář namísto důvěry deklarovaným offsetům, jak popisuje článek o validaci ZIP end-of-central-directory. Obsahy metasouborů jsou další vrstvou téhož problému

Co dekodér kontroluje, než cokoli nakreslí

Validace je strukturální a děje se předem, protože parser, který začne kreslit a validuje za pochodu, už jednal nad daty, jež neověřil. Hlavička se musí shodovat striktně, nikoli věrohodně. Každý záznam musí deklarovat velikost, která se vejde do zbylého bufferu a je dost velká pro vlastní pevná pole. Počet záznamů deklarovaný hlavičkou se musí shodovat se záznamy skutečně přítomnými. Záznam konce souboru musí sedět přesně tam, kde stream končí, nikoli jen někde poblíž, čímž se zavírá trik koncového odpadu, který skrývá druhý obsah za platným obrázkem

Kromě struktury je dekodér na sémantice selhávající uzavřeně. Escape záznamy jsou odmítnuty, nikoli přeskočeny. Záznam měnící stav, který dekodér nemodeluje, způsobí selhání dekódování namísto ignorování, protože ignorování změny stavu znamená, že každý následující kreslicí příkaz se provede ve stavu, o který soubor nežádal, a výsledkem je obrázek chybný způsobem, který nikdo nedokáže předpovědět. Kreslicí záznamy mimo podporovanou sadu příkazů jsou jiná záležitost: ty se počítají a přeskočí, protože chybějící tvar je viditelná, hlášená mezera namísto tiché korupce

XLSDecodeVectorScene kontroluje předem hlavičku, velikosti záznamů, součty a umístění EOF, poté odmítá escape záznamy a počítá nepodporované kreslicí záznamy
Strukturální kontroly běží předem a selhávající uzavřená sémantika odmítá escape záznamy, zatímco nepodporované kreslicí záznamy se jen počítají a přeskočí

Rozpočty jsou součástí smlouvy formátu

Vektorové formáty mají vlastní verzi dekompresní bomby. Pár kilobytů záznamů může deklarovat polylines se stovkami milionů bodů nebo obrázek, jehož deklarované rozměry se vynásobí do terabajtů. Meze proto musí být explicitní konstanty, nikoli cokoli, co stroj náhodou přežije

// Z lxVectorScene: rozpočet dekódování, vyjádřený, nikoli domnělý
XL_VECTOR_MAX_RECORDS           = 1000000;
XL_VECTOR_MAX_HANDLES           = 4096;
XL_VECTOR_MAX_DC_DEPTH          = 32;
XL_VECTOR_MAX_COMMANDS          = 100000;
XL_VECTOR_MAX_POINTS_PER_RECORD = 100000;
XL_VECTOR_MAX_TOTAL_POINTS      = 2000000;
XL_VECTOR_MAX_TEXT_CHARS        = 4096;
XL_VECTOR_MAX_TOTAL_TEXT_CHARS  = 1000000;
XL_VECTOR_MAX_IMAGE_SIDE        = 8192;
XL_VECTOR_MAX_IMAGE_PIXELS      = 32 * 1024 * 1024;
XL_VECTOR_MAX_IMAGE_BYTES       = 64 * 1024 * 1024;
XL_VECTOR_MAX_COORD             = 1000000000;

Dvě z nich si zaslouží poznámku. Strop hloubky device context 32 existuje proto, že se záznamy SaveDC a RestoreDC zanořují a nevyvážený stream může tlačit navždy; 32 je pro skutečné metasoubory štědré a levné na vynucení. Strop souřadnic existuje proto, že souřadnice živí transformaci a hodnota poblíž mezí rozsahu celých čísel produkuje transformovaný výsledek, který je buď nekonečný, nebo se přetočí, načež je každý výpočet ohraničujícího rámečku dále v toku nesmysl. Ořezávání souřadnic v okamžiku parsování je mnohem snazší uvažovat než bránit každého konzumenta geometrie

Konstanty rozpočtu dekódování v HotXLS lxVectorScene pro záznamy, handle, hloubku DC, příkazy, body, text, velikost obrázků a ořez souřadnic
Každý limit je pojmenovaná konstanta vynucovaná při parsování; strop hloubky DC a ořez souřadnic si zaslouží největší pozornost

Používání scény

Dekodér odevzdá objekt, který vlastníte, počet příkazů, nominální velikost a počet kreslicích záznamů, které se rozhodla nereprezentovat

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data drží surový obsah obrázku převzatý z pracovního sešitu
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Odmítnuto: hlavička, meze, součty, umístění EOF nebo rozpočet
    LogReject('metafile rejected: ' + Error);
    Exit;
  end;
  try
    if Scene.SkippedDrawRecords > 0 then
      LogWarning(Format('%d drawing records outside the safe subset',
        [Scene.SkippedDrawRecords]));
    for I := 0 to Scene.Count - 1 do
      case Scene.Commands[I].Kind of
        xlsvcRectangle: DrawRect(Scene.Commands[I]);
        xlsvcEllipse:   DrawEllipse(Scene.Commands[I]);
        xlsvcPolyline,
        xlsvcPolygon,
        xlsvcBezier:    DrawPath(Scene.Commands[I]);
        xlsvcText:      DrawText(Scene.Commands[I]);
        xlsvcImage:     DrawImage(Scene.Commands[I]);
      end;
  finally
    Scene.Free;
  end;
end;

Záznam příkazu nese vše, co backend potřebuje, a nic, co vyžaduje zařízení: přítomnost pera, barvu, šířku a styl; přítomnost štětce a barvu; geometrii; a pro text řetězec, název písma, velikost, styly a zarovnání. To činí tutéž scénu použitelnou on-screen canvas rendererem i SVG writerem a proto se vektorová cesta nerozchází mezi náhledem a exportem. Vykreslování obsahu listů na obrazovce obecně pokrývá článek o vykreslování vlastní mřížky VCL

Odmítnutí obrázku nepoškozuje pracovní sešit

Důležitou vlastností tohoto návrhu je, že odmítnuté dekódování ovlivní jen vykreslování. Původní obsah zůstává v modelu, takže pracovní sešit, který se otevře a znovu uloží, vynese své obrázky metasouborů ven bajt za bajtem, ať už je bezpečný dekodér dokázal nakreslit, nebo ne. Existující ohraničená rastrová cesta také zůstává dostupná jako záloha. Jinými slovy, striktní parser brání to, co se provede, nikoli to, co se zachová, což je rozlišení, jež dovolí bezpečnostně motivované změně vyjít, aniž by se změnila ve změnu se ztrátou dat

Obsluha kreslicích objektů obecně, včetně částí objektového modelu, které přežívají zpáteční cesty nedotčené, je předmětem článku o grafech, obrázcích a kresbách

Kde tím je serverové nasazení

Renderujete-li ve službě nahrané pracovní sešity uživatelů, praktická pozice je nyní obhajitelná: obrázky metasouborů parsuje kód, který můžete auditovat, ohraničený konstantami, které můžete číst, a nikdy není podán grafickému ovladači. Poctivá výhrada je pokrytí. Komplexní metasoubory produkované kreslicími nástroji narazí na počítadlo přeskočených záznamů a odpovědí je počítadlo vynést na povrch, nikoli potichu rozšířit whitelist. Obrázek, který se vykreslí částečně a řekne to, je konverzace s podporou; obrázek, který se vykreslí chybně a neřekne nic, je bug report od zákazníka

HotXLS obsluhuje XLS, XLSX, ODS a CSV nativně v Delphi a C++Builder bez nainstalovaného Excelu a tatáž filozofie ohraničeného parsování protéká jeho vrstvami kontejneru, vzorců a kreseb. Detaily formátů a bezpečnosti jsou uvedeny na stránce produktu HotXLS Delphi spreadsheet component