CVE-2024-27851

Overview

Advisory: Apple Security Advisory

Impact:

Description: Processing maliciously crafted web content may lead to arbitrary code execution

Researchers: Nan Wang (@eternalsakura13) of 360 Vulnerability Research Institute

Attribute Value
CVE CVE-2024-27851
Bugzilla 272106
Component WebCore
Bug Class OOB
Severity critical
Commit f93441ff7ade8faf…
Advisory Apple Advisory

Root Cause Analysis

The ControlStates class wrapped an OptionSet<States> with a uint16_t enum and was passed by reference through the theme rendering pipeline. Platform-specific RenderTheme implementations used the internal state bitmask to index into style lookup tables without proper bounds validation. When rendering form controls (checkboxes, inputs, options) with certain state combinations, the computed index could exceed the lookup table bounds, causing an out-of-bounds read. The fix deletes the ControlStates wrapper entirely and replaces it with OptionSet<ControlStyle::State>, which is passed by value and eliminates the unsafe reference-based access pattern.

Attack Path

1. Craft a page with a form control

Attacker serves a web page containing a checkbox, radio button, or select option element that uses native platform styling (hasEffectiveAppearance).

2. Manipulate element state

Attacker toggles the element’s state (checked, hover, disabled, indeterminate) via JavaScript or CSS pseudo-classes, triggering style recalculation and theme rendering.

3. Trigger theme paint path

The renderer calls RenderTheme::paint() or stateChanged(), passing the element’s ControlStates reference into the platform theme implementation (macOS or Adwaita).

4. OOB index computation

The theme implementation derives an array index from the ControlStates bitmask. With certain state combinations, the computed index exceeds the platform theme style lookup table bounds.

5. Arbitrary code execution

The out-of-bounds read accesses attacker-influenced heap memory, leaking pointers or executing arbitrary code via corrupted vtables or subsequent heap spraying.

Changed Functions

Function File Change Note
ControlStates (class) Source/WebCore/platform/ControlStates.h deleted Removed entire class; replaced with OptionSetControlStyle::State to eliminate unsafe reference-based state passing.
Theme::paint Source/WebCore/platform/Theme.h modified Changed signature from ControlStates& to OptionSetControlStyle::State, preventing unsafe reference access.
Theme::stateChanged Source/WebCore/platform/Theme.h modified Changed signature from ControlStates& to OptionSetControlStyle::State.
Element::setActive Source/WebCore/dom/Element.cpp modified Updated stateChanged call from ControlStates::States::Pressed to ControlStyle::State::Pressed.
Element::setHovered Source/WebCore/dom/Element.cpp modified Updated stateChanged call from ControlStates::States::Hovered to ControlStyle::State::Hovered.
HTMLFormControlElement::disabledStateChanged Source/WebCore/html/HTMLFormControlElement.cpp modified Updated stateChanged call from ControlStates::States::Enabled to ControlStyle::State::Enabled.
HTMLInputElement::setChecked Source/WebCore/html/HTMLInputElement.cpp modified Updated stateChanged call from ControlStates::States::Checked to ControlStyle::State::Checked.
HTMLInputElement::setIndeterminate Source/WebCore/html/HTMLInputElement.cpp modified Updated stateChanged call from ControlStates::States::Checked to ControlStyle::State::Checked.
HTMLOptionElement::attributeChanged Source/WebCore/html/HTMLOptionElement.cpp modified Updated stateChanged call from ControlStates::States::Enabled to ControlStyle::State::Enabled.
RenderTheme::paint / stateChanged Source/WebCore/rendering/RenderTheme.cpp modified Updated to use OptionSetControlStyle::State for state-based style lookups.
RenderThemeMac Source/WebCore/rendering/RenderThemeMac.mm modified Platform theme implementation updated to use new state type.
RenderThemeAdwaita Source/WebCore/rendering/RenderThemeAdwaita.cpp modified Platform theme implementation updated to use new state type.

Files Changed

