Skip to content

Commit a3128d0

Browse files
committed
#1979 Fix GSE Options related taint
1 parent 35bc331 commit a3128d0

1 file changed

Lines changed: 60 additions & 68 deletions

File tree

GSE_Options/Options.lua

Lines changed: 60 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ local MIN_DEBUGGER_HEIGHT = 500
4646
local MAX_DEBUGGER_HEIGHT = 2000
4747
local MIN_DEBUGGER_WIDTH = 700
4848
local MAX_DEBUGGER_WIDTH = 3000
49-
local settingsButtonFixInstalled = false
5049
local settingsLabelButtonFixInstalled = false
5150
local settingsModernColorRowFixInstalled = false
5251
local settingsExclusiveRowFixInstalled = false
@@ -169,53 +168,36 @@ local MODERN_CLASS_FILE_BY_ID = {
169168
-- SECTION 2 -- Blizzard Settings panel patching / shim layer
170169
--
171170
-- The Blizzard Settings panel has a few rough edges that GSE works
172-
-- around: a missing "Open this panel from chat" button on Classic /
173-
-- BoA / MoP (InstallSettingsButtonFix), the lack of native exclusive
174-
-- (radio-style) checkbox groups (RegisterExclusiveOptionRow /
175-
-- InstallSettingsExclusiveRowFix), and the absence of a label+button
176-
-- row template (InstallSettingsLabelButtonFix). All three are
177-
-- installed once at GSE init and provide the building blocks for the
178-
-- option panels further down.
171+
-- around: the lack of native exclusive (radio-style) checkbox groups
172+
-- (RegisterExclusiveOptionRow / InstallSettingsExclusiveRowFix), and
173+
-- the absence of a label+button row template
174+
-- (InstallSettingsLabelButtonFix). Both are installed once at GSE init
175+
-- and provide the building blocks for the option panels further down.
176+
--
177+
-- TAINT RULE FOR EVERYTHING IN THIS SECTION
178+
--
179+
-- Never assign over a Blizzard Settings mixin method
180+
-- (`SomeControlMixin.Init = function(...) end`). Doing so makes *every*
181+
-- control of that type -- Blizzard's own included -- execute GSE code,
182+
-- so the entire Settings panel initialises under GSE_Options taint. As
183+
-- of WoW 12.1 that surfaced as:
184+
--
185+
-- * ADDON_ACTION_FORBIDDEN -- AddOn 'GSE_Options' tried to call the
186+
-- protected function 'IsUserOAuthed()' from
187+
-- Blizzard_SettingsDefinitions_Frame/Social.lua, because our
188+
-- replacement Init ran for Blizzard's Social checkboxes.
189+
-- * "attempt to compare a secret number value (execution tainted by
190+
-- 'GSE_Options')" from Blizzard_TextStatusBar / CompactUnitFrame on
191+
-- nameplates, once the taint reached CVar-backed settings values.
192+
-- 12.x returns unit health as secret values, which tainted code
193+
-- cannot compare.
194+
--
195+
-- Use hooksecurefunc() instead: Blizzard's Init still runs securely and
196+
-- our post-hook's taint does not propagate back to the caller. A
197+
-- post-hook must also bail out early on any row that isn't ours, so we
198+
-- never write to Blizzard's controls.
179199
-- =========================================================================
180200

