Teknisk artikkel

HotXLS sperrer formel-callbacks: CALL og WEBSERVICE

HotXLS nekter å rute 20 farlige formelnavn, blant dem CALL, REGISTER.ID, WEBSERVICE og DDE, videre til dine Delphi user-function-callbacks med mindre du sier deg selv ja til det. Arbeidsbokegenskapen AllowUnsafeFormulaCallbacks står som standard til False, sjekken kjører før noe argument evalueres, og et avvist kall rapporterer xlfeUnsafeFunctionDenied uten å aktivere én eneste handler

Scenariet som gjorde dette nødvendig, er helt hverdagslig. En tjeneste tar imot opplastede XLS- eller XLSX-filer, rekalkulerer dem på serversiden og leser noen totalsummer tilbake. Vertsapplikasjonen registrerte en OnUserFunction-handler for år siden for et par forretningsfunksjoner, og et sted på veien fikk den handleren en catch-all-gren som videresender alt den ikke gjenkjenner, til en plugintabell. Ingen på teamet skrev noensinne =WEBSERVICE(...) i en celle. Opplasteren gjorde det. Å holde den formelen intakt gjennom åpning, rekalkulering og lagring er en filtroverdighetsfunksjon. Å la den nå kode i verten som kan åpne sockets eller filer, er en autorisasjonsbeslutning, og helt til HotXLS skilte de to, tok biblioteket den beslutningen stille på dine vegne

Hvorfor ble det å bevare en formel tillatelse til å kjøre den?

Rotårsaken var én enkelt fallback-sti. HotXLS parser hvert Excel-funksjonsnavn det kjenner, men ikke hvert kjente navn har en implementasjon i beregningsmotoren. Innebygde funksjoner som ble gjenkjent, men ikke implementert, endte tidligere i samme user-defined function-fallback som ekte egendefinerte navn, så CALL og REGISTER.ID delte en dispatch-sti med dine DISCOUNT eller REGIONRATE. Ukjente navn som WEBSERVICE eller DDE kunne på samme måte treffe en likes navngitt oppføring i arbeidsbokens register, det prosessomfattende registeret eller en eventhandler. Mekanikken i den fallback-en er dekket i hvordan HotXLS løser opp egendefinerte funksjoner gjennom OnUserFunction; problemet var at ingenting på den stien spurte om navnet selv var en funksjon et fornuftig program noensinne burde kjøre

Dispatch-rekkefølgen betyr noe for hva «ukjent» betyr her. Et kall motoren ikke kan evaluere nativt, tilbys i tur og orden leksikale LAMBDA- og LET-bindinger, som closure-støtten i HotXLS formelmotor løser opp først, deretter arbeidsboklokale funksjoner registrert med RegisterUserFunction, så prosessomfattende funksjoner fra TXLSWorkbook.RegisterGlobalUserFunction og til slutt hendelsene OnUserFunction og OnUserFunctionEx. Først når alle sier nei, blir en virkelig ukjent funksjon til #NAME?. Hvert trinn etter lambda-oppslaget gir kontrollen til kode du har skrevet, og det er nøyaktig derfor sikkerhetssjekken må sitte foran hele kjeden i stedet for inne i én vilkårlig handler

Hvordan HotXLS porter utrygge formel-callbacks: GetValueItemUserFunction sjekker navnet før argumentmatrisen bygges eller noen resolver kjører, så et nestet =WEBSERVICE(AUDIT_TOKEN()) avslutter med lxErrorUnsafeFunctionDenied og revisjonsloggen forblir tom, mens trygge kall går kjeden fra LAMBDA- og LET-bindinger ned til OnUserFunction
Først når hvert trinn sier nei, blir en virkelig ukjent funksjon til #NAME?, og det er derfor sikkerhetssjekken sitter foran hele kjeden i stedet for inne i én vilkårlig handler

Hvilke funksjonsnavn blokkerer HotXLS som standard?

XLSFormulaCallbackIsUnsafe i lxCalc.pas holder et fast nevningssett på 20 navn: DDE, CALL, REGISTER, REGISTER.ID, WEBSERVICE, RTD, SQL.REQUEST, EXEC, RUN, CREATE.OBJECT, APP.ACTIVATE, SEND.KEYS, OPEN, SAVE, SAVE.AS, FOPEN, FWRITE, FWRITELN, FCLOSE og FILE.DELETE. Det er navnene som, i Excel eller makrospråket dets, laster inn native kode, når nettverket, snakker med andre prosesser eller rører filsystemet. Før sammenligningen trimmer funksjonen omkringliggende mellomrom, konverterer navnet til store bokstaver og stripper ett enkelt _XLFN.- eller _XLWS.-prefiks, så _xlfn.webservice skrevet av en nyere Excel-versjon fanges opp akkurat som den bare stavemåten. Listen bor ved kalkulatorgrensen i stedet for i Classic-, XLSX- og ODS-parserne, noe som holder én AST, én BIFF-tokenstrøm og én konvertert arbeidsbok til å oppføre seg identisk

