HotXLS atsisako nukreipti į jūsų Delphi vartotojo funkcijų callback 20 pavojingų formulių vardų, įskaitant CALL, REGISTER.ID, WEBSERVICE ir DDE, nebent to pats pageisite. Darbaknygės savybė AllowUnsafeFormulaCallbacks pagal nutylėjimą False, patikra paleidžiama dar prieš vertinant bent vieną argumentą, o atmestas kvietimas praneša xlfeUnsafeFunctionDenied nepaleidęs nė vieno handlerio
Scenarijus, dėl kurio to prireikė, visai kasdieniškas. Servisas priima įkeltus XLS arba XLSX failus, perskaičiuoja juos serveryje ir perskaito kelias sumas atgal. Šeiminė programa prieš kelerius metus užregistravo OnUserFunction handlerį kelioms verslo funkcijoms, ir kažkur pakelyje tas handleris užaugo catch-all šaka, kuri viską, ko neatpažįsta, persiunčia į papildinių lentelę. Nė vienas komandos narys niekada nėra įvedęs =WEBSERVICE(...) į ląstelę. Įkeliantysis įvedė. Išlaikyti tą formulę nepaliestą per atidarymą, perskaičiavimą ir įrašymą yra failo ištikimybės funkcija. Leisti jai pasiekti šeiminį kodą, galintį atidaryti soketus ar failus, yra autorizacijos sprendimas, ir kol HotXLS abu dalykus atskyrė, biblioteka tą sprendimą tyliai priimdavo už jus
Kodėl formulės išsaugojimas virto leidimu ją paleisti?
Šakninė priežastis buvo vienas atsarginis kelias. HotXLS išanalizuoja kiekvieną jam žinomą Excel funkcijos vardą, bet ne kiekvienas žinomas vardas turi implementaciją skaičiavimo variklyje. Įtaisytieji, atpažįstami, bet neįgyvendinti vardai anksčiau nukrisdavo į tą patį vartotojo funkcijų atsarginį kelią kaip tikrai savos vardos, tad CALL ir REGISTER.ID dalindavosi dispečio keliu su jūsų DISCOUNT ar REGIONRATE. Nežinomi vardai, tokie kaip WEBSERVICE ar DDE, panašiai galėjo sutapti su to paties vardo įrašu darbaknygės registre, viso proceso registre arba įvykių handler'yje. To atsarginio kelio mechanika aprašyta kaip HotXLS išsprendžia savas funkcijas per OnUserFunction; problema buvo tai, kad niekas tame kelyje nepaklausdavo, ar pats vardas yra toks, kurį sveikas hostas apskritai turėtų leisti
Dispečio tvarka svarbi tam, ką čia reiškia „nežinomas“. Kvietimas, kurio variklis negali įvertinti savomis jėgomis, paeiliui siūlomas leksinėms LAMBDA ir LET priklausomybėms, kurias pirmiausiai išsprendžia uždaročių palaikymas HotXLS formulių variklyje, tada darbaknygės lokaliosioms funkcijoms, užregistruotoms per RegisterUserFunction, tada viso proceso funkcijoms iš TXLSWorkbook.RegisterGlobalUserFunction ir galiausiai OnUserFunction ir OnUserFunctionEx įvykiams. Tik kai visi jie atsisako, tikrai nežinoma funkcija tampa #NAME?. Kiekvienas etapas po lambda paieškos perduoda valdymą jūsų pačių kodui — būtent todėl saugos patikra turi stovėti prieš visą grandinę, o ne kurio nors vieno handlerio viduje
Kuriuos funkcijų vardus HotXLS blokuoja pagal nutylėjimą?
XLSFormulaCallbackIsUnsafe faile lxCalc.pas laiko fiksuotą 20 vardų atmetimo aibę: 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 ir FILE.DELETE. Tai vardai, kurie Excel'yje ar jo makrokalboje įkelia savąjį kodą, pasiekia tinklą, kalbasi su kitais procesais ar paliečia failų sistemą. Prieš palyginimą funkcija nukerpa aplinkinius tarpus, vardą padaro didžiosiomis raidėmis ir nulupa vieną _XLFN. arba _XLWS. priešdėlį, tad naujesnės Excel versijos parašytą _xlfn.webservice sugauna taip pat, kaip ir pliką rašybą. Sąrašas gyvena skaičiuoklės riboje, o ne Classic, XLSX ir ODS analizatoriuose, todėl vienas AST, vienas BIFF tokenų srautas ir viena konvertuota darbaknygė elgiasi identiškai
Dvi smulkmenos vertos žinoti, prieš jumis pasitikint tuo. Sutapimas tikslus, tad jūsų užregistruotas MYWEBSERVICE handlerio nepaliečia, ir atvirkščiai — teisėta vidinė UDF, atsitiktinai vadinama OPEN ar RUN, dabar pagal nutylėjimą atmetama. Atmetimo aibė taip pat nėra jūsų pačių handlerių smėlio dėžė. Jei jūsų catch-all šaka vykdo savavališkus papildinių vardus, vartai sustabdo garsiuosius pavojinguosius ir nieko daugiau; patvari pataisa vis dar yra handleris, kuris SameText lygina su aiškiu leidžiamųjų vardų sąrašu ir viskam, ko nepriklauso, palieka Handled False
Kodėl vartai turi veikti prieš argumentų vertinimą?
Vartai, užsidegantys po to, kai argumentai jau apskaičiuoti, pavėluoti, nes patys argumentai gali iškviesti jūsų kodą. GetValueItemUserFunction vardą patikrina pirmiausia ir išeina su lxErrorUnsafeFunctionDenied dar nesukūręs argumentų masyvo, dar nepasiklausęs nė valdytojo, nė kurio registro, ir net dar nepastebėjęs, kad iš viso nepriskirtas joks handleris. Ta tvarka ir nugalimas žemiau esantis įdėtasis atvejis, kai išorinis kvietimas būtų atsisakytas bet kaip, bet nepastebimai atrodanti vidinė UDF kitu atveju suveiktų pirma ir paliktų savo šalutinį poveikį
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'); // šalutinis poveikis šeiminiame kode
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, o FAuditLog vis dar tuščias
Darbaknygės numatytoji reikšmė prieš per kvietimą skirtos TXLSFormulaEvaluationOptions
Darbaknygės žymė yra numatytoji, o per kvietimą skirta parinktis — paskutinis žodis. TXLSWorkbook.AllowUnsafeFormulaCallbacks ir TXLSXWorkbook.AllowUnsafeFormulaCallbacks valdo įprastą perskaičiavimą, Calculate, dviejų argumentų EvaluateFormulaAt, vertinimo šablonus, tik skaitomus rodinius ir, XLSX atveju, kiekvieną darbuotoją lygiagretaus perskaičiavimo base. Bet koks įėjimo taškas, priimantis aiškų TXLSFormulaEvaluationOptions įrašą, tam kvietimui paskelbia verdiktą Options.AllowUnsafeFormulaCallbacks ir jo nesumaišo su darbaknygės savybe OR operacija. Ta asimetrija sąmoninga: patikėta vidinė užduotis gali suteikti vienai RTD paieškai leidimą neversdama visos darbaknygės, o globaliai įjungta darbaknygė vis tiek gali jautrų vertinimą grąžinti prie atmetimo
var
Options: TXLSFormulaEvaluationOptions;
Eval: TXLSFormulaEvaluationResult;
begin
// darbaknygė lieka užrakinta, vienas patikėtas kvietimas praleidžiamas
Book.AllowUnsafeFormulaCallbacks := False;
Options := XLSDefaultFormulaEvaluationOptions;
Options.AllowUnsafeFormulaCallbacks := True;
Eval := Sheet.EvaluateFormulaAt(4, 2, '=WEBSERVICE(B1)', xlfrsA1, Options);
// darbaknygė įjungta, bet šis įkeltos tekstyno vertinimas — ne
Book.AllowUnsafeFormulaCallbacks := True;
Options := XLSDefaultFormulaEvaluationOptions; // žymė vėl False
Eval := Sheet.EvaluateFormulaAt(4, 2, UploadedFormula, xlfrsA1, Options);
if Eval.Status = xlfeUnsafeFunctionDenied then
LogRejected(Eval.Issue.Message);
end;
Darbaknygės savybės perjungimas abiejuose varikliuose ir priklausomybių grafą pažymi nešvariu. Be to žingsnio podėlyje sukaupta reikšmė, apskaičiuota, kol callback buvo leisti, galėtų būti patiekiama juos atšaukus, arba podėlyje sukautas xlfeUnsafeFunctionDenied rezultatas galėtų išgyventi įjungimą. Naujasis statusas prie TXLSFormulaEvaluationStatus pridėtas po xlfeFailed, tad jo ordinalinis numeris 10 ir kiekviena esama reikšmė išlaiko savo vertę; ta pati galo pridėjimo taisyklė galioja parinkčių įrašo laukui ir IXLSWorkbook getteriui bei setteriui, nors vartotojas, sukurtas prie senesnės laidos, vis tiek reikalauja perkompiliavimo
Kas nutinka nežinomam ir nesaugiam formulės tekstui įrašant?
Formulės išlaikymas ir jos paleidimas dabar du atskiri klausimai, o įvedimo politika atsako tik į pirmąjį. FormulaEntryPolicy abiejose darbaknygių klasėse neša UnknownFunctionMode ir UnknownNameMode, abu pagal nutylėjimą xlfusmReject, tad formulės su nežinomu kvietimu priskyrimas per įprastą Formula savybę atmetamas, dar nepakeitus nei ląstelės reikšmės, nei formulių podėlio, nei priklausomybių. ValidateFormulaEntry tą patį sprendimą praneša be šalutinio poveikio. Patikėti keliai, tokie kaip failo įkėlimas, kopijavimas ir formato konversija, aplenkia tą vartotojo įvedimo politiką, nes griežtas numatytasis niekada neturi atmesti simbolių, jau esančių faile, kurį jūs tiesiog atidarote
var
Policy: TXLSFormulaEntryPolicy;
begin
Policy := Book.FormulaEntryPolicy;
Policy.UnknownFunctionMode := xlfusmPreserve; // suderinamumo įvedimas
Book.FormulaEntryPolicy := Policy;
Sheet.Cells[3, 1].Formula := '=ACME_RATE(B3)'; // išsaugota, ne autorizuota
Book.SaveAs('rates.xls');
end;
Klasikiniame BIFF8 nežinomas kvietimas neturi savo tokeno, tad HotXLS jį rašo taip, kaip Excel rašo papildinių funkcijas. Formulė gauna PtgNameX tokeną ($59), kurio XTI įrašas rodo į papildinio SUPBOOK su abiem lapų indeksais, nustatytais į $FFFE, po jo eina argumentų tokenai ir PtgFuncVar su funkcijos numeriu 255 ir argumentų skaičiumi, įskaitančiu vardo vietą. Už jo stovintis ExternName kūnas yra šeši nuliniai baitai, ilgio baitas ir Unicode žymė, UTF-16 funkcijos vardas, tada dviejų baitų formulė $1C $17 — PtgErr, laikantis #REF!. Rašytojas atsisako vardų ilgesnių nei 255 simboliai, daugiau nei 29 argumentų ir BIFF5 tikslo. Kaip HotXLS šiuos papildinių SUPBOOK įrašus klasifikuoja šalia išorinių darbaknygių nuorodų, aiškinta SUPBOOK ir XTI klasifikavimo taisyklėse BIFF išorinėms nuorodoms. XLSX išlaiko žalią funkcijos tekstą, ODS — savą msoxl: formulę, ir kiekviename formate failas, išsaugojęs =WEBSERVICE(...), vėl atsidaro su tekstu nepaliestu ir pagal nutylėjimą vis tiek vertinamas į xlfeUnsafeFunctionDenied
Jei jūsų srautas vertina darbaknyges, kurių pats nesudėliojo, palikite AllowUnsafeFormulaCallbacks False, laikykite handlerius aiškiame leidžiamųjų vardų sąraše, o per kvietimą skirtas parinktis suteikite tik ten, kur formulės šaltinis jūsų. Pilnas callback, įvedimo politikos ir vertinimo API dokumentuotas prie HotXLS Delphi spreadsheet komponento