Техническа статия

Безопасно възпроизвеждане на ненадеждни EMF и WMF в HotXLS

Excel работна книга може да носи EMF и WMF картини и конвенционалният начин да се нарисува една е да се предаде байтовият поток на metafile плейъра на операционната система; Това е решение, което си заслужава да се погледне директно: metafile е сериализиран команден поток за graphics API и възпроизвеждането на един означава да позволите на файл, пристигнал по имейл, да кара graphics драйвера; HotXLS взема другия маршрут; XLSDecodeVectorScene парсва самия metafile, валидира header-а, всеки размер на запис, декларирания общ брой записи и точното разположение на end-of-file записа, отхвърля escape записи изцяло и връща TXLSVectorScene от примитивни команди за рисуване, които Canvas и SVG бекендовете възпроизвеждат през собствения си код; Никакво driver възпроизвеждане не е включено във всяка точка

HotXLS парсва ненадеждни EMF и WMF байтове от работни листи с XLSDecodeVectorScene в TXLSVectorScene команден списък вместо GDI metafile възпроизвеждане
HotXLS парсва самия metafile и връща примитивни команди за Canvas и SVG възпроизвеждане; конвенционалният маршрут изпълнява байтовия поток върху graphics стека

Размяната е покритие за обуздание; Команден whitelist, ориентиран към правоъгълници, няма да възпроизведе всеки metafile, който дизайнер може да създаде, така че сцената докладва колко записи за рисуване не може да представи и извикващият решава какво да направи по въпроса; За сървърен процес, изобразяващ документи, които не е създал, тази размяна е правилната посока

Защо metafile възпроизвеждането е лошо пасване за ненадежден вход?

Защото форматът не е картина, той е програма; EMF записов поток манипулира стек от device-context състояния, разпределя и избира обекти от handle таблица и може да носи escape записи, чиито payload се предава към device драйвер; Възпроизвеждането му упражнява пътища в платформения graphics стек, които са написани с предположението, че metafile-ът идва от сътрудничещо приложение на същата машина; Когато входът е прикачен файл от електронна таблица, това предположение е изчезнало и никакво количество грижи вътре в библиотеката за електронни таблица не помага, защото библиотеката не е компонентът, който прави парсването

Това е същото разсъждение, което управлява контейнерния слой; Работна книга е ZIP архив и HotXLS валидира неговия central directory, вместо да вярва на декларирани offsets, както е описано в статията за ZIP end-of-central-directory валидация; Metafile payload-ите са следващият слой на същия проблем

Какво декодерът проверява, преди да нарисува нещо

Валидацията е структурна и се случва предварително, защото парсер, който започва да рисува и валидира докато върви, вече е действал върху данни, които не е верифицирал; Header-ът трябва да съвпада строго, а не правдоподобно; Всеки запис трябва да декларира размер, който се побира вътре в оставащия буфер и е достатъчно голям за собствените му фиксирани полета; Броят записи, който header-ът декларира, трябва да съвпада с записите действително налични; End-of-file записът трябва да седи точно там, където потокът свършва, не просто някъде близо до него, което затваря трика с trailing боклук, криещ втори payload зад валидна картина

отвъд структурата декодерът е fail-closed по семантика; Escape записи се отхвърлят, не се пропускат; Запис, променящ състоянието, който декодерът не моделира, кара декодирането да се провали, вместо да бъде игнориран, защото игнорирането на промяна на състоянието означава всяка следваща команда за рисуване да се изпълнява в състояние, което файлът не е искал, а резултатът е картина, грешна по начин, който никой не може да предскаже; Записи за рисуване извън поддържания команден набор са друга материя: тези се броят и пропускат, защото липсваща форма е видима, докладваема дупка, а не мълшиво повреда

XLSDecodeVectorScene проверява header, размери на записи, общи бройки и EOF разположение предварително, после отхвърля escape записи и брои неподдържани draw записи
Структурни проверки тичат предварително и fail-closed семантика отхвърля escape записи, докато неподдържани записи за рисуване само се броят и пропускат