181-
local function InstallSettingsButtonFix()
182-
if settingsButtonFixInstalled or not SettingsButtonControlMixin or not SettingsButtonControlMixin.Init then return end
183-
184-
local originalInit = SettingsButtonControlMixin.Init
185-
SettingsButtonControlMixin.Init = function(self, initializer)
186-
local data = initializer and initializer.GetData and initializer:GetData()
187-
if data and data.gseSettingsButton and self.Button and self.Button.ClearAllPoints then
188-
self.Button:ClearAllPoints()
189-
end
190-
191-
local result = originalInit(self, initializer)
192-
193-
if data and data.gseSettingsButton and self.Button then
194-
self.Button:ClearAllPoints()
195-
if data.name == "" then
196-
self.Button:SetPoint("LEFT", self.Text, "LEFT", 0, 0)
197-
if self.Tooltip then self.Tooltip:Hide() end
198-
else
199-
self.Button:SetPoint("LEFT", self, "CENTER", -40, 0)
200-
if self.Tooltip then self.Tooltip:Show() end
201-
end
202-
self.Button:SetWidth(SETTINGS_BUTTON_WIDTH)
203-
self.Button:SetHeight(SETTINGS_BUTTON_HEIGHT)
204-
self.Button:SetText(data.buttonText)
205-
self.Button:SetScript("OnClick", function(button, ...)
206-
if data.buttonClick then
207-
data.buttonClick(button, ...)
208-
end
209-
end)
210-
self.Button:Enable()
211-
self.Button:Show()
212-
end
213-
214-
return result
215-
end
216-
settingsButtonFixInstalled = true
217-
end
218-
219201
local function RunAfterOptionsUpdate(func)
220202
C_Timer.After(0, func)
221203
end
@@ -283,17 +265,16 @@ end
283265
local function InstallSettingsExclusiveRowFix()
284266
if settingsExclusiveRowFixInstalled or not SettingsCheckboxControlMixin or not SettingsCheckboxControlMixin.Init then return end
285267

286-
local originalInit = SettingsCheckboxControlMixin.Init
287-
SettingsCheckboxControlMixin.Init = function(self, initializer)
268+
-- Post-hook only -- see the taint rule at the top of SECTION 2.
269+
hooksecurefunc(SettingsCheckboxControlMixin, "Init", function(self, initializer)
288270
local data = initializer and initializer.GetData and initializer:GetData()
289-
if data then
290-
data.name = SafeOptionText(data.name)
291-
data.tooltip = SafeOptionText(data.tooltip)
292-
end
293-
local result = originalInit(self, initializer)
271+
-- Act on our own exclusive rows, plus any row the settings frame pool
272+
-- has recycled away from us (it still carries our fields, and
273+
-- RegisterExclusiveOptionRow clears them). Blizzard's own rows are
274+
-- never touched.
275+
if not ((data and data.gseExclusiveOptionGroup) or self.GSEExclusiveOptionGroup) then return end
294276
RegisterExclusiveOptionRow(self, data)
295-
return result
296-
end
277+
end)
297278

298279
settingsExclusiveRowFixInstalled = true
299280
end
@@ -302,6 +283,10 @@ local function MarkExclusiveCheckboxInitializer(initializer, group, getValue)
302283
InstallSettingsExclusiveRowFix()
303284
local data = initializer and initializer.GetData and initializer:GetData()
304285
if data then
286+
-- Previously done inside the replacement mixin Init, which applied it
287+
-- to every checkbox row in the game. Only our own rows ever needed it.
288+
data.name = SafeOptionText(data.name)
289+
data.tooltip = SafeOptionText(data.tooltip)
305290
data.gseExclusiveOptionGroup = group
306291
data.gseExclusiveGetValue = getValue
307292
end
@@ -324,7 +309,6 @@ local function InstallSettingsLabelButtonFix()
324309
if settingsLabelButtonFixInstalled or not SettingsCheckboxWithButtonControlMixin or
325310
not SettingsCheckboxWithButtonControlMixin.Init then return end
326311

327-
local originalInit = SettingsCheckboxWithButtonControlMixin.Init
328312
local function HideCheckboxFrame(row, frame)
329313
if not frame or frame == row or frame == row.GSELabelButton then return end
330314
if frame.Hide then frame:Hide() end
@@ -334,11 +318,15 @@ local function InstallSettingsLabelButtonFix()
334318
if frame.Text and frame.Text.Hide then frame.Text:Hide() end
335319
end
336320

