Chrome · Transactions Platform
CVE-2026-85047
Logic Error in Transactions Platform
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/autofill/ios/browser/autofill_util.mm |
modified | |
forcomponents/autofill/ios/browser/autofill_util.mm |
modified | |
TEST_Fcomponents/autofill/ios/browser/autofill_util_unittest.mm |
modified |
Files Changed
components/autofill/ios/browser/autofill_util.hcomponents/autofill/ios/browser/autofill_util.mmcomponents/autofill/ios/browser/autofill_util_unittest.mm
Patch
From 57bfec56176222bcdf18fbf64d0c1c34489ef064 Mon Sep 17 00:00:00 2001 From: Christoph Schwering <[email protected]> Date: Tue, 30 Jun 2026 14:39:30 -0700 Subject: [PATCH] [Autofill] Validate child frame predecessor index on Bling Bug: 513790581 Change-Id: If56c123329c002de88317915b7aba32e0337e27a Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8004591 Reviewed-by: Vincent Boisselle <[email protected]> Commit-Queue: Christoph Schwering <[email protected]> Cr-Commit-Position: refs/heads/main@{#1655061} --- diff --git a/components/autofill/ios/browser/autofill_util.h b/components/autofill/ios/browser/autofill_util.h index 9e3fd331..eff9c0f0 100644 --- a/components/autofill/ios/browser/autofill_util.h +++ b/components/autofill/ios/browser/autofill_util.h @@ -134,12 +134,6 @@ const FieldDataManager& field_data_manager, FormFieldData* field_data); -// Extracts a single child frame's data from the JSON dictionary into a -// FrameTokenWithPredecessor object. Returns false if the data could not be -// extracted. -bool ExtractRemoteFrameToken(const base::DictValue& frame_data, - FrameTokenWithPredecessor* token_with_predecessor); - typedef base::OnceCallback<void(const base::Value*)> JavaScriptResultCallback; // Creates a callback for a string JS function return type. @@ -196,6 +190,13 @@ // works. web::WebFramesManager* GetWebFramesManagerForAutofill(web::WebState* web_state); +std::vector<FrameTokenWithPredecessor> ExtractChildFramesForTest( + const base::DictValue& form); + +bool ExtractRemoteFrameTokenForTest( + const base::DictValue& frame_data, + FrameTokenWithPredecessor* token_with_predecessor); + } // namespace autofill #endif // COMPONENTS_AUTOFILL_IOS_BROWSER_AUTOFILL_UTIL_H_ diff --git a/components/autofill/ios/browser/autofill_util.mm b/components/autofill/ios/browser/autofill_util.mm index 3588dca..b3065cf 100644 --- a/components/autofill/ios/browser/autofill_util.mm +++ b/components/autofill/ios/browser/autofill_util.mm @@ -70,6 +70,59 @@ std::move(callback).Run(result); } +// Extracts a single child frame's data from the JSON dictionary into a +// FrameTokenWithPredecessor object. Returns false if the data could not be +// extracted. +bool ExtractRemoteFrameToken( + const base::DictValue& frame_data, + FrameTokenWithPredecessor* token_with_predecessor) { + const std::string* frame_id = frame_data.FindString("token"); + if (!frame_id) { + return false; + } + + std::optional<base::UnguessableToken> token = + DeserializeJavaScriptFrameId(*frame_id); + if (!token) { + return false; + } + + const std::optional<int> predecessor = + frame_data.FindDouble("predecessor").transform([](double x) { + return base::saturated_cast<int>(x); + }); + if (!predecessor || *predecessor < -1) { + return false; + } + + token_with_predecessor->token = RemoteFrameToken(*token); + token_with_predecessor->predecessor = *predecessor; + return true; +} + +// Extracts the child frames from the JSON dictionary. Returns an empty vector +// if the data could not be extracted. +std::vector<FrameTokenWithPredecessor> ExtractChildFrames( + const base::DictValue& form) { + std::vector<FrameTokenWithPredecessor> child_frames; + if (const base::ListValue* child_frames_list = + form.FindList("child_frames")) { + for (const auto& frame_dict : *child_frames_list) { + if (FrameTokenWithPredecessor token; + frame_dict.is_dict() && + ExtractRemoteFrameToken(frame_dict.GetDict(), &token)) { + child_frames.push_back(std::move(token)); + } + } + } + // Validate that the child frames occur in ascending order. + if (!std::ranges::is_sorted(child_frames, {}, + &FrameTokenWithPredecessor::predecessor)) { + child_frames.clear(); + } + return child_frames; +} + } // namespace bool IsContextSecureForWebState(web::WebState* web_state) { @@ -257,19 +310,7 @@ } if (include_frame_metadata) { - // Child frame tokens, optional. - if (const base::ListValue* child_frames_list = - form.FindList("child_frames")) { - std::vector<FrameTokenWithPredecessor> child_frames; - for (const auto& frame_dict : *child_frames_list) { - FrameTokenWithPredecessor token; - if (frame_dict.is_dict() && - ExtractRemoteFrameToken(frame_dict.GetDict(), &token)) { - child_frames.push_back(std::move(token)); - } - } - form_data.set_child_frames(std::move(child_frames)); - } + form_data.set_child_frames(ExtractChildFrames(form)); } // Field list (mandatory) is extracted. @@ -436,31 +477,6 @@ return true; } -bool ExtractRemoteFrameToken( - const base::DictValue& frame_data, - FrameTokenWithPredecessor* token_with_predecessor) { - const std::string* frame_id = frame_data.FindString("token"); - if (!frame_id) { - return false; - } - - std::optional<base::UnguessableToken> token = - DeserializeJavaScriptFrameId(*frame_id); - if (!token) { - return false; - } - - const std::optional<double> predecessor = - frame_data.FindDouble("predecessor"); - if (!predecessor) { - return false; - } - - token_with_predecessor->token = RemoteFrameToken(*token); - token_with_predecessor->predecessor = *predecessor; - return true; -} - JavaScriptResultCallback CreateStringCallback( void (^completionHandler)(NSString*)) { return CreateStringCallback(base::BindOnce(completionHandler)); @@ -531,4 +547,15 @@ ContentWorldForAutofillJavascriptFeatures()); } +std::vector<FrameTokenWithPredecessor> ExtractChildFramesForTest( // IN-TEST + const base::DictValue& form) { + return ExtractChildFrames(form); +} + +bool ExtractRemoteFrameTokenForTest( // IN-TEST + const base::DictValue& frame_data, + FrameTokenWithPredecessor* token_with_predecessor) { + return ExtractRemoteFrameToken(frame_data, token_with_predecessor); +} + } // namespace autofill diff --git a/components/autofill/ios/browser/autofill_util_unittest.mm b/components/autofill/ios/browser/autofill_util_unittest.mm index 6dfb3af0..019909b 100644 --- a/components/autofill/ios/browser/autofill_util_unittest.mm +++ b/components/autofill/ios/browser/autofill_util_unittest.mm @@ -16,6 +16,8 @@ #import "components/autofill/core/common/form_field_data.h" #import "components/autofill/core/common/unique_ids.h" #import "components/autofill/ios/common/features.h" +#import "testing/gmock/include/gmock/gmock.h" +#import "testing/gtest/include/gtest/gtest.h" #import "testing/platform_test.h" #import "url/gurl.h" #import "url/origin.h" @@ -30,6 +32,8 @@ using ::autofill::ExtractIDs; using ::autofill::FieldRendererId; using ::base::ASCIIToUTF16; +using ::testing::IsEmpty; +using ::testing::SizeIs; TEST_F(AutofillUtilTest, ExtractFormData_FullUrl) { base::test::ScopedFeatureList scoped_feature_list; @@ -179,17 +183,16 @@
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/components/autofill/ios/browser/autofill_util_unittest.mm b/components/autofill/ios/browser/autofill_util_unittest.mm
index 6dfb3af0..019909b 100644
--- a/components/autofill/ios/browser/autofill_util_unittest.mm
+++ b/components/autofill/ios/browser/autofill_util_unittest.mm
@@ -16,6 +16,8 @@
#import "components/autofill/core/common/form_field_data.h"
#import "components/autofill/core/common/unique_ids.h"
#import "components/autofill/ios/common/features.h"
+#import "testing/gmock/include/gmock/gmock.h"
+#import "testing/gtest/include/gtest/gtest.h"
#import "testing/platform_test.h"
#import "url/gurl.h"
#import "url/origin.h"
@@ -30,6 +32,8 @@
using ::autofill::ExtractIDs;
using ::autofill::FieldRendererId;
using ::base::ASCIIToUTF16;
+using ::testing::IsEmpty;
+using ::testing::SizeIs;
TEST_F(AutofillUtilTest, ExtractFormData_FullUrl) {
base::test::ScopedFeatureList scoped_feature_list;
@@ -179,17 +183,16 @@
EXPECT_FALSE(token.has_value());
}
-// Test that the properties mask is extracted from the form field data.
+// Test that the child frames is extracted from the form field data.
TEST_F(AutofillUtilTest, ExtractRemoteFrameToken) {
- base::DictValue remote_frame_token_dict;
- remote_frame_token_dict.Set("token",
- base::Value("beefbeefbeefbeefcafecafecafecafe"));
- remote_frame_token_dict.Set("predecessor", base::Value(64));
+ base::DictValue wellformed1;
+ wellformed1.Set("token", base::Value("beefbeefbeefbeefcafecafecafecafe"));
+ wellformed1.Set("predecessor", base::Value(64));
autofill::FrameTokenWithPredecessor token_with_predecessor;
- ASSERT_TRUE(ExtractRemoteFrameToken(remote_frame_token_dict,
- &token_with_predecessor));
+ ASSERT_TRUE(
+ ExtractRemoteFrameTokenForTest(wellformed1, &token_with_predecessor));
EXPECT_EQ(base::ToLowerASCII(std::get<autofill::RemoteFrameToken>(
token_with_predecessor.token)
.ToString()),
@@ -198,16 +201,78 @@
base::DictValue malformed1;
malformed1.Set("garbage", base::Value("garbage"));
- EXPECT_FALSE(ExtractRemoteFrameToken(malformed1, &token_with_predecessor));
+ EXPECT_FALSE(
+ ExtractRemoteFrameTokenForTest(malformed1, &token_with_predecessor));
base::DictValue malformed2;
malformed2.Set("token", base::Value("garbage"));
- EXPECT_FALSE(ExtractRemoteFrameToken(malformed2, &token_with_predecessor));
+ EXPECT_FALSE(
+ ExtractRemoteFrameTokenForTest(malformed2, &token_with_predecessor));
base::DictValue malformed3;
malformed3.Set("token", base::Value("beefbeefbeefbeefcafecafecafecafe"));
malformed3.Set("predecessor", base::Value("garbage"));
- EXPECT_FALSE(ExtractRemoteFrameToken(malformed3, &token_with_predecessor));
+ EXPECT_FALSE(
+ ExtractRemoteFrameTokenForTest(malformed3, &token_with_predecessor));
+
+ // Test that -1 is the only negative number supported for `predecessor`.
+ base::DictValue wellformed2 = wellformed1.Clone();
+ wellformed2.Set("predecessor", base::Value(-1));
+ EXPECT_TRUE(
+ ExtractRemoteFrameTokenForTest(wellformed2, &token_with_predecessor));
+
+ base::DictValue malformed4 = wellformed1.Clone();
+ malformed4.Set("predecessor", base::Value(-5));
+ EXPECT_FALSE(
+ ExtractRemoteFrameTokenForTest(malformed4, &token_with_predecessor));
+}
+
+// Tests that ExtractChildFrames() only accepts predecessors that in ascending
+// order.
+TEST_F(AutofillUtilTest, ExtractChildFrames_PredecessorsMustBeSorted) {
+ auto create_child = [](std::string token, int predecessor) {
+ base::DictValue child;
+ child.Set("token", base::Value(std::move(token)));
+ child.Set("predecessor", base::Value(predecessor));
+ return child;
+ };
+ auto create_children = [](auto&&... children) {
+ base::ListValue list;
+ (list.Append(std::move(children)), ...);
+ return list;
+ };
+
+ base::DictValue form;
+ EXPECT_THAT(ExtractChildFramesForTest(form), IsEmpty());
+
+ form.Set("child_frames", base::ListValue());
+ EXPECT_THAT(ExtractChildFramesForTest(form), IsEmpty());
+
+ form.Set("child_frames", create_children());
+ EXPECT_THAT(ExtractChildFramesForTest(form), IsEmpty());
+
+ form.Set("child_frames", create_children(create_child(
+ "aeefbeefbeefbeefcafecafecafecafe", 12)));
+ EXPECT_THAT(ExtractChildFramesForTest(form), SizeIs(1));
+
+ form.Set(
+ "child_frames",
+ create_children(create_child("aeefbeefbeefbeefcafecafecafecafe", 12),
+ create_child("beefbeefbeefbeefcafecafecafecafe", 23)));
+ EXPECT_THAT(ExtractChildFramesForTest(form), SizeIs(2));
+
+ form.Set(
+ "child_frames",
+ create_children(create_child("aeefbeefbeefbeefcafecafecafecafe", -1),
+ create_child("beefbeefbeefbeefcafecafecafecafe", 12),
+ create_child("ceefbeefbeefbeefcafecafecafecafe", 23)));
+ EXPECT_THAT(ExtractChildFramesForTest(form), SizeIs(3));
+
+ form.Set(
+ "child_frames",
+ create_children(create_child("aeefbeefbeefbeefcafecafecafecafe", 99),
+ create_child("beefbeefbeefbeefcafecafecafecafe", 1)));
+ EXPECT_THAT(ExtractChildFramesForTest(form), IsEmpty());
}
} // namespace
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page