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

  1. 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")
  2. Reload the UI: /reload
  3. Click the macro. Note that the chat output shows that the oldValue key on a control (the Sticky Targeting checkbox) is now tainted.
  4. 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>
  5. 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

  1. When the Interface Options frame is displayed, InterfaceOptionsFrame​_OnShow triggers to update the panel categories and panel states.
  2. 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.
  3. InterfaceOptionsFrame_OnShow then selects the Controls panel for display. This calls InterfaceOptionsFrame​_OpenToCategory, which triggers InterfaceOptionsListButton​_OnClick and OptionsList​_SelectButton. The latter sets the InterfaceOptionsFrame​Categories​.selection key, tainting it.
  4. On subsequent calls to InterfaceOptionsFrame​_OnShow, the execution is tainted by InterfaceCategoryList​_Update accessing the InterfaceOptionsFrame​Categories​.selection key. This causes practically all InterfaceOptionsFrame and panel code to run on a tainted execution path.
  5. 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.
  6. The nameplates error is caused by the Larger Nameplates checkbox in the Names panel:
    1. When the refresh handler for the panel is called from a tainted execution path, BlizzardOptionsPanel​_CheckButton​_Refresh taints the value key on the checkbox.
    2. When the cancel handler for the panel runs, InterfaceOptionsPanel_Cancel calls GetValue and setFunc methods on this checkbox (both assigned by InterfaceOptions​LargerNamePlate​_OnLoad). GetValue accesses the tainted value key and taints the execution, while setFunc calls NamePlateDriverFrame​:UpdateNamePlateOptions, which taints its preferredInsets key.
    3. 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

  1. Delay updating the addon category tree in InterfaceOptionsFrame​_OnShow:
    --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();
    This (hopefully) avoids tainting InterfaceOptionsFrame​Categories​.selection through this path.
  2. Consider whether the addon-related functions in InterfaceOptionsFrame​_OnShow could be securecall'ed.
  3. 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.
  4. 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.