Бюджетите са част от договора на формата

Векторните формати имат собствена версия на декомпресионната бомба; Няколко килобайта записи могат да декларират полилинии със стотици милиони точки или изображение, чиито декларирани размери се умножават в терабайти; Границите следователно трябва да са изрични константи, а не каквото машината случайно оцелее

// От lxVectorScene: бюджетът за декодиране, заявени, а не подразбиращи се
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;

Две от тези заслужават бележка; Таванът за дълбочина на device-context от 32 съществува, защото записите SaveDC и RestoreDC се влагат и небалансиран поток може да push-ва завинаги; 32 е щедро за реални metafile-и и евтино за налагане; Таванът за координати съществува, защото координатите захранват трансформация и стойност близо до границите на целочисления диапазон произвежда трансформиран резултат, който е или безкраен, или wrap-ва, след което всяко изчисление на bounding box надолу по веригата е безсмислица; Ограничаването на координатите по време на парсване е много по-лесно за разсъждение, отколкото защитаването на всеки консуматор на геометрията

Константи на decode бюджет в HotXLS lxVectorScene за записи, handle-и, DC дълбочина, команди, точки, текст, размер на изображение и ограничаване на координати
Всяка граница е именувана константа, налагана по време на парсване; таванът за DC дълбочина и ограничението на координатите заслужават най-много внимание

Използване на сцената

Декодерът връща обект, който вие притежавате, брой команди, номинален размер и брой записи за рисуване, които е избрал да не представи

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data държи суровия picture payload, взет от работната книга
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Отказано: header, граници, общи бройки, EOF разположение или бюджет
    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;

Командният запис носи всичко, от което един бекенд се нуждае и нищо, което изисква устройство: наличие на pen, цвят, ширина и стил; наличие на brush и цвят; геометрията; и за текста низа, име на шрифт, размер, стилове и подравняване; Това е това, което прави същата сцена използваема и от on-screen canvas renderer-а, и от SVG writer-а и е причината векторният път да не се разминава между preview и export; Изобразяването на екран на съдържание от работен лист изобщо е разгледано в статията за rendering на собствен VCL grid

Отхвърлянето на картина не поврежда работната книга

Важно свойство на този дизайн е, че отказан decode засяга само изобразяването; Оригиналният payload остава в модела, така че работна книга, която се отваря и записва отново, изнася своите metafile картини байт по байт, независимо дали безопасният декодер можеше да ги нарисува; Съществуващият ограничен растерен път също остава наличен като fallback; С други думи, строгият парсер огражда какво се изпълнява, не какво се запазва, което е разграничението, което позволява промяна, мотивирана от сигурността, да се достави, без да се превръща в промяна, загубваща данни

Обработката на drawing обекти изобщо, включително частите на обектния модел, които оцеляват през round-trip-и недокоснати, е разгледана в статията за графики, изображения и рисунки

Къде това оставя сървърно внедряване

Ако изобразявате качени от потребители работни книги в услуга, практическата позиция вече е защитима: metafile картини се парсват от код, който можете да одитирате, ограничени от константи, които можете да прочетете и никога не се предават на graphics драйвер; Честното уточнение е покритието; Сложни metafile-и, произведени от инструменти за рисуване, ще ударят брояча на пропуснати записи и отговорът на това е да изведете брояча на повърхността, а не да разширите whitelist-а тихо; Картина, която се изобразява частично и го казва, е разговор с поддръжката; картина, която се изобразява грешно и не казва нищо, е доклад за дефект от клиент

HotXLS обработва XLS, XLSX, ODS и CSV нативно в Delphi и C++Builder без инсталиран Excel и същата философия за ограничено парсване минава през неговите container, formula и drawing слоеве; Форматни и защитни детайли са изброени на продуктовата страница HotXLS Delphi spreadsheet component