HotXLS odbija da usmeri 20 opasnih imena formula, među kojima su CALL, REGISTER.ID, WEBSERVICE i DDE, ka Vašim Delphi user-function callback-ovima osim ako se Vi ne uključite. Svojstvo workbook-a AllowUnsafeFormulaCallbacks podrazumeva False, provera se vrši pre nego što bilo koji argument bude evaluiran, i odbijen poziv prijavljuje xlfeUnsafeFunctionDenied bez pozivanja ijednog handler-a
Scenario koji je ovo učinio neophodnim je svakidašnji. Servis prihvata upload-ovane XLS ili XLSX fajlove, preračunava ih na serveru i čita par ukupnosti nazad. Host aplikacija je registrovala OnUserFunction handler pre par godina za par poslovnih funkcija, i negde usput taj je handler izrastao u catch-all granu koja sve što ne prepozna prosleđuje tabeli plugina. Niko u timu nikada nije kucao =WEBSERVICE(...) u ćeliju. Onaj ko je otpremio fajl jeste. Zadržati tu formulu netaknutom kroz otvaranje, preračun i čuvanje je fičer vernosti fajla. Pustiti je do host koda koji može da otvori sokete ili fajlove je odluka o autorizaciji, i dok HotXLS nije odvojio jedno od drugog, biblioteka je tu odluku tiho donosila u Vaše ime
Zašto je čuvanje formule postalo dozvola da se izvrši?
Koreni uzrok bila je jedna fallback putanja. HotXLS parsira svako ime Excel funkcije koje zna, ali ne svako poznato ime ima implementaciju u motoru računanja. Ugrađene funkcije koje su prepoznate a neimplementirane padale su u isti user-defined function fallback kao zaista custom imena, pa su CALL i REGISTER.ID delile dispatch putanju sa Vašim DISCOUNT ili REGIONRATE. Nepoznata imena poput WEBSERVICE ili DDE mogla su isto tako upasti na istoimeno ime u registru workbook-a, registru celog procesa ili event handler-u. Mehanika tog fallback-a pokrivena je u kako HotXLS razrešava custom funkcije kroz OnUserFunction; problem je bio što se ništa na toj putanji nije pitalo da li je samo ime nešto što razumni host treba ikada da izvrši
Redosled dispatch-a bitan je za ono što „nepoznato“ ovde znači. Poziv koji motor ne ume nativno da evaluira nudi se redom leksičkim LAMBDA i LET vezama, koje podrška za closures u HotXLS formula motoru razrešava prve, pa funkcijama lokalnim za workbook registrovanim sa RegisterUserFunction, pa funkcijama celog procesa iz TXLSWorkbook.RegisterGlobalUserFunction, i napokon event-ima OnUserFunction i OnUserFunctionEx. Tek kad svi odbiju, zaista nepoznata funkcija postaje #NAME?. Svaka faza posle lambda pretrage predaje kontrolu kodu koji ste Vi napisali, i baš zato sigurnosna provera mora da sedi ispred celog lanca, a ne unutar bilo kog pojedinačnog handler-a
Koja imena funkcija HotXLS blokira po podrazumevanju?
XLSFormulaCallbackIsUnsafe u lxCalc.pas drži fiksni skup od 20 zabranjenih 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 sa drugim procesima ili diraju fajl sistem. Pre poređenja funkcija odseče okolne praznine, pretvori ime u velika slova i skine jedan prefiks _XLFN. ili _XLWS., pa je _xlfn.webservice napisan novijim Excel build-om uhvaćen isto kao gola varijanta. Lista živi na granici kalkulatora, a ne u Classic, XLSX i ODS parserima, što drži jedan AST, jedan BIFF token stream i jedan konvertovani workbook ponašajućim se identično
Dve ivice vrede znanja pre nego što se na to oslonite. Poklapanje je tačno, pa handler kojeg registrujete kao MYWEBSERVICE nije pogođen, i obrnuto, legitimna in-house UDF koja se slučajno zove OPEN ili RUN sada se odbija po podrazumevanju. Skup zabrana takođe nije sandbox za Vaše sopstvene handler-e. Ako Vaša catch-all grana izvršava proizvoljna imena plugina, kapija zaustavi poznate opasne i ništa drugo; trajna popravka je i dalje handler koji upoređuje sa eksplicitnom allowlist-om preko SameText i ostavlja Handled na False za sve što nije njegovo
Zašto kapija mora da radi pre evaluacije argumenata?
Kapija koja opali posle računanja argumenata je prekasno, jer sami argumenti mogu da pozovu Vaš kod. GetValueItemUserFunction prvo proveri ime i izađe sa lxErrorUnsafeFunctionDenied pre nego što izgradi niz argumenata, pre nego što konsultuje bilo koji resolver ili koji od registara, i čak pre nego što primeti da nijedan handler uopšte nije dodeljen. Taj redosled je ono što savlađuje ugnježdeni slučaj ispod, gde bi spoljašnji poziv ionako bio odbijen, ali bi bez toga neškodljivo izgledajuća unutrašnja 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, a FAuditLog je i dalje prazan
Podrazumevano u workbook-u naspram po pozivu TXLSFormulaEvaluationOptions
Zastavica workbook-a je podrazumevana, a opcija po pozivu je poslednja reč. TXLSWorkbook.AllowUnsafeFormulaCallbacks i TXLSXWorkbook.AllowUnsafeFormulaCallbacks upravljaju običnim preračunavanjem, Calculate, dvargumentnim EvaluateFormulaAt, šablonima evaluacije, read-only prikazima i, na XLSX, svakim radnikom u paralelnom bazenu preračunavanja. Svaki ulazni punkt koji prima eksplicitni record TXLSFormulaEvaluationOptions uzima Options.AllowUnsafeFormulaCallbacks kao presudu za taj poziv i ne kombinuje ga OR operacijom sa svojstvom workbook-a. Ta asimetrija je namerna: poverljivi interni posao može da autorizuje jedan RTD upit bez prevrtanja celog workbook-a, i workbook koji je globalno uključen i dalje može da tera osetljivu evaluaciju nazad na odbijanje
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// workbook ostaje zaključan, jedan poverljivi poziv se propušta
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// workbook je uključen, ali ova evaluacija otpremljenog 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 workbook-a takođe označava graf zavisnosti neažurnim na oba engine-a. Bez tog koraka keširani rezultat izračunat dok su callback-ovi bili dozvoljeni mogao bi se poslužiti posle nego što su povučeni, ili keširani ishod xlfeUnsafeFunctionDenied mogao bi nadživeti uključenje. Novi status je nadovezan na TXLSFormulaEvaluationStatus posle xlfeFailed, pa ima ordinal 10 i svaki postojeći ordinal čuva svoju vrednost; isto pravilo nadovezivanja na rep važi za polje options record-a i getter i setter IXLSWorkbook-a, mada potrošač građen protiv starijeg izdanja i dalje treba rekompilaciju
Šta se sa nepoznatim i nesigurnim tekstom formule dešava pri čuvanju?
Čuvanje formule i njeno izvršavanje sada su dva odvojena pitanja, i politika unosa odgovara samo na prvo. FormulaEntryPolicy na bilo kojoj klasi workbook-a nosi UnknownFunctionMode i UnknownNameMode, oba podrazumevano xlfusmReject, pa se dodela formule sa nepoznatim pozivom kroz obično svojstvo Formula odbija pre nego što se vrednost ćelije, keš formule ili zavisnosti promene. ValidateFormulaEntry prijavljuje istu odluku bez side efekata. Poverljive putanje poput učitavanja fajla, kopiranja i konverzije formata zaobilaze tu politiku korisničkog unosa, jer strog podrazumevani režim nikada ne sme da odbije simbole koji već postoje u fajlu koji 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)'; // sačuvano, ne autorizovano
Book.SaveAs('rates.xls');
end;
U Classic BIFF8 nepoznati poziv nema svoj sopstveni token, pa ga HotXLS zapisuje na način na koji Excel zapisuje add-in funkcije. Formula dobija token PtgNameX ($59) čiji XTI unos pokazuje na add-in SUPBOOK sa oba indeksa lista postavljenim na $FFFE, iza kojeg idu argument tokeni i PtgFuncVar koji nosi broj funkcije 255 i broj argumenata koji uključuje slot imena. Prateće telo ExternName-a je šest nula bajtova, bajt dužine i Unicode zastavica, UTF-16 ime funkcije, pa dvobajtna formula $1C $17, PtgErr koji drži #REF!. Pisac odbija imena duža od 255 znakova, više od 29 argumenata i BIFF5 cilj. Kako HotXLS klasifikuje ove add-in SUPBOOK unose pored linkova ka spoljnim workbook-ovima objašnjeno je u SUPBOOK i XTI pravilima klasifikacije za BIFF spoljne linkove. XLSX čuva sirovi tekst funkcije, ODS čuva svoju msoxl: formulu, i u svakom formatu fajl koji je sačuvao =WEBSERVICE(...) se otvara sa tekstom netaknutim i i dalje evaluira u xlfeUnsafeFunctionDenied po podrazumevanju
Ako Vaš pipeline evaluira workbook-ove koje nije sam autor, ostavite AllowUnsafeFormulaCallbacks na False, držite handler-e na eksplicitnoj allowlist-i, i dodeljujte opcije po pozivu samo tamo gde je izvor formule Vaš. Ceo callback, entry-policy i evaluacioni API dokumentovan je uz HotXLS Delphi spreadsheet komponentu