Technisch artikel

Onbetrouwbare EMF en WMF veilig herafspelen in HotXLS

Een Excel-werkboek kan EMF- en WMF-afbeeldingen meedragen, en de conventionele manier om er één te tekenen is de bytestream aan de metafile-speler van het besturingssysteem overhandigen. Dat is een beslissing om rechtstreeks naar te kijken: een metafile is een geserialiseerde commandostroom voor een graphics-API, en er één herafspelen betekent een bestand dat via e-mail binnenkwam de grafische driver laten besturen. HotXLS neemt de andere route. XLSDecodeVectorScene parseert de metafile zelf, valideert de header, elke recordgrootte, het gedeclareerde recordtotaal en de exacte plaatsing van het end-of-file-record, verwerpt escape-records ronduit en geeft een TXLSVectorScene van primitieve tekencommandos terug die de Canvas- en SVG-backends via hun eigen code herafspelen. Op geen enkel moment is driver-afspeel betrokken

HotXLS parseert onbetrouwbare EMF- en WMF-werkbladbytes met XLSDecodeVectorScene naar een TXLSVectorScene-commandolijst in plaats van GDI-metafile-afspeel
HotXLS parseert de metafile zelf en geeft primitieve commandos terug voor herafspelen in Canvas en SVG; de conventionele route voert de bytestream uit op de graphics-stack

De ruil is dekking tegen insluiting. Een op rechthoeken gerichte commandowhitelist zal niet elke metafile reproduceren die een ontwerper kan maken, dus de scène meldt hoeveel tekenrecords die niet kon representeren en de aanroeper beslist wat ermee te doen. Voor een serverproces dat documenten rendert die het niet zelf maakte, is die ruil de juiste kant op

Waarom past metafile-afspeel slecht bij onbetrouwbare input?

Omdat het formaat geen afbeelding is, het is een programma. Een EMF-recordstroom manipuleert een statestack van de device context, wijst objecten toe uit een handle-tabel en selecteert ze, en kan escape-records meedragen waarvan de payload wordt doorgegeven aan een apparaatstuurprogramma. Het herafspelen ervan oefent paden uit in de graphics-stack van het platform die zijn geschreven onder de aanname dat de metafile afkomstig was van een meewerkende applicatie op dezelfde machine. Wanneer de input een spreadsheetbijlage is, is die aanname weg, en geen enkele zorgvuldigheid binnen de spreadsheetbibliotheek helpt omdat de bibliotheek niet het onderdeel is dat parseert

Dit is dezelfde redenering die de containerlaag stuurt. Een werkboek is een ZIP-archief, en HotXLS valideert zijn central directory in plaats van gedeclareerde offsets te vertrouwen, zoals beschreven in het artikel over ZIP end-of-central-directory-validatie. Metafile-payloads zijn de volgende laag van hetzelfde probleem

Wat de decoder controleert voordat die iets tekent

De validatie is structureel en vindt vooraf plaats, want een parser die begint te tekenen en onderweg valideert heeft al gehandeld naar gegevens die niet waren geverifieerd. De header moet strikt overeenkomen, niet plausibel. Elke record moet een grootte declareren die binnen de resterende buffer past en groot genoeg is voor zijn eigen vaste velden. Het recordaantal dat de header declareert moet overeenkomen met de werkelijk aanwezige records. Het end-of-file-record moet exact liggen waar de stream eindigt, niet slechts ergens in de buurt, wat de trailing-garbage-truc afsluit die een tweede payload achter een geldige afbeelding verbergt

Voorbij de structuur is de decoder fail-closed op semantiek. Escape-records worden geweigerd, niet overgeslagen. Een toestandsveranderend record dat de decoder niet modelleert laat de decode falen in plaats van genegeerd te worden, want een toestandswissel negeren betekent dat elk daaropvolgend tekencommando wordt uitgevoerd in een toestand die het bestand niet vroeg, en het resultaat is een afbeelding die fout is op een manier die niemand kan voorspellen. Tekenrecords buiten de ondersteunde commandoset zijn een andere zaak: die worden geteld en overgeslagen, want een ontbrekende vorm is een zichtbaar, rapporteerbaar gat in plaats van een geruisloze corruptie

XLSDecodeVectorScene controleert vooraf header, recordgroottes, totalen en EOF-plaatsing, weigert daarna escape-records en telt niet-ondersteunde tekenrecords
Structurele controles draaien vooraf en fail-closed-semantiek weigert escape-records, terwijl niet-ondersteunde tekenrecords alleen worden geteld en overgeslagen

