HotXLS odbija usmjeriti 20 opasnih imena formula, među njima CALL, REGISTER.ID, WEBSERVICE i DDE, Vašim Delphi user-function callbackovima osim ako Vi to izričito uključite. Svojstvo radne knjige AllowUnsafeFormulaCallbacks zadano je False, provjera se izvršava prije evaluacije ijednog argumenta, a odbijeni poziv javlja xlfeUnsafeFunctionDenied ne pozvavši nijedan handler
Scenarij koji je ovo učinio nužnim svakidašnji je. Servis prima uploadane XLS ili XLSX datoteke, preračunava ih na serveru i čita natrag par zbrojeva. Host aplikacija registrirala je OnUserFunction handler prije godina za par poslovnih funkcija, i negdje uz put taj handler izrastao je u catch-all granu koja sve što ne prepoznaje prosljeđuje tablici plugina. Nitko u timu nikad nije utipkao =WEBSERVICE(...) u ćeliju. Uploader jest. Zadržati tu formulu netaknutom kroz otvaranje, preračun i spremanje značajka je vjernosti datoteke. Pustiti je do host koda koji može otvoriti sockete ili datoteke odluka je o ovlastima, i dok HotXLS nije odvojio jedno od drugog, biblioteka je tu odluku tiho donosila na Vaše ime
Zašto je čuvanje formule postalo dopuštenje za njeno izvršavanje?
Korijenski uzrok bila je jedna fallback putanja. HotXLS parsira svako ime Excel funkcije koje poznaje, ali ne svako poznato ime ima implementaciju u kalkulacijskom engineu. Ugrađena imena koja su prepoznata a neimplementirana padala su u isti user-defined function fallback kao istinski custom imena, pa su CALL i REGISTER.ID dijelili dispatch putanju s Vašim DISCOUNT ili REGIONRATE. Nepoznata imena poput WEBSERVICE ili DDE mogla su jednako tako poklopiti istoimeni unos u registru radne knjige, registru cijelog procesa ili event handleru. Mehanika tog fallbacka pokrivena je u kako HotXLS razrješuje custom funkcije kroz OnUserFunction; problem je bio što ništa na toj putanji nije pitalo je li ime samo jedno koje bi zdravi host trebao ikad izvršiti
Dispatch redoslijed bitan je za to što "nepoznato" ovdje znači. Poziv koji engine ne može izvorno evaluirati nudi se redom leksičkim LAMBDA i LET vezama, koje prve razrješuje podrška closurea u HotXLS formula engineu, zatim funkcijama lokalnim za radnu knjigu registriranim s RegisterUserFunction, onda funkcijama cijelog procesa iz TXLSWorkbook.RegisterGlobalUserFunction, i na kraju eventima OnUserFunction i OnUserFunctionEx. Tek kad svi odbiju, istinski nepoznata funkcija postaje #NAME?. Svaka faza nakon lambda pretrage predaje kontrolu kodu koji ste napisali, i baš zato sigurnosna provjera mora stajati ispred cijelog lanca, a ne unutar bilo kojeg pojedinog handlera
Koja imena funkcija HotXLS blokira po defaultu?
XLSFormulaCallbackIsUnsafe u lxCalc.pas drži fiksni deny skup od 20 imena: 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 i FILE.DELETE. To su imena koja, u Excelu ili njegovom makro jeziku, učitavaju nativni kod, izlaze na mrežu, razgovaraju s drugim procesima ili diraju datotečni sustav. Prije usporedbe funkcija odreže okolni whitespace, nametne imenu velika slova i skine jedan prefiks _XLFN. ili _XLWS., pa se _xlfn.webservice koji ga napiše novija Excel verzija hvata jednako kao golo pisanje. Popis živi na granici kalkulatora umjesto u Classic, XLSX i ODS parserima, što jednom AST-u, jednom BIFF tokenu streamu i jednoj konvertiranoj radnoj knjici ostavlja identično ponašanje
Dvije su rubne situacije vrijedne znanja prije nego se na to oslonite. Poklapanje je točno, pa handler koji registrirate kao MYWEBSERVICE nije pogođen, i obrnuto, legitimna kućna UDF koja se slučajno zove OPEN ili RUN sada je po defaultu odbijena. Deny skup također nije sandbox za Vaše vlastite handlere. Ako Vaša catch-all grana izvršava proizvoljna imena plugina, kapija zaustavi poznate opasne i ništa više; trajni popravak i dalje je handler koji se poklapa s eksplicitnim allowlistom putem SameText i ostavlja Handled na False za sve što ne posjeduje
Zašto kapija mora raditi prije evaluacije argumenata?
Kapija koja opali nakon što su argumenti izračunati kasni, jer argumenti sami mogu zvati Vaš kod. GetValueItemUserFunction najprije provjerava ime i izlazi s lxErrorUnsafeFunctionDenied prije nego izgradi polje argumenata, prije nego konzultira resolver ili bilo koji registar, i čak prije nego uoči da nijedan handler uopće nije dodijeljen. Taj redoslijed je ono što obara ugniježđeni slučaj dolje, gdje bi vanjski poziv ionako bio odbijen, ali bi bez toga bezopasno izgledajuća unutarnja UDF opalila prva i ostavila svoj side effect za sobom
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'); // side effect u host kodu
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, i FAuditLog je i dalje prazan
Default radne knjige u odnosu na TXLSFormulaEvaluationOptions po pozivu
Zastavica radne knjige je default, a opcija po pozivu zadnja je riječ. TXLSWorkbook.AllowUnsafeFormulaCallbacks i TXLSXWorkbook.AllowUnsafeFormulaCallbacks upravljaju običnim preračunavanjem, Calculate, dvargumentnim EvaluateFormulaAt, evaluacijskim predlošcima, read-only prikazima i, na XLSX, svakim workerom u paralelnom preračunskom poolu. Svaki ulaz koji prima eksplicitni zapis TXLSFormulaEvaluationOptions uzima Options.AllowUnsafeFormulaCallbacks kao presudu za taj poziv i ne spaja ga OR-om sa svojstvom radne knjige. Ta je asimetrija namjerna: pouzdani interni posao može ovlastiti jedno RTD traženje bez prevrtanja cijele radne knjige, a radna knjiga globalno uključena i dalje može osjetljivu evaluaciju vratiti na odbijanje
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// radna knjiga ostaje zaključana, jedan pouzdani poziv prolazi
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// radna knjiga uključena, ali ova evaluacija uploadanog teksta nije
Book.AllowUnsafeFormulaCallbacks := True;
Options := XLSDefaultFormulaEvaluationOptions; // zastavica je opet False
Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
if Eval.Status = xlfeUnsafeFunctionDenied then
LogRejected(Eval.Issue.Message);
end;
Prebacivanje svojstva radne knjige također označava graf ovisnosti prljavim na oba enginea. Bez tog koraka keširani rezultat izračunat dok su callbackovi bili dopušteni mogao bi se poslužiti nakon što su opozvani, ili keširani ishod xlfeUnsafeFunctionDenied mogao bi nadživjeti uključenje. Novi status dodan je na TXLSFormulaEvaluationStatus nakon xlfeFailed, pa ima redni broj 10 i svaki postojeći redni broj zadržava svoju vrijednost; isto pravilo dodavanja na rep vrijedi za polje zapisa opcija te getter i setter IXLSWorkbook, iako potrošač građen protiv starijeg izdanja i dalje treba rekompilaciju
Što se s nepoznatim i nesigurnim formulama događa pri spremanju?
Čuvati formulu i izvršiti je sada su dva odvojena pitanja, i entry politika odgovara samo na prvo. FormulaEntryPolicy na bilo kojoj klasi radne knjige nosi UnknownFunctionMode i UnknownNameMode, oba zadano xlfusmReject, pa se dodjela formule s nepoznatim pozivom kroz normalno svojstvo Formula odbija prije nego se vrijednost ćelije, formula cache ili ovisnosti promijene. ValidateFormulaEntry javlja istu odluku bez side effecta. Pouzdani putevi poput učitavanja datoteke, kopiranja i konverzije formata zaobilaze tu user-entry politiku, jer strogi default nikad ne smije odbiti simbole koji već postoje u datoteci koju Vi samo otvarate
var
Policy: TXLSFormulaEntryPolicy;
begin
Policy := Book.FormulaEntryPolicy;
Policy.UnknownFunctionMode := xlfusmPreserve; // unos radi kompatibilnosti
Book.FormulaEntryPolicy := Policy;
Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)'; // spremljeno, ne ovlašteno
Book.SaveAs('rates.xls');
end;
U Classic BIFF8 nepoznati poziv nema vlastiti token, pa ga HotXLS piše onako kako Excel piše add-in funkcije. Formula dobiva token PtgNameX ($59) čiji XTI unos pokazuje na add-in SUPBOOK s oba indeksa listova postavljenima na $FFFE, nakon kojeg slijede tokeni argumenata i PtgFuncVar s brojem funkcije 255 i brojačem argumenata koji uključuje slot imena. Podupiruće tijelo ExternName je šest nula bajtova, bajt duljine i Unicode zastavica, UTF-16 ime funkcije, zatim dvobajtna formula $1C $17, PtgErr koji drži #REF!. Pisac odbija imena dulja od 255 znakova, više od 29 argumenata i BIFF5 cilj. Kako HotXLS klasificira te add-in SUPBOOK unose uz vanjske veze radnih knjiga objašnjeno je u SUPBOOK i XTI pravilima klasifikacije za BIFF vanjske veze. XLSX čuva sirovi tekst formule, ODS svoju msoxl: formulu, i u svakom formatu datoteka koja je spremila =WEBSERVICE(...) otvara se s tekstom netaknutim i i dalje evaluira u xlfeUnsafeFunctionDenied po defaultu
Ako Vaš pipeline evaluira radne knjige koje nije sam autor, ostavite AllowUnsafeFormulaCallbacks na False, držite handlere na eksplicitnom allowlistu i dodijelite opcije po pozivu samo tamo gdje je izvor formule Vaš. Cijeli callback, entry-policy i evaluacijski API dokumentiran je uz HotXLS Delphi spreadsheet komponentu