337-
SettingsCheckboxWithButtonControlMixin.Init = function(self, initializer)
321+
-- Post-hook only -- see the taint rule at the top of SECTION 2.
322+
hooksecurefunc(SettingsCheckboxWithButtonControlMixin, "Init", function(self, initializer)
338323
local data = initializer and initializer.GetData and initializer:GetData()
339-
local result = originalInit(self, initializer)
340324

341325
if not (data and data.gseLabelButton) then
326+
-- Only restore rows the settings frame pool has recycled away from
327+
-- us. A row we have never dressed up belongs to Blizzard, so we
328+
-- leave it entirely alone.
329+
if not (self.GSELabelText or self.GSELabelButton) then return end
342330
if self.GSELabelText then self.GSELabelText:Hide() end
343331
if self.GSELabelButton then
344332
self.GSELabelButton:Hide()
@@ -362,7 +350,7 @@ local function InstallSettingsLabelButtonFix()
362350
self.Text:SetText(SafeOptionText(data and data.name))
363351
self.Text:Show()
364352
end
365-
return result
353+
return
366354
end
367355

368356
HideCheckboxFrame(self, self.Checkbox or self.CheckBox or self.CheckButton or self.Check)
@@ -427,9 +415,7 @@ local function InstallSettingsLabelButtonFix()
427415
self.GSELabelButton:Show()
428416
SkinGSESettingsButton(self.GSELabelButton)
429417
RunAfterOptionsUpdate(function() SkinGSESettingsButton(self.GSELabelButton) end)
430-
431-
return result
432-
end
418+
end)
433419
settingsLabelButtonFixInstalled = true
434420
end
435421

@@ -452,6 +438,10 @@ local function CreateGSESettingsLabelButtonInitializer(category, settingID, labe
452438

453439
local data = initializer and initializer.GetData and initializer:GetData()
454440
if data then
441+
-- Sanitise here rather than in the Init hook: Blizzard's Init now runs
442+
-- first and reads these, so they have to be strings before it does.
443+
data.name = SafeOptionText(data.name)
444+
data.tooltip = SafeOptionText(data.tooltip)
455445
data.gseLabelButton = true
456446
data.gseLabelText = label
457447
data.gseButtonText = buttonText
@@ -1006,18 +996,21 @@ local function InstallSettingsModernColorRowFix()
1006996
if settingsModernColorRowFixInstalled or not SettingsCheckboxWithButtonControlMixin or
1007997
not SettingsCheckboxWithButtonControlMixin.Init then return end
1008998

1009-
local originalInit = SettingsCheckboxWithButtonControlMixin.Init
1010-
SettingsCheckboxWithButtonControlMixin.Init = function(self, initializer)
999+
-- Post-hook only -- see the taint rule at the top of SECTION 2.
1000+
hooksecurefunc(SettingsCheckboxWithButtonControlMixin, "Init", function(self, initializer)
10111001
local data = initializer and initializer.GetData and initializer:GetData()
1012-
local result = originalInit(self, initializer)
10131002

10141003
if not (data and data.gseModernCustomColorRow) then
1004+
-- Only tidy up rows the settings frame pool has recycled away from
1005+
-- us; rows we have never dressed up are Blizzard's, leave them be.
1006+
if not (self.GSEModernColorData or self.GSEModernColorSwatch
1007+
or self.GSEModernCustomCheck or self.GSEModernCustomLabel) then return end
10151008
self.GSEModernColorData = nil
10161009
self.GSEModernRefreshColorRow = nil
10171010
if self.GSEModernCustomCheck then self.GSEModernCustomCheck:Hide() end
10181011
if self.GSEModernCustomLabel then self.GSEModernCustomLabel:Hide() end
10191012
if self.GSEModernColorSwatch then self.GSEModernColorSwatch:Hide() end
1020-
return result
1013+
return
10211014
end
10221015

10231016
RegisterExclusiveOptionRow(self, data)
@@ -1069,8 +1062,7 @@ local function InstallSettingsModernColorRowFix()
10691062
end
10701063

10711064
self:GSEModernRefreshColorRow()
1072-
return result
1073-
end
1065+
end)
10741066
settingsModernColorRowFixInstalled = true
10751067
end
10761068

0 commit comments

Comments
 (0)