Source Files

  • Source/WebCore/Headers.cmake
  • Source/WebCore/WebCore.xcodeproj/project.pbxproj
  • Source/WebCore/dom/Element.cpp
  • Source/WebCore/html/HTMLFormControlElement.cpp
  • Source/WebCore/html/HTMLInputElement.cpp
  • Source/WebCore/html/HTMLOptionElement.cpp
  • Source/WebCore/html/ValidatedFormListedElement.cpp
  • Source/WebCore/platform/ControlStates.h
  • Source/WebCore/platform/Theme.h
  • Source/WebCore/platform/adwaita/ThemeAdwaita.cpp
  • Source/WebCore/platform/adwaita/ThemeAdwaita.h
  • Source/WebCore/platform/mac/ThemeMac.h
  • Source/WebCore/platform/mac/ThemeMac.mm
  • Source/WebCore/rendering/RenderElement.h
  • Source/WebCore/rendering/RenderTheme.cpp
  • Source/WebCore/rendering/RenderTheme.h
  • Source/WebCore/rendering/RenderThemeAdwaita.cpp
  • Source/WebCore/rendering/RenderThemeMac.mm

Patch Preview

diff --git a/Source/WebCore/Headers.cmake b/Source/WebCore/Headers.cmake
index 8e77c29ab1cb..cc6c4a31e6d5 100644
--- a/Source/WebCore/Headers.cmake
+++ b/Source/WebCore/Headers.cmake
@@ -1638,7 +1638,6 @@ set(WebCore_PRIVATE_FRAMEWORK_HEADERS
     platform/ContentType.h
     platform/ContextMenu.h
     platform/ContextMenuItem.h
-    platform/ControlStates.h
     platform/Cookie.h
     platform/CountedUserActivity.h
     platform/Cursor.h
diff --git a/Source/WebCore/WebCore.xcodeproj/project.pbxproj b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
index 3a27b071ec9a..7f8182f08adc 100644
--- a/Source/WebCore/WebCore.xcodeproj/project.pbxproj
+++ b/Source/WebCore/WebCore.xcodeproj/project.pbxproj
@@ -2809,7 +2809,6 @@
 		7553CFE8108F473F00EA281E /* TimelineRecordFactory.h in Headers */ = {isa = PBXBuildFile; fileRef = 7553CFE6108F473F00EA281E /* TimelineRecordFactory.h */; };
 		75793E840D0CE0B3007FC0AC /* MessageEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 75793E810D0CE0B3007FC0AC /* MessageEvent.h */; };
 		75793EC90D0CE72D007FC0AC /* JSMessageEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 75793EC70D0CE72D007FC0AC /* JSMessageEvent.h */; };
-		759CB837192DA9190012BC64 /* ControlStates.h in Headers */ = {isa = PBXBuildFile; fileRef = 311C08BC18E35D6800B65615 /* ControlStates.h */; settings = {ATTRIBUTES = (Private, ); }; };
 		7633A72613D8B33A008501B6 /* LocaleToScriptMapping.h in Headers */ = {isa = PBXBuildFile; fileRef = 7633A72413D8B33A008501B6 /* LocaleToScriptMapping.h */; };
 		7694563D1214D97C0007CBAE /* JSDOMTokenList.h in Headers */ = {isa = PBXBuildFile; fileRef = 7694563B1214D97C0007CBAE /* JSDOMTokenList.h */; };
 		76CDD2F31103DA6600680521 /* AccessibilityMenuList.h in Headers */ = {isa = PBXBuildFile; fileRef = 76CDD2ED1103DA6600680521 /* AccessibilityMenuList.h */; };
@@ -9295,7 +9294,6 @@
 		310D71931B335C9D009C7B73 /* ThemeCocoa.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ThemeCocoa.mm; sourceTree = "<group>"; };
 		310D71941B335C9D009C7B73 /* ThemeCocoa.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThemeCocoa.h; sourceTree = "<group>"; };
 		311518FB1E78C15F00EC514A /* GPUBasedCanvasRenderingContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GPUBasedCanvasRenderingContext.h; sourceTree = "<group>"; };
-		311C08BC18E35D6800B65615 /* ControlStates.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ControlStates.h; sourceTree = "<group>"; };
 		31288E6E0E3005D6003619AE /* CSSKeyframeRule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSKeyframeRule.cpp; sourceTree = "<group>"; };
 		31288E6F0E3005D6003619AE /* CSSKeyframeRule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CSSKeyframeRule.h; sourceTree = "<group>"; };
 		31288E700E3005D6003619AE /* CSSKeyframesRule.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CSSKeyframesRule.cpp; sourceTree = "<group>"; };
@@ -33488,7 +33486,6 @@
 				93B6A0E50B0BCA5C00F5027A /* ContextMenu.h */,
 				1AB40EE01BF4271E00BA81BE /* ContextMenuItem.cpp */,
 				06027CAC0B1CBFC000884B2D /* ContextMenuItem.h */,
