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.cmakeSource/WebCore/WebCore.xcodeproj/project.pbxprojSource/WebCore/dom/Element.cppSource/WebCore/html/HTMLFormControlElement.cppSource/WebCore/html/HTMLInputElement.cppSource/WebCore/html/HTMLOptionElement.cppSource/WebCore/html/ValidatedFormListedElement.cppSource/WebCore/platform/ControlStates.hSource/WebCore/platform/Theme.hSource/WebCore/platform/adwaita/ThemeAdwaita.cppSource/WebCore/platform/adwaita/ThemeAdwaita.hSource/WebCore/platform/mac/ThemeMac.hSource/WebCore/platform/mac/ThemeMac.mmSource/WebCore/rendering/RenderElement.hSource/WebCore/rendering/RenderTheme.cppSource/WebCore/rendering/RenderTheme.hSource/WebCore/rendering/RenderThemeAdwaita.cppSource/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);