TaintLess: InterfaceOptionsFrame taints all options panels
Accessing the Interface Options frame after an addon options panel has been added can cause all Blizzard panels to be refreshed insecurely on subsequent interactions. This can taint the panels' controls' state, and may break protected functionality upon changing options or closing the Interface Options frame.
Status: fixed by replacing InterfaceOptions with a different Settings implementation (10.0, 1.15, 3.4).
To reproduce
- Create the following macro and place it on your action bars:
Patch 8.3.0
/run f=CreateFrame("Frame") f.name="Foo" InterfaceOptions_AddCategory(f) /click GameMenuButtonUIOptions /click InterfaceOptionsFrameOkay /click GameMenuButtonUIOptions /dump issecurevariable(InterfaceOptionsControlsPanel.controls[1], "oldValue")
- Reload the UI: /reload
- Click the macro. Note that the chat output shows that the oldValue key on a control (the Sticky Targeting checkbox) is now tainted.
- While no nameplates are visible, close the options frame by pressing Escape.
The following error will occur when a nameplate next appears:
...\Blizzard_NamePlates.lua:448: Action[FrameMeasurement] failed because[Can't measure restricted regions]: attempted from: <unnamed>:GetLeft() [C]: in function `GetLeft' ...\Blizzard_NamePlates.lua:448: in function `GetPreferredInsets' ...\Blizzard_NamePlates.lua:138: in function `UpdateInsetsForType' ...\Blizzard_NamePlates.lua:122: in function `ApplyFrameOptions' ...\Blizzard_NamePlates.lua:93: in function `OnNamePlateAdded' ...\Blizzard_NamePlates.lua:51: in function <...\Blizzard_NamePlates\Blizzard_NamePlates.lua:42>
- Repeat steps 2-3. While in combat, close the options frame by pressing Escape. An Interface action failed because of an AddOn message appears in chat.
An action was blocked in combat because of taint from MACRO_TAINT - MultiBarBottomLeft:SetShown() Interface\FrameXML\MultiActionBars.lua:36 UpdateMultiActionBar() Interface\FrameXML\MultiActionBars.lua:68 MultiActionBar_Update() Interface\FrameXML\InterfaceOptionsPanels.lua:1184 setFunc() Interface\FrameXML\InterfaceOptionsPanels.lua:87 pcall() Interface\FrameXML\InterfaceOptionsFrame.lua:214 securecall() Interface\FrameXML\InterfaceOptionsFrame.lua:249 [...]
How this gets tainted
- When the Interface Options frame is displayed, InterfaceOptionsFrame_OnShow triggers to update the panel categories and panel states.
- InterfaceAddOnsList_Update populates the addon options category list. The execution is tainted by accessing tainted keys on the addon panels at this point, both via the hidden key directly in InterfaceAddOnsList_Update, and the name, parent, and hasChildren keys inside the OptionsList_DisplayButton calls used to update the category list widgets.
- InterfaceOptionsFrame_OnShow then selects the Controls panel for display. This calls InterfaceOptionsFrame_OpenToCategory, which triggers InterfaceOptionsListButton_OnClick and OptionsList_SelectButton. The latter sets the InterfaceOptionsFrameCategories.selection key, tainting it.
- On subsequent calls to InterfaceOptionsFrame_OnShow, the execution is tainted by InterfaceCategoryList_Update accessing the InterfaceOptionsFrameCategories.selection key. This causes practically all InterfaceOptionsFrame and panel code to run on a tainted execution path.
- Blizzard panels' refresh handlers snapshot settings using oldValue keys on individual controls. If these keys are tainted, the cancel handlers will not be able to restore the settings securely.
- The nameplates error is caused by the Larger Nameplates checkbox in the Names panel:
- When the refresh handler for the panel is called from a tainted execution path, BlizzardOptionsPanel_CheckButton_Refresh taints the value key on the checkbox.
- When the cancel handler for the panel runs, InterfaceOptionsPanel_Cancel calls GetValue and setFunc methods on this checkbox (both assigned by InterfaceOptionsLargerNamePlate_OnLoad). GetValue accesses the tainted value key and taints the execution, while setFunc calls NamePlateDriverFrame:UpdateNamePlateOptions, which taints its preferredInsets key.
- When a nameplate is next displayed, NamePlateDriverMixin:UpdateInsetsForType accesses the tainted preferredInsets key, and then calls NamePlateBaseMixin:GetPreferredInsets, which triggers the measurement error.
How this can be fixed
- Delay updating the addon category tree in InterfaceOptionsFrame_OnShow:
This (hopefully) avoids tainting InterfaceOptionsFrameCategories.selection through this path.
--Refresh the two category lists and display the "Controls" group of options if nothing is selected. InterfaceCategoryList_Update(); InterfaceOptionsOptionsFrame_RefreshCategories(); - InterfaceAddOnsList_Update(); if ( not InterfaceOptionsFramePanelContainer.displayedPanel ) then InterfaceOptionsFrame_OpenToCategory(CONTROLS_LABEL); end + InterfaceAddOnsList_Update();
- Consider whether the addon-related functions in InterfaceOptionsFrame_OnShow could be securecall'ed.
- Move the setFunc check/call from InterfaceOptionsPanel_Cancel inside
InterfaceOptionsPanel_CancelControl (a local helper function only called here).
The already-present securecall boundary would prevent a single insecure control from tainting the cancellation flow for the rest of the panel. - Do not proceed with NamePlateDriverFrame:UpdateNamePlateOptions unless the current execution path is secure.
It would be better to abort or delay the update and perhaps complain in chat (e.g. "some settings will be applied when you next log in").
AddOn workaround
A workaround for this issue is included in TaintLess.