-				311C08BC18E35D6800B65615 /* ControlStates.h */,
 				D8B6152E1032495100C8554A /* Cookie.h */,
 				862F129D18C1572C005C54AF /* CountedUserActivity.h */,
 				46C696CA1E7205E400597937 /* CPUMonitor.cpp */,
@@ -37929,7 +37926,6 @@
 				7ADE722610CBBB9B006B3B3A /* ContextMenuProvider.h in Headers */,
 				72A645262949158000C14BA3 /* ControlFactory.h in Headers */,
 				72A64520294903CE00C14BA3 /* ControlPart.h in Headers */,
-				759CB837192DA9190012BC64 /* ControlStates.h in Headers */,
 				72A645222949071600C14BA3 /* ControlStyle.h in Headers */,
 				FD31602912B0267600C1A359 /* ConvolverNode.h in Headers */,
 				83F28C0024DB1DE1005BA6F6 /* ConvolverOptions.h in Headers */,
diff --git a/Source/WebCore/dom/Element.cpp b/Source/WebCore/dom/Element.cpp
index 5c2d7fefef5c..b39121d020e5 100644
--- a/Source/WebCore/dom/Element.cpp
+++ b/Source/WebCore/dom/Element.cpp
@@ -865,7 +865,7 @@ void Element::setActive(bool value, Style::InvalidationScope invalidationScope)
         return;
 
     if (renderer->style().hasEffectiveAppearance())
-        renderer->theme().stateChanged(*renderer, ControlStates::States::Pressed);
+        renderer->theme().stateChanged(*renderer, ControlStyle::State::Pressed);
 }
 
 static bool shouldAlwaysHaveFocusVisibleWhenFocused(const Element& element)
@@ -940,7 +940,7 @@ void Element::setHovered(bool value, Style::InvalidationScope invalidationScope,
 
     if (auto* style = renderStyle(); style && style->hasEffectiveAppearance()) {
         CheckedPtr renderer = this->renderer();
-        renderer->theme().stateChanged(*renderer, ControlStates::States::Hovered);
+        renderer->theme().stateChanged(*renderer, ControlStyle::State::Hovered);
     }
 }
 
diff --git a/Source/WebCore/html/HTMLFormControlElement.cpp b/Source/WebCore/html/HTMLFormControlElement.cpp
index f987e1dcf29b..23346c801bad 100644
--- a/Source/WebCore/html/HTMLFormControlElement.cpp
+++ b/Source/WebCore/html/HTMLFormControlElement.cpp
@@ -27,7 +27,6 @@
 
 #include "AXObjectCache.h"
 #include "Autofill.h"
-#include "ControlStates.h"
 #include "ElementInlines.h"
 #include "Event.h"
 #include "EventHandler.h"
@@ -169,7 +168,7 @@ void HTMLFormControlElement::disabledStateChanged()
 {
     ValidatedFormListedElement::disabledStateChanged();
     if (renderer() && renderer()->style().hasEffectiveAppearance())
-        renderer()->theme().stateChanged(*renderer(), ControlStates::States::Enabled);
+        renderer()->theme().stateChanged(*renderer(), ControlStyle::State::Enabled);
 }
 
 void HTMLFormControlElement::readOnlyStateChanged()
diff --git a/Source/WebCore/html/HTMLInputElement.cpp b/Source/WebCore/html/HTMLInputElement.cpp
index f44045c80203..4083ef463449 100644
--- a/Source/WebCore/html/HTMLInputElement.cpp
+++ b/Source/WebCore/html/HTMLInputElement.cpp
@@ -1069,7 +1069,7 @@ void HTMLInputElement::setChecked(bool isChecked, WasSetByJavaScript wasCheckedB
     if (auto* buttons = radioButtonGroups())
         buttons->updateCheckedState(*this);
     if (auto* renderer = this->renderer(); renderer && renderer->style().hasEffectiveAppearance())
-        renderer->theme().stateChanged(*renderer, ControlStates::States::Checked);