Hvordan HotXLS normaliserer et funksjonsnavn før sammenligningen mot utrygge callbacks: mellomrom trimmes, navnet konverteres til store bokstaver og ett enkelt _XLFN.- eller _XLWS.-prefiks strippes, så _xlfn.webservice fanges opp som den bare stavemåten, og resultatet matches deretter eksakt mot det faste nevningssettet på 20 navn i lxCalc.pas
Listen spenner over navnene som laster inn native kode, når nettverket, snakker med andre prosesser eller rører filsystemet, fra DDE, CALL og WEBSERVICE til FWRITE og FILE.DELETE

To kanter er verdt å kjenne før du støtter deg til den. Treffen er eksakt, så en handler du registrerer som MYWEBSERVICE, berøres ikke, og motsatt blir en lovlig intern UDF som tilfeldigvis heter OPEN eller RUN, nå avvist som standard. Nevningssettet er heller ikke en sandkasse for dine egne handlere. Kjører catch-all-grenen din vilkårlige pluginnavn, stopper gaten de beryktede farlige og ingenting annet; den varige fiksen er fortsatt en handler som matcher en eksplisitt tillatelsesliste med SameText og lar Handled stå på False for alt den ikke eier

Hvorfor må gaten kjøre før argumentevaluering?

En gate som utløses etter at argumentene er beregnet, kommer for sent, fordi argumentene i seg selv kan kalle koden din. GetValueItemUserFunction sjekker navnet først og avslutter med lxErrorUnsafeFunctionDenied før den bygger argumentmatrisen, før den rådfører seg med en resolver eller et av registrene, og til og med før den legger merke til at ingen handler i det hele tatt er tilordnet. Den rekkefølgen er det som beseirer det nestede tilfellet under, der det ytre kallet ville blitt nektet likevel, men en uskyldig utseende indre UDF ellers ville utløst først og etterlatt bieffekten sin

procedure TImportService.HandleUdf(Sender: TObject;
  const FunctionName: WideString; const Args: Variant;
  var Value: Variant; var Handled: Boolean);
begin
  if SameText(FunctionName, 'AUDIT_TOKEN') then
  begin
    FAuditLog.Add('AUDIT_TOKEN evaluated');   // bieffekt i vertskoden
    Value := 'token-42';
    Handled := True;
  end;
end;

Book.OnUserFunction := HandleUdf;
Eval := Sheet.EvaluateFormulaAt(1, 1, '=WEBSERVICE(AUDIT_TOKEN())');
// Eval.Status = xlfeUnsafeFunctionDenied, Eval.Value = Null,
// Eval.Issue.NativeCode = -106, og FAuditLog er fortsatt tom

Arbeidsbokstandard versus per-kall TXLSFormulaEvaluationOptions

Arbeidsbokflagget er standarden, og per-kall-valget har siste ord. TXLSWorkbook.AllowUnsafeFormulaCallbacks og TXLSXWorkbook.AllowUnsafeFormulaCallbacks styrer vanlig rekalkulering, Calculate, den toargument EvaluateFormulaAt-varianten, evalueringsmaler, skrivebeskyttede visninger og, på XLSX, hver worker i det parallelle rekalkuleringsbassenget. Ethvert inngangspunkt som tar imot en eksplisitt TXLSFormulaEvaluationOptions-record, behandler Options.AllowUnsafeFormulaCallbacks som kjennelsen for det kallet og OR-er den ikke sammen med arbeidsbokegenskapen. Den asymmetrien er bevisst: en betrodd intern jobb kan autorisere ett RTD-oppslag uten å vippe hele arbeidsboken, og en arbeidsbok som er globalt optet inn, kan fortsatt tvinge en sensitiv evaluering tilbake til avslag

var
  Options: TXLSFormulaEvaluationOptions;
  Eval: TXLSFormulaEvaluationResult;