Budgets horen bij het formaatcontract

Vectorformaten hebben hun eigen versie van de decompressiebom. Een paar kilobyte aan records kan polylines met honderden miljoenen punten declareren, of een afbeelding waarvan de gedeclareerde afmetingen vermenigvuldigen naar terabytes. Grenzen moeten dus expliciete constanten zijn in plaats van wat de machine toevallig overleeft

// Uit lxVectorScene: het decode-budget, uitgesproken in plaats van geïmpliceerd
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;

Twee daarvan verdienen een notitie. De dieptecap van 32 op de device context bestaat omdat SaveDC- en RestoreDC-records nesten, en een uit balans zijnde stream kan oneindig pushen; 32 is royaal voor echte metafiles en goedkoop af te dwingen. De coördinaatcap bestaat omdat coördinaten een transformatie voeden, en een waarde nabij de grenzen van het integerbereik levert een getransformeerd resultaat op dat oneindig is of omslaat, waarna elke bounding-boxberekening stroomafwaarts onzin is. Coördinaten bij parsen begrenzen is veel makkelijker te redeneren dan elke verbruiker van de geometrie verdedigen

Decode-budgetconstanten in HotXLS lxVectorScene voor records, handles, DC-diepte, commandos, punten, tekst, afbeeldingsgrootte en coördinaatbegrenzing
Elke limiet is een benoemde constante die tijdens het parsen wordt afgedwongen; de DC-dieptecap en de coördinaatbegrenzing verdienen de meeste aandacht

De scène gebruiken

De decoder geeft een object terug dat van u is, een commandotelling, een nominale grootte en een telling van tekenrecords die die niet wilde representeren

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data bevat de ruwe afbeeldingspayload uit het werkboek
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Geweigerd: header, grenzen, totalen, EOF-plaatsing of een budget
    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;

Het commandorecord draagt alles mee dat een backend nodig heeft en niets dat een apparaat vereist: pen-aanwezigheid, kleur, breedte en stijl; brush-aanwezigheid en kleur; de geometrie; en bij tekst de string, fontnaam, grootte, stijlen en uitlijning. Dat maakt dezelfde scène bruikbaar voor zowel de on-screen canvas-renderer als de SVG-schrijver, en dat is waarom het vectorpad niet uiteenloopt tussen voorvertoning en export. Schermrendering van werkbladinhoud in het algemeen wordt behandeld in het artikel over custom VCL grid-rendering

Een afbeelding weigeren beschadigt het werkboek niet

Een belangrijke eigenschap van dit ontwerp is dat een geweigerde decode alleen het renderen raakt. De oorspronkelijke payload blijft in het model, dus een werkboek dat wordt geopend en opnieuw opgeslagen voert zijn metafile-afbeeldingen byte voor byte mee, of de veilige decoder ze nu kon tekenen of niet. Het bestaande begrensde rasterpad blijft ook als fallback beschikbaar. Anders gezegd: de strenge parser begrenst wat wordt uitgevoerd, niet wat wordt bewaard, en dat is het onderscheid dat een uit beveiligingsoogpunt gemotiveerde wijziging laat verschepen zonder in een gegevensverlies-wijziging te veranderen

De afhandeling van tekenobjecten in het algemeen, inclusief de delen van het objectmodel die round-trips onaangetast overleven, wordt behandeld in het artikel over grafieken, afbeeldingen en tekeningen

Waar dit een serveruitrol laat staan

Rendert u in een dienst werkboeken die gebruikers uploaden, dan is de praktische positie nu verdedigbaar: metafile-afbeeldingen worden geparseerd door code die u kunt auditen, begrensd door constanten die u kunt lezen, en nooit aan een grafische driver overhandigd. De eerlijke kanttekening is dekking. Complexe metafiles uit tekenhulpmiddelen zullen de teller van overgeslagen records raken, en het antwoord daarop is de teller zichtbaar te maken in plaats van de whitelist geruisloos te verbreden. Een afbeelding die deels rendert en dat zegt is een supportgesprek; een afbeelding die verkeerd rendert en niets zegt is een bugrapport van een klant

HotXLS verwerkt XLS, XLSX, ODS en CSV natively in Delphi en C++Builder zonder geïnstalleerde Excel, en dezelfde begrensd-parsen-filosofie loopt door zijn container-, formule- en tekenlagen. Formaat- en beveiligingsdetails staan op de productpagina van de HotXLS Delphi spreadsheet component