CVE-2026-79192
Overview
Files Changed
chrome/browser/net/cert_verifier_service_time_updater.ccservices/cert_verifier/cert_verifier_service_factory.ccservices/cert_verifier/cert_verifier_service_factory.hservices/cert_verifier/cert_verifier_service_factory_unittest.ccservices/cert_verifier/public/mojom/cert_verifier_service_factory.mojom
Patch
From 823df872a35856b0d49804c3bd097810bebea534 Mon Sep 17 00:00:00 2001
From: Roger McFarlane <rogerm@chromium.org>
Date: Thu, 23 Apr 2026 12:10:17 -0700
Subject: [PATCH] Plumb network time uncertainty to CertVerifierService.
This CL updates the CertVerifierServiceFactory::UpdateNetworkTime Mojo
interface to accept the NetworkTimeTracker's uncertainty parameter,
and ensures that the CertVerifierServiceTimeUpdater forwards it.
This allows the CertVerifierService to consider network time uncertainty
and skew when deciding on the validity of a certificate.
Bug: 502082953
Change-Id: I62df04169de2ee8eb90e14f4017d786e1f5a5e54
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7765821
Reviewed-by: Matt Mueller <mattm@chromium.org>
Reviewed-by: Joe Mason <joenotcharles@google.com>
Reviewed-by: Alexei Svitkine <asvitkine@chromium.org>
Commit-Queue: Roger McFarlane <rogerm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1619674}
---
diff --git a/chrome/browser/net/cert_verifier_service_time_updater.cc b/chrome/browser/net/cert_verifier_service_time_updater.cc
index 979cd79..e0535185 100644
--- a/chrome/browser/net/cert_verifier_service_time_updater.cc
+++ b/chrome/browser/net/cert_verifier_service_time_updater.cc
@@ -17,7 +17,8 @@
network_time::TimeTracker::TimeTrackerState state;
if (tracker->GetTrackerState(&state)) {
content::GetCertVerifierServiceFactory()->UpdateNetworkTime(
- state.system_time, state.system_ticks, state.known_time);
+ state.system_time, state.system_ticks, state.known_time,
+ state.uncertainty);
}
}
@@ -26,5 +27,6 @@
void CertVerifierServiceTimeUpdater::OnNetworkTimeChanged(
network_time::TimeTracker::TimeTrackerState state) {
content::GetCertVerifierServiceFactory()->UpdateNetworkTime(
- state.system_time, state.system_ticks, state.known_time);
+ state.system_time, state.system_ticks, state.known_time,
+ state.uncertainty);
}
diff --git a/services/cert_verifier/cert_verifier_service_factory.cc b/services/cert_verifier/cert_verifier_service_factory.cc
index 6efcda37b..30d0b20 100644
--- a/services/cert_verifier/cert_verifier_service_factory.cc
+++ b/services/cert_verifier/cert_verifier_service_factory.cc
@@ -510,9 +510,11 @@
void CertVerifierServiceFactoryImpl::UpdateNetworkTime(
base::Time system_time,
base::TimeTicks system_ticks,
- base::Time current_time) {
+ base::Time current_time,
+ base::TimeDelta uncertainty) {
+ // TODO: crbug.com/502082953 - Consider what to do if the uncertainty is high.
proc_params_.time_tracker.emplace(system_time, system_ticks, current_time,
- base::TimeDelta());
+ uncertainty);
UpdateVerifierServices();
}
diff --git a/services/cert_verifier/cert_verifier_service_factory.h b/services/cert_verifier/cert_verifier_service_factory.h
index f6239094..ba3a9f5 100644
--- a/services/cert_verifier/cert_verifier_service_factory.h
+++ b/services/cert_verifier/cert_verifier_service_factory.h
@@ -89,7 +89,8 @@
#endif
void UpdateNetworkTime(base::Time system_time,
base::TimeTicks system_ticks,
- base::Time current_time) override;
+ base::Time current_time,
+ base::TimeDelta uncertainty) override;
// Remove a CertVerifyService from needing updates to the Chrome Root Store.
void RemoveService(internal::CertVerifierServiceImpl* service_impl);
diff --git a/services/cert_verifier/cert_verifier_service_factory_unittest.cc b/services/cert_verifier/cert_verifier_service_factory_unittest.cc
index d35fe3f..61bfb2c 100644
--- a/services/cert_verifier/cert_verifier_service_factory_unittest.cc
+++ b/services/cert_verifier/cert_verifier_service_factory_unittest.cc
@@ -68,6 +68,8 @@
namespace cert_verifier {
namespace {
+constexpr base::TimeDelta kNetworkTimeUncertainty = base::Seconds(31);
+
struct DummyCVServiceRequest : public mojom::CertVerifierRequest {
explicit DummyCVServiceRequest(base::RepeatingClosure on_finish)
: on_finish_(std::move(on_finish)) {}
@@ -1519,7 +1521,8 @@
// Update the time tracker so the current time is within the certificate
// validity range.
cv_service_factory_impl.UpdateNetworkTime(now, ticks_now,
- now - base::Days(2));
+ now - base::Days(2),
+ kNetworkTimeUncertainty);
mojo::Remote<mojom::CertVerifierService> cv_service_remote;
DummyCVServiceClient cv_service_client;
@@ -1587,7 +1590,8 @@
// Update the time tracker so the current time is within the certificate
// validity range.
cv_service_factory_impl.UpdateNetworkTime(now, ticks_now,
- now - base::Days(2));
+ now - base::Days(2),
+ kNetworkTimeUncertainty);
// Update should have been notified.
EXPECT_NO_FATAL_FAILURE(cv_service_client.WaitForCertVerifierChange(1u));
diff --git a/services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom b/services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom
index 8afc587..6453cbdb 100644
--- a/services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom
+++ b/services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom
@@ -132,12 +132,17 @@
[EnableIf=is_platform_root_store_info_supported]
GetPlatformRootStoreInfo() => (PlatformRootStoreInfo root_store_info);
- // Updates the time used for certificate verification. After this is called,
- // the system time will be recorded along with a known time passed in as
- // `current_time`. Future verification attempts will estimate the real time
- // based on how much the system clock has advanced since the last update.
- // This only has an effect when the built-in verifier is being used.
+ // Synchronizes the clock used for certificate verification. After this is
+ // called, the local machine's time (recorded as both wall clock system time
+ // and system ticks) will be correlated with a known current_time and an
+ // estimate of uncertainty. Future certificate verification attempts will
+ // estimate the real time based on how much the system clock has advanced
+ // since the last update. This only has an effect when the built-in verifier
+ // is being used. The CertVerifierService implementation may choose to ignore
+ // or adjust the synchronziation request if the uncertainty is too high (impl
+ // defined)
UpdateNetworkTime(mojo_base.mojom.Time system_time,
mojo_base.mojom.TimeTicks system_ticks,
- mojo_base.mojom.Time current_time);
+ mojo_base.mojom.Time current_time,
+ mojo_base.mojom.TimeDelta uncertainty);
};
Regression Test / PoC
diff --git a/services/cert_verifier/cert_verifier_service_factory_unittest.cc b/services/cert_verifier/cert_verifier_service_factory_unittest.cc
index d35fe3f..61bfb2c 100644
--- a/services/cert_verifier/cert_verifier_service_factory_unittest.cc
+++ b/services/cert_verifier/cert_verifier_service_factory_unittest.cc
@@ -68,6 +68,8 @@
namespace cert_verifier {
namespace {
+constexpr base::TimeDelta kNetworkTimeUncertainty = base::Seconds(31);
+
struct DummyCVServiceRequest : public mojom::CertVerifierRequest {
explicit DummyCVServiceRequest(base::RepeatingClosure on_finish)
: on_finish_(std::move(on_finish)) {}
@@ -1519,7 +1521,8 @@
// Update the time tracker so the current time is within the certificate
// validity range.
cv_service_factory_impl.UpdateNetworkTime(now, ticks_now,
- now - base::Days(2));
+ now - base::Days(2),
+ kNetworkTimeUncertainty);
mojo::Remote<mojom::CertVerifierService> cv_service_remote;
DummyCVServiceClient cv_service_client;
@@ -1587,7 +1590,8 @@
// Update the time tracker so the current time is within the certificate
// validity range.
cv_service_factory_impl.UpdateNetworkTime(now, ticks_now,
- now - base::Days(2));
+ now - base::Days(2),
+ kNetworkTimeUncertainty);
// Update should have been notified.
EXPECT_NO_FATAL_FAILURE(cv_service_client.WaitForCertVerifierChange(1u));
Original Bug Report
Cert validation bypass via NetworkTimeTracker past-skew from delayed Variations Seed body
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: An on-path attacker can skew Chrome’s NetworkTimeTracker into the past by intentionally delaying the HTTP response body of a Variations Seed fetch. The resulting uncertainty value is incorrectly discarded before reaching the CertVerifierService, causing it to fully trust the skewed network time. This potentially allows the attacker to bypass certificate expiration checks and impersonate arbitrary origins using expired certificates.
Affected files:
components/variations/service/variations_service.ccchrome/browser/net/cert_verifier_service_time_updater.ccservices/cert_verifier/cert_verifier_service_factory.ccnet/cert/cert_verify_proc_builtin.cccomponents/network_time/network_time_tracker.cc
Estimated timestamp from git blame: 2026-03-12
Vulnerability Details
This vulnerability arises from a chain of logic errors spanning the Variations Service, Network Time Tracker, and the Certificate Verifier:
- Unbounded Fetch & Inflated Latency: In
components/variations/service/variations_service.cc,DoFetchFromURLcreates aSimpleURLLoaderto download the variations seed usingDownloadToStringOfUnboundedSizeUntilCrashAndDie, but fails to set a timeout duration.OnSimpleLoaderCompletecalculates request latency as the entire duration from request start to full body completion (now - last_request_started_time_). An on-path attacker can trickle the response body over a prolonged duration $\Delta$, arbitrarily inflating this latency. - Time Skew:
VariationsServicepasses this inflated latency toNetworkTimeTracker::UpdateNetworkTime. The tracker estimates that the server’s time (from the HTTPDateheader) corresponds to the midpoint of the latency (latency / 2). This effectively skews the tracked network time into the past by $\Delta / 2$. - Discarded Uncertainty:
NetworkTimeTrackercorrectly calculates a largeuncertaintyvalue for this measurement. However,CertVerifierServiceTimeUpdater::OnNetworkTimeChanged(inchrome/browser/net/cert_verifier_service_time_updater.cc) drops theuncertaintyparameter when forwarding the time state over Mojo.CertVerifierServiceFactoryImpl::UpdateNetworkTimethen initializes its internalTimeTrackerwith a hardcoded zero uncertainty (base::TimeDelta()). - Validation Bypass:
CertVerifyProcBuiltin::VerifyInternaluses this custom, zero-uncertainty network time for certificate validation. Because the time is trusted implicitly, an expired certificate that was valid $\Delta / 2$ ago will be considered currently valid. WhenTryBuildPathsucceeds with the custom time, the verification loop breaks immediately, completely skipping any fallback checks against the true system clock.
Potential Attacker Steps
Note: These steps are based on static code analysis; an automated agent has not executed a live proof-of-concept.
- Intercept Seed Fetch: An on-path attacker monitors for Chrome’s Variations Seed fetch (e.g., to
clients4.google.com). - Trickle Body: The attacker permits the TLS-encrypted headers to reach Chrome immediately but stalls the delivery of the TLS-opaque response body over an extended duration $\Delta$ (e.g., 2 hours) using TCP window manipulation or simple packet delays.
- Skew Time: Once the body transfer completes, Chrome’s internal network time is skewed $\Delta/2$ (1 hour) into the past, with its uncertainty wiped out during Mojo IPC.
- Impersonate Target: Within the next several hours (before the next randomized background time fetch), the attacker MITMs a connection to a target origin (e.g.,
https://target.com). - Bypass Expiration: The attacker presents a certificate for the target origin that expired less than $\Delta/2$ ago. Chrome’s
CertVerifieruses the skewed past-time, finds the certificate valid, and allows the attacker to spoof the origin.
Proposed Fix
- Enforce Timeouts: Call
SetTimeoutDuration()on theSimpleURLLoaderused inVariationsService::DoFetchFromURLto prevent arbitrary latency inflation. - Plumb Uncertainty: Update the
CertVerifierServiceFactory::UpdateNetworkTimeMojo interface to accept theuncertaintyparameter, and ensureCertVerifierServiceTimeUpdaterforwards it. - Enforce Limits: Update
CertVerifyProcBuiltinor the internalTimeTrackerto reject or deprioritize network time if theuncertaintyis excessively large or if the deviation from the system clock is unreasonably high.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.