From 28686e63de0d3d7270a49b0d6b656467bc4fbf68 Mon Sep 17 00:00:00 2001
From: Justin Michaud <justin_michaud@apple.com>
Date: Wed, 9 Nov 2022 19:20:41 -0800
Subject: [PATCH] Error() ICs should not cache special properties.
 https://bugs.webkit.org/show_bug.cgi?id=247699

Reviewed by Yusuke Suzuki.

HasOwnProperty/DeleteProperty are not always cacheable for special Error()
properties like column. These special properties are materialized on-demand
in materializeErrorInfoIfNeeded, but this function's behaviour can be changed
by Error.stackTraceLimit without causing a structure transition or firing watchpoints.

That is, we cannot cache property misses, and we cannot assume HasOwnProperty is deterministic
for a given structure if we are using one of these properties.

* Source/JavaScriptCore/runtime/ErrorInstance.cpp:
(JSC::ErrorInstance::deleteProperty):
* Source/JavaScriptCore/runtime/ErrorInstance.h:

Canonical link: https://commits.webkit.org/256519@main

CVE: CVE-2022-46699

Upstream-Status: Backport
[https://github.com/WebKit/WebKit/commit/28686e63de0d3d7270a49b0d6b656467bc4fbf68]

Signed-off-by: Yogita Urade <yogita.urade@windriver.com>
---
 JSTests/stress/delete-cache-error.js          | 19 ++++++++++++++++++
 .../get-own-property-slot-cache-error.js      |  6 ++++++
 JSTests/stress/get-property-cache-error.js    | 20 +++++++++++++++++++
 .../JavaScriptCore/runtime/ErrorInstance.cpp  |  4 +++-
 Source/JavaScriptCore/runtime/ErrorInstance.h |  3 ++-
 5 files changed, 50 insertions(+), 2 deletions(-)
 create mode 100644 JSTests/stress/delete-cache-error.js
 create mode 100644 JSTests/stress/get-own-property-slot-cache-error.js
 create mode 100644 JSTests/stress/get-property-cache-error.js

diff --git a/JSTests/stress/delete-cache-error.js b/JSTests/stress/delete-cache-error.js
new file mode 100644
index 000000000000..d77c09185a13
--- /dev/null
+++ b/JSTests/stress/delete-cache-error.js
@@ -0,0 +1,19 @@
+delete Error.stackTraceLimit
+
+// sourceURL is not materialized
+function cacheColumn(o) {
+    delete o.sourceURL
+}
+noInline(cacheColumn)
+
+for (let i = 0; i < 200; ++i) {
+    let e = Error()
+    cacheColumn(e)
+    if (e.sourceURL !== undefined)
+        throw "Test failed on iteration " + i + " " + e.sourceURL
+
+    if (i == 197) {
+        // now it is
+        Error.stackTraceLimit = 10
+    }
+}
\ No newline at end of file
diff --git a/JSTests/stress/get-own-property-slot-cache-error.js b/JSTests/stress/get-own-property-slot-cache-error.js
new file mode 100644
index 000000000000..f8202213bf79
--- /dev/null
+++ b/JSTests/stress/get-own-property-slot-cache-error.js
@@ -0,0 +1,6 @@
+delete Error.stackTraceLimit
+// GetOwnPropertySlot does not materializeErrorInfoIfNeeded because stackString is null.
+Object.hasOwn(Error(), "column")
+Error.stackTraceLimit = 10
+// Now it does
+Object.hasOwn(Error(), "column")
\ No newline at end of file
diff --git a/JSTests/stress/get-property-cache-error.js b/JSTests/stress/get-property-cache-error.js
new file mode 100644
index 000000000000..b35272ea6fe2
--- /dev/null
+++ b/JSTests/stress/get-property-cache-error.js
@@ -0,0 +1,20 @@
+// GetOwnPropertySlot does not materializeErrorInfoIfNeeded because stackString is null.
+delete Error.stackTraceLimit
+expected = undefined
+
+function cacheColumn(o) {
+    return o.column
+}
+noInline(cacheColumn)
+
+for (let i = 0; i < 1000; ++i) {
+    let val = cacheColumn(Error())
+    if (val !== expected)
+        throw "Test failed on iteration " + i + ": " + val
+
+    if (i == 900) {
+        // now it does
+        Error.stackTraceLimit = 10
+        expected = 32
+    }
+}
\ No newline at end of file
diff --git a/Source/JavaScriptCore/runtime/ErrorInstance.cpp b/Source/JavaScriptCore/runtime/ErrorInstance.cpp
index ddf96869e84a..8e5373257d34 100644
--- a/Source/JavaScriptCore/runtime/ErrorInstance.cpp
+++ b/Source/JavaScriptCore/runtime/ErrorInstance.cpp
@@ -303,7 +303,9 @@ bool ErrorInstance::deleteProperty(JSCell* cell, JSGlobalObject* globalObject, P
 {
     VM& vm = globalObject->vm();
     ErrorInstance* thisObject = jsCast<ErrorInstance*>(cell);
-    thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
+    bool materializedProperties = thisObject->materializeErrorInfoIfNeeded(vm, propertyName);
+    if (materializedProperties)
+        slot.disableCaching();
     return Base::deleteProperty(thisObject, globalObject, propertyName, slot);
 }

diff --git a/Source/JavaScriptCore/runtime/ErrorInstance.h b/Source/JavaScriptCore/runtime/ErrorInstance.h
index 28807b4ea33e..2afb153a7442 100644
--- a/Source/JavaScriptCore/runtime/ErrorInstance.h
+++ b/Source/JavaScriptCore/runtime/ErrorInstance.h
@@ -30,7 +30,8 @@ namespace JSC {
 class ErrorInstance : public JSNonFinalObject {
 public:
     using Base = JSNonFinalObject;
-    static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | OverridesGetOwnSpecialPropertyNames | OverridesPut;
+
+    static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | OverridesGetOwnSpecialPropertyNames | OverridesPut | GetOwnPropertySlotIsImpureForPropertyAbsence;
     static constexpr bool needsDestruction = true;

     static void destroy(JSCell* cell)
--
2.40.0