begin
  // arbeidsboken forblir låst, ett betrodd kall slippes gjennom
  Book.AllowUnsafeFormulaCallbacks := False;
  Options := XLSDefaultFormulaEvaluationOptions;
  Options.AllowUnsafeFormulaCallbacks := True;
  Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);

  // arbeidsboken er optet inn, men denne evalueringen av opplastet tekst er det ikke
  Book.AllowUnsafeFormulaCallbacks := True;
  Options := XLSDefaultFormulaEvaluationOptions;   // flagget er False igjen
  Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
  if Eval.Status = xlfeUnsafeFunctionDenied then
    LogRejected(Eval.Issue.Message);
end;

Å veksle arbeidsbokegenskapen merker også avhengighetsgrafen som skitten i begge motorer. Uten det steget kunne et cachet resultat beregnet mens callbacks var tillatt, blitt servert etter at de var trukket tilbake, eller et cachet xlfeUnsafeFunctionDenied-utfall kunne leve lenger enn en opt-in. Den nye statusen ble lagt til bakerst i TXLSFormulaEvaluationStatus etter xlfeFailed, så den har ordinal 10, og hver eksisterende ordinal beholder verdien sin; den samme legg-bakerst-regelen gjelder feltet i options-recorden og getteren og setteren i IXLSWorkbook, selv om en konsument bygget mot en eldre utgivelse fortsatt trenger rekompilering

Hva skjer med ukjent og utrygg formeltekst ved lagring?

Å beholde en formel og å kjøre den er nå to adskilte spørsmål, og inngangspolicyen svarer bare på det første. FormulaEntryPolicy på begge arbeidsbokklassene bærer UnknownFunctionMode og UnknownNameMode, begge med standardverdien xlfusmReject, så å tildele en formel med et ukjent kall gjennom den vanlige Formula-egenskapen avvises før celleverdien, formel-cachen eller avhengighetene endres. ValidateFormulaEntry rapporterer den samme kjennelsen uten bieffekter. Betrode stier som filinnlesing, kopiering og formatkonvertering går utenom den brukerbaserte inngangspolicyen, fordi et strengt standardvalg aldri må avvise symboler som allerede finnes i en fil du bare åpner

var
  Policy: TXLSFormulaEntryPolicy;
begin
  Policy := Book.FormulaEntryPolicy;
  Policy.UnknownFunctionMode := xlfusmPreserve;   // kompatibilitetsinngang
  Book.FormulaEntryPolicy := Policy;
  Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)';  // lagret, ikke autorisert
  Book.SaveAs('rates.xls');
end;

I Classic BIFF8 har et ukjent kall ingen egen token, så HotXLS skriver det slik Excel skriver tilleggsfunksjoner. Formelen får en PtgNameX-token ($59) hvis XTI-oppføring peker på tilleggs-SUPBOOK-en med begge arkindekser satt til $FFFE, etterfulgt av argumenttokenene og en PtgFuncVar som bærer funksjonsnummer 255 og et argumentantall som inkluderer navneplassen. Den underliggende ExternName-kroppen er seks nullbyte, en lengdebyte og Unicode-flagg, UTF-16-funksjonsnavnet, deretter en to-byte-formel av $1C $17, en PtgErr som holder #REF!. Skriveren nekter navn lengre enn 255 tegn, mer enn 29 argumenter, og BIFF5-målet. Hvordan HotXLS klassifiserer disse tilleggs-SUPBOOK-oppføringene ved siden av eksterne arbeidsboklenker, er forklart i SUPBOOK- og XTI-klassifiseringsreglene for BIFF eksterne lenker. XLSX beholder rå funksjonstekst og ODS beholder msoxl:-formelen sin, og i hvert format åpnes en fil som lagret =WEBSERVICE(...) igjen med teksten intakt og evalueres fortsatt til xlfeUnsafeFunctionDenied som standard

Hvordan HotXLS skriver et ukjent formelkall inn i klassisk BIFF8: formelen bærer en PtgNameX-token hvis XTI-oppføring peker på tilleggs-SUPBOOK-en med begge arkindekser $FFFE, deretter argumenttokenene og en PtgFuncVar med funksjonsnummer 255, støttet av en ExternName-kropp som ender i en to-byte $1C $17 PtgErr som holder #REF!
XLSX beholder rå funksjonstekst og ODS beholder msoxl:-formelen sin, så en fil som lagret =WEBSERVICE(...) åpnes igjen med teksten intakt og evalueres fortsatt til xlfeUnsafeFunctionDenied som standard

Evaluerer pipelinen din arbeidsbøker den ikke selv har forfattet, la AllowUnsafeFormulaCallbacks stå på False, hold handlere på en eksplisitt tillatelsesliste, og gi per-kall-valg bare der formelkilden er din egen. Hele callback-, inngangspolicy- og evaluerings-API-et er dokumentert hos HotXLS Delphi spreadsheet-komponenten