High watch CVSS 8.1 chrome OOB 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactHeap buffer overflow in Sync
DescriptionHeap buffer overflow in Sync
ComponentSync
Sub-componentTrustedVault
Bug ClassOOB
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker443196747
Fix commit0e63d7ab6292 (chromium/src) +18/-2
CWECWE-122, CWE-787 (Heap buffer overflow, Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N
CISA KEVNot listed
Creditedraven at KunLun lab
Reported2025-09-05 · bounty $5000 (per vendor advisory)
Fix landed2025-09-26 (public git)
Disclosed2025-10-07
Patch gap11 days — fix public in source before the advisory

Background

Trusted Vault / SecureBox
components/trusted_vault holds the browser-side client for Chrome Sync’s trusted-vault (security-domain) key management. securebox.cc implements 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 0x04 prefix byte followed by the 32-byte X and Y coordinates. Its length is a fixed constant, kECPointLength in this file.
`ECPublicKeyFromBytes`
a helper in securebox.cc that turns public_key_bytes into a BoringSSL EC_KEY on curve NID_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.

Key insight
The public-key import path relied on a fixed-length precondition it had only written in a comment and never checked, so wrongly sized key material could reach code that assumed kECPointLength bytes. The fix enforces the exact length at the entry to ECPublicKeyFromBytes and fails closed with nullptr.

Attack Path

  1. 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).
  2. Reach `ECPublicKeyFromBytes` without a size check Before the fix, the span went straight into EC_KEY construction for P-256 with no length check, even though the function’s contract required a single X9.62 point.
  3. Fixed-size assumption on a mismatched buffer Code that assumed kECPointLength bytes 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).
  4. 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

The advisory reports a heap buffer overflow in Sync with vendor severity High; the metadata gives no in-the-wild exploitation status. The vulnerable code is in the browser-process trusted-vault component, so memory corruption there is outside the renderer sandbox. Any escalation to arbitrary code execution is (inferred). Nothing in the diff shows ordinary web content reaching 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

FunctionChangeNotes
TEST_F
components/trusted_vault/securebox_unittest.cc
modified

Files Changed

  • components/trusted_vault/securebox.cc
  • components/trusted_vault/securebox_unittest.cc

Audit Directions

  • Preconditions only in comments
    In components/trusted_vault and other crypto wrappers, look for “must be N bytes” / “must be formatted as …” comments on functions that take base::span or raw pointers, and check that a runtime size check or a fixed-extent span<const uint8_t, N> enforces them.
  • Other SecureBox import paths
    Check 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 material
    Trace 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.
From 0e63d7ab62928f5645a42484fbf47cba43b0128f Mon Sep 17 00:00:00 2001
From: Mikel Astiz <mastiz@chromium.org>
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 <mmoskvitin@google.com>
Commit-Queue: Mikel Astiz <mastiz@chromium.org>
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();
Loading diff…

Regression Test / PoC

shipped with the fix
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();
Loading diff…

Original Bug Report

reported by wx...@gmail.com

out of bound in function ECPublicKeyFromBytes

Steps to reproduce the problem

my chromim commit is 6a99accd9be43e397fe4e41571a7e1f5c656ab0d

  1. 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.)
  2. out\asan\chrome.exe –no-sandbox https://accounts.google.com/
  3. 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 \

View on issue tracker