Technischer Artikel

Sichere EMF- und WMF-Wiedergabe in HotXLS

Eine Excel-Arbeitsmappe kann EMF- und WMF-Bilder mitführen, und der konventionelle Weg, eines zu zeichnen, ist, den Bytestream dem Metadatei-Player des Betriebssystems zu übergeben. Das ist eine Entscheidung, die man direkt anschauen sollte: Eine Metadatei ist ein serialisierter Befehlsstrom für eine Grafik-API, und sie abzuspielen bedeutet, eine Datei, die per E-Mail ankam, den Grafiktreiber steuern zu lassen. HotXLS nimmt den anderen Weg. XLSDecodeVectorScene parst die Metadatei selbst, validiert den Header, jede Datensatzgröße, die deklarierte Datensatzgesamtzahl und die exakte Platzierung des End-of-File-Datensatzes, weist Escape-Datensätze kategorisch zurück und gibt eine TXLSVectorScene aus primitiven Zeichnenbefehlen zurück, die die Canvas- und SVG-Backends durch ihren eigenen Code abspielen. Kein Treiber-Playback ist an irgendeinem Punkt beteiligt

HotXLS parst nicht vertrauenswürdige EMF- und WMF-Arbeitsblatt-Bytes mit XLSDecodeVectorScene in eine TXLSVectorScene-Befehlsliste statt GDI-Metadatei-Playback
HotXLS parst die Metadatei selbst und gibt primitive Befehle für Canvas- und SVG-Wiedergabe zurück; der konventionelle Weg führt den Bytestream auf dem Grafikstack aus

Der Tausch ist Abdeckung gegen Eindämmung. Eine rechteckorientierte Befehls-Whitelist wird nicht jede Metadatei reproduzieren, die ein Designer erzeugen kann, also meldet die Szene, wie viele Zeichnen-Datensätze sie nicht darstellen konnte, und der Aufrufer entscheidet, was er damit macht. Für einen Serverprozess, der Dokumente rendert, die er nicht selbst erzeugt hat, ist dieser Tausch die richtige Richtung

Warum ist Metadatei-Playback ein schlechter Fit für nicht vertrauenswürdige Eingaben?

Weil das Format kein Bild ist, sondern ein Programm. Ein EMF-Datensatzstrom manipuliert einen Gerätekontext-Zustandsstapel, allokiiert und wählt Objekte aus einer Handle-Tabelle und kann Escape-Datensätze tragen, deren Nutzlast an einen Gerätetreiber durchgereicht wird. Es abzuspielen übt Pfade im Grafikstack der Plattform aus, die unter der Annahme geschrieben wurden, die Metadatei stamme von einer kooperierenden Anwendung auf derselben Maschine. Wenn die Eingabe ein Tabellen-Anhang ist, ist diese Annahme dahin, und keine Sorgfalt innerhalb der Tabellenbibliothek hilft, denn die Bibliothek ist nicht die Komponente, die parst

Das ist dieselbe Begründung, die die Container-Schicht regiert. Eine Arbeitsmappe ist ein ZIP-Archiv, und HotXLS validiert sein Central Directory, statt deklarierten Offsets zu vertrauen, wie in dem Artikel zur ZIP-End-of-Central-Directory-Validierung beschrieben. Metadatei-Nutzlasten sind die nächste Schicht desselben Problems

Was der Dekodierer prüft, bevor er irgendetwas zeichnet

Die Validierung ist strukturell und geschieht vorab, denn ein Parser, der zu zeichnen beginnt und beim Gehen validiert, hat bereits auf nicht verifizierten Daten gehandelt. Der Header muss strikt statt plausibel übereinstimmen. Jeder Datensatz muss eine Größe deklarieren, die in den verbleibenden Puffer passt und groß genug für seine eigenen festen Felder ist. Die Datensatzzahl, die der Header deklariert, muss zu den tatsächlich vorhandenen Datensätzen passen. Der End-of-File-Datensatz muss exakt dort sitzen, wo der Stream endet, nicht bloß irgendwo in der Nähe, was den Trailing-Garbage-Trick abschneidet, der eine zweite Nutzlast hinter einem gültigen Bild versteckt

Jenseits der Struktur ist der Dekodierer bei der Semantik fail-closed. Escape-Datensätze werden abgewiesen, nicht übersprungen. Ein zustandsändernder Datensatz, den der Dekodierer nicht modelliert, lässt die Dekodierung scheitern, statt ignoriert zu werden, denn eine Zustandsänderung zu ignorieren bedeutet, dass jeder nachfolgende Zeichnen-Befehl in einem Zustand ausgeführt wird, den die Datei nicht verlangt hat, und das Ergebnis ist ein Bild, das auf eine Weise falsch ist, die niemand vorhersagen kann. Zeichnen-Datensätze außerhalb des unterstützten Befehlssatzes sind eine andere Sache: Sie werden gezählt und übersprungen, denn eine fehlende Form ist eine sichtbare, meldbare Lücke statt einer stillen Korruption

XLSDecodeVectorScene prüft Header, Datensatzgrößen, Summen und EOF-Platzierung vorab, weist dann Escape-Datensätze zurück und zählt nicht unterstützte Zeichnen-Datensätze
Strukturelle Prüfungen laufen vorab, und fail-closed-Semantik weist Escape-Datensätze zurück, während nicht unterstützte Zeichnen-Datensätze nur gezählt und übersprungen werden

Budgets sind Teil des Formatvertrags

Vektorformate haben ihre eigene Version der Dekompressionsbombe. Ein paar Kilobyte Datensätze können Polylines mit Hunderten von Millionen Punkten deklarieren oder ein Bild, dessen deklarierte Abmessungen sich zu Terabytes multiplizieren. Grenzen müssen daher explizite Konstanten sein, nicht das, was die Maschine zufällig überlebt

// Aus lxVectorScene: das Dekodier-Budget, ausgesprochen statt implizit
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;

Zwei davon verdienen eine Notiz. Die Gerätekontext-Tiefenbegrenzung von 32 existiert, weil SaveDC- und RestoreDC-Datensätze verschachteln, und ein unausgeglichener Stream kann ewig pushen; 32 ist großzügig für echte Metadateien und billig durchzusetzen. Die Koordinatenbegrenzung existiert, weil Koordinaten in eine Transformation eingespeist werden, und ein Wert nahe den Grenzen des Integer-Bereichs erzeugt ein transformiertes Ergebnis, das unendlich ist oder überläuft, wonach jede nachgelagerte Bounding-Box-Berechnung Unsinn ist. Koordinaten zur Parse-Zeit festzuklemmen ist viel leichter zu durchdenken, als jeden Konsumenten der Geometrie zu verteidigen

Dekodier-Budget-Konstanten in HotXLS lxVectorScene für Datensätze, Handles, DC-Tiefe, Befehle, Punkte, Text, Bildgröße und Koordinatenklemmung
Jede Grenze ist eine benannte Konstante, die beim Parsen durchgesetzt wird; die DC-Tiefenbegrenzung und die Koordinatenklemme verdienen die meiste Aufmerksamkeit

Die Szene verwenden

Der Dekodierer reicht ein Objekt zurück, das Ihnen gehört, eine Befehlszahl, eine Nenngröße und eine Zählung der Zeichnen-Datensätze, die er nicht darstellen wollte

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data enthält die rohe Bild-Nutzlast aus der Arbeitsmappe
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Abgewiesen: Header, Grenzen, Summen, EOF-Platzierung oder ein 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;

Der Befehlsdatensatz trägt alles, was ein Backend braucht, und nichts, das ein Gerät verlangt: Pen-Präsenz, Farbe, Breite und Stil; Brush-Präsenz und Farbe; die Geometrie; und für Text die Zeichenkette, Fontname, Größe, Stile und Ausrichtung. Das macht dieselbe Szene sowohl für den Bildschirm-Canvas-Renderer als auch für den SVG-Writer brauchbar, und es ist der Grund, warum der Vektorpfad zwischen Vorschau und Export nicht auseinanderläuft. Das Bildschirm-Rendering von Arbeitsblattinhalten im Allgemeinen behandelt der Artikel zum Custom-VCL-Grid-Rendering

Ein Bild abzulehnen beschädigt die Arbeitsmappe nicht

Eine wichtige Eigenschaft dieses Designs: Eine abgelehnte Dekodierung wirkt sich nur auf das Rendering aus. Die ursprüngliche Nutzlast bleibt im Modell, also trägt eine Arbeitsmappe, die geöffnet und erneut gespeichert wird, ihre Metadatei-Bilder byte für Byte hinaus, ob der sichere Dekodierer sie zeichnen konnte oder nicht. Der bestehende begrenzte Rasterpfad bleibt ebenfalls als Fallback verfügbar. Mit anderen Worten: Der strikte Parser regelt, was ausgeführt wird, nicht was bewahrt wird, und das ist der Unterschied, der es einer sicherheitsmotivierten Änderung erlaubt auszuliefern, ohne in eine Datenverlust-Änderung zu kippen

Der Umgang mit Zeichnungsobjekten im Allgemeinen, einschließlich der Teile des Objektmodells, die Round-Trips unangetastet überstehen, behandelt der Artikel zu Diagrammen, Bildern und Zeichnungen

Wo das ein Server-Deployment lässt

Wer in einem Dienst hochgeladene Arbeitsmappen rendert, hat nun eine verteidigungsfähige Position: Metadatei-Bilder werden von Code geparst, den man prüfen kann, begrenzt durch Konstanten, die man lesen kann, und niemals an einen Grafiktreiber übergeben. Die ehrliche Einschränkung ist die Abdeckung. Komplexe Metadateien aus Zeichenwerkzeugen treffen auf den Zähler der übersprungenen Datensätze, und die Antwort darauf ist, den Zähler sichtbar zu machen, statt die Whitelist still zu verbreitern. Ein Bild, das teilweise rendiert und das auch sagt, ist ein Support-Gespräch; ein Bild, das falsch rendiert und nichts sagt, ist ein Bugreport von einem Kunden

HotXLS verarbeitet XLS, XLSX, ODS und CSV nativ in Delphi und C++Builder ohne installiertes Excel, und dieselbe Philosophie des begrenzten Parsens zieht sich durch seine Container-, Formel- und Zeichnungsschichten. Format- und Sicherheitsdetails listet die Produktseite der HotXLS Delphi Tabellenkomponente