CVE-2025-11458
Overview
Background
- Trusted Vault / SecureBox
components/trusted_vaultholds the browser-side client for Chrome Sync’s trusted-vault (security-domain) key management.securebox.ccimplements the SecureBox public-key encryption scheme it uses to wrap and unwrap keys.- `SecureBoxPublicKey::CreateByImport`
- the factory that builds a SecureBox public key from serialized bytes. It is expected to return null on malformed input, as the commit message and the new unit test state.
- X9.62 uncompressed P-256 point
- the serialization of a NIST P-256 public key: one
0x04prefix byte followed by the 32-byte X and Y coordinates. Its length is a fixed constant,kECPointLengthin this file. - `ECPublicKeyFromBytes`
- a helper in
securebox.ccthat turnspublic_key_bytesinto a BoringSSLEC_KEYon curveNID_X9_62_prime256v1.
Root Cause Analysis
ECPublicKeyFromBytes took an arbitrary-length base::span<const uint8_t> public_key_bytes. Its own removed comment stated a precondition ("public_key_bytes must be a X9.62 formatted NIST P-256 point") that it never enforced. According to the commit message, SecureBoxPublicKey::CreateByImport() lacked “safeguards against input data not matching the expected size”, so input of the wrong length reached the EC-key construction code. The advisory classifies the result as a heap buffer overflow. The diff does not show the exact sink after the check. The likeliest mechanism is code later in the function, or in its caller, that assumes the buffer is exactly kECPointLength bytes and reads or copies a fixed-size region from the shorter or longer heap buffer (inferred).
The fix adds a check at the very top of ECPublicKeyFromBytes, before EC_KEY_new_by_curve_name and any later parsing. It returns nullptr when public_key_bytes.size() != kECPointLength, and the comment now says the function returns null for invalid input. The new test confirms that 64-byte, 66-byte and empty inputs to CreateByImport now give null.
kECPointLength bytes. The fix enforces the exact length at the entry to ECPublicKeyFromBytes and fails closed with nullptr.Attack Path
- Supply a malformed public key
Wrongly sized bytes reach
SecureBoxPublicKey::CreateByImport(the new unit test calls it with 64, 66 and 0 bytes). The patch does not show which production callers pass untrusted bytes; possible sources are trusted-vault server responses or persisted vault state (inferred). - Reach `ECPublicKeyFromBytes` without a size check
Before the fix, the span went straight into
EC_KEYconstruction for P-256 with no length check, even though the function’s contract required a single X9.62 point. - Fixed-size assumption on a mismatched buffer
Code that assumed
kECPointLengthbytes then accessed the heap buffer out of bounds, which the advisory reports as a heap buffer overflow. The exact access is not in the diff (inferred). - Memory corruption in the browser process The trusted-vault component runs in the browser process, so an overflow there could corrupt browser heap memory (escalation to code execution is inferred, not shown).
Impact Assessment
CreateByImport. Exploitation probably requires Sync or trusted-vault features to be in use and the attacker to influence the public-key bytes that Chrome imports, for example through a server response or tampered local state (inferred). This makes the preconditions much narrower than for a renderer-reachable bug.Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcomponents/trusted_vault/securebox_unittest.cc |
modified |
Files Changed
components/trusted_vault/securebox.cccomponents/trusted_vault/securebox_unittest.cc
Audit Directions
- Preconditions only in commentsIn
components/trusted_vaultand other crypto wrappers, look for “must be N bytes” / “must be formatted as …” comments on functions that takebase::spanor raw pointers, and check that a runtime size check or a fixed-extentspan<const uint8_t, N>enforces them. - Other SecureBox import pathsCheck the sibling private-key import (
SecureBoxPrivateKey), the key-pair import, and the encrypt/decrypt routines (SecureBoxKeyPair, the SecureBox decrypt helpers) for similar unchecked lengths on ciphertext headers, nonces and embedded public points. - Server-supplied key materialTrace where trusted-vault RPC responses and persisted vault protos (member keys, key-rotation proofs, wrapped keys) are deserialized into fixed-size crypto structures, and confirm that every length is validated before parsing.
Patch
From 0e63d7ab62928f5645a42484fbf47cba43b0128f Mon Sep 17 00:00:00 2001 From: Mikel Astiz <[email protected]> Date: Fri, 26 Sep 2025 11:49:35 -0700 Subject: [PATCH] [TrustedVault] Fix public import missing size validation `SecureBoxPublicKey::CreateByImport()` should have safeguards against input data not matching the expected size, and return null in that case. Bug: 443196747 Change-Id: I0996dbe383a51b1fd8269fbe429c78f24f283989 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6990129 Reviewed-by: Maksim Moskvitin <[email protected]> Commit-Queue: Mikel Astiz <[email protected]> Cr-Commit-Position: refs/heads/main@{#1521442} --- diff --git a/components/trusted_vault/securebox.cc b/components/trusted_vault/securebox.cc index 384c7c5..ad63561 100644 --- a/components/trusted_vault/securebox.cc +++ b/components/trusted_vault/securebox.cc @@ -63,11 +63,16 @@ return result; } -// Creates public EC_KEY from |public_key_bytes|. |public_key_bytes| must be -// a X9.62 formatted NIST P-256 point. +// Creates public EC_KEY from |public_key_bytes|. Returns nullptr if +// |public_key_bytes| does not represent a X9.62 formatted NIST P-256 point. bssl::UniquePtr<EC_KEY> ECPublicKeyFromBytes( base::span<const uint8_t> public_key_bytes, const crypto::OpenSSLErrStackTracer& err_tracer) { + if (public_key_bytes.size() != kECPointLength) { + // |public_key_bytes| doesn't represent a valid NIST P-256 point. + return nullptr; + } + bssl::UniquePtr<EC_KEY> ec_key( EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); DCHECK(ec_key); diff --git a/components/trusted_vault/securebox_unittest.cc b/components/trusted_vault/securebox_unittest.cc index dfd08987..45622b3 100644 --- a/components/trusted_vault/securebox_unittest.cc +++ b/components/trusted_vault/securebox_unittest.cc @@ -25,6 +25,7 @@ using testing::Eq; using testing::IsEmpty; +using testing::IsNull; using testing::Ne; using testing::NotNull; using testing::SizeIs; @@ -43,6 +44,16 @@ const std::vector<uint8_t> kTestPayload = StringToBytes("TEST_PAYLOAD"); }; +TEST_F(SecureBoxTest, ShouldReturnNullIfCreateByImportWithIncorrectSize) { + // Sizes other than `kPublicKeyLengthInBytes` should return null. + EXPECT_THAT(SecureBoxPublicKey::CreateByImport(std::vector<uint8_t>(64, 1)), + IsNull()); + EXPECT_THAT(SecureBoxPublicKey::CreateByImport(std::vector<uint8_t>(66, 1)), + IsNull()); + EXPECT_THAT(SecureBoxPublicKey::CreateByImport(std::vector<uint8_t>()), + IsNull()); +} + TEST_F(SecureBoxTest, ShouldExportAndImportPublicKey) { std::unique_ptr<SecureBoxKeyPair> key_pair = SecureBoxKeyPair::GenerateRandom();
Regression Test / PoC
diff --git a/components/trusted_vault/securebox_unittest.cc b/components/trusted_vault/securebox_unittest.cc
index dfd08987..45622b3 100644
--- a/components/trusted_vault/securebox_unittest.cc
+++ b/components/trusted_vault/securebox_unittest.cc
@@ -25,6 +25,7 @@
using testing::Eq;
using testing::IsEmpty;
+using testing::IsNull;
using testing::Ne;
using testing::NotNull;
using testing::SizeIs;
@@ -43,6 +44,16 @@
const std::vector<uint8_t> kTestPayload = StringToBytes("TEST_PAYLOAD");
};
+TEST_F(SecureBoxTest, ShouldReturnNullIfCreateByImportWithIncorrectSize) {
+ // Sizes other than `kPublicKeyLengthInBytes` should return null.
+ EXPECT_THAT(SecureBoxPublicKey::CreateByImport(std::vector<uint8_t>(64, 1)),
+ IsNull());
+ EXPECT_THAT(SecureBoxPublicKey::CreateByImport(std::vector<uint8_t>(66, 1)),
+ IsNull());
+ EXPECT_THAT(SecureBoxPublicKey::CreateByImport(std::vector<uint8_t>()),
+ IsNull());
+}
+
TEST_F(SecureBoxTest, ShouldExportAndImportPublicKey) {
std::unique_ptr<SecureBoxKeyPair> key_pair =
SecureBoxKeyPair::GenerateRandom();
Original Bug Report
out of bound in function ECPublicKeyFromBytes
Steps to reproduce the problem
my chromim commit is 6a99accd9be43e397fe4e41571a7e1f5c656ab0d
- Apply my patch and build Chromium.(This patch only removes the account check that is unrelated to the vulnerability. Since Chromium generally does not have Chrome accounts, I have temporarily disabled it.)
- out\asan\chrome.exe –no-sandbox https://accounts.google.com/
- Enter the code in the browser’s address bar
jaavscript:let buffer = new ArrayBuffer(1);let view = new Uint8Array(buffer);view[0] = 4;chrome.addTrustedSyncEncryptionRecoveryMethod(()=>{console.log("123")},"test", buffer,2);
you will see the asan output
Problem Description
the function https://source.chromium.org/chromium/chromium/src/+/main:chrome/common/trusted_vault_encryption_keys_extension.mojom;l=28;drc=e3ad182cacc3a02eb6a13091a867815fd17a129c;bpv=0;bpt=1
AddTrustedRecoveryMethod(string gaia_id, array<uint8> public_key, int32 method_type_hint) => ();
This function receives a public_key sent from the renderer, but it does not perform any length validation. As a result, if the renderer sends malicious data, it will be passed directly to the EC_POINT_oct2point function for parsing, which leads to an out-of-bounds read.
Since the renderer parameters are not properly checked here, an exploit prerequisite is either an XSS on accounts.google.com (allowing us to execute arbitrary JavaScript), or a renderer RCE that can send arbitrary IPC messages.
let buffer = new ArrayBuffer(1);
let view = new Uint8Array(buffer);
view[0] = 4;
chrome.addTrustedSyncEncryptionRecoveryMethod(()=>{console.log("123")},"test", buffer,2);
When this JavaScript snippet is executed on the accounts.google.com page, the buffer with length 1 is passed to EC_POINT_oct2point for parsing, resulting in an out-of-bounds read in the browser process.
Summary
out of bound in function ECPublicKeyFromBytes
Custom Questions
Type of crash:
browser
Reporter credit:
raven at KunLun Lab
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \