@@ -46,7 +46,6 @@ local MIN_DEBUGGER_HEIGHT = 500
4646local MAX_DEBUGGER_HEIGHT = 2000
4747local MIN_DEBUGGER_WIDTH = 700
4848local MAX_DEBUGGER_WIDTH = 3000
49- local settingsButtonFixInstalled = false
5049local settingsLabelButtonFixInstalled = false
5150local settingsModernColorRowFixInstalled = false
5251local 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-
219201local function RunAfterOptionsUpdate (func )
220202 C_Timer .After (0 , func )
221203end
@@ -283,17 +265,16 @@ end
283265local 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
299280end
@@ -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
434420end
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
10751067end
10761068
0 commit comments