CVE-2026-79000
Overview
Background
- Device Bound Session Credentials (DBSC)
- a mechanism where a site uses response headers such as
Secure-Session-Registrationto ask the browser to create a session bound to a device key. The browser then keeps and refreshes that session’s cookies over time. - `URLRequestHttpJob::ProcessDeviceBoundSessionsHeader`
- the
netcode, compiled only withBUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS), that passes a response’s DBSC headers to thedevice_bound_sessions::SessionService(HandleResponseHeaders). - `SSLInfo` / `cert_status`
- per-response TLS details.
is_valid()is true only when a certificate is present, andIsCertStatusError(cert_status)is true when the certificate failed verification (for exampleCERT_STATUS_DATE_INVALID). - Certificate-error interstitial bypass
- when a user clicks through a TLS warning, the page loads over a connection whose certificate was not trusted. It should not be treated as a secure context for persistent, origin-scoped state.
Root Cause Analysis
Before the patch, URLRequestHttpJob::ProcessDeviceBoundSessionsHeader() went straight to request_->context()->device_bound_session_service() and handed the response headers to the session service, without checking the TLS state of the connection that delivered them. A response served after the user bypassed a certificate error could therefore register or change a long-lived DBSC session for the origin, even though the server’s identity was never authenticated. The commit message states the violated invariant: bypassing the TLS warning interstitial does not make the connection a secure context for persistent state changes.
The fix adds an early return at the top of the function, if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status)) return;, so DBSC headers are ignored unless the response came over TLS with a certificate that has no error status. The check also means responses without any certificate (!is_valid()) are skipped. That is why the existing unit tests had to give their mock SSL providers a certificate (ok_cert.pem) to keep exercising the service.
ProcessDeviceBoundSessionsHeader() on a present, error-free certificate.Attack Path
- Network-positioned attacker An attacker who can intercept the victim’s traffic presents an invalid or untrusted certificate for a target origin.
- User bypasses the warning
The victim clicks through the certificate-error interstitial, so the response is delivered with
IsCertStatusError(cert_status)true. This user action is required. - Attacker-supplied DBSC header
The attacker’s response includes a
Secure-Session-Registrationheader (as in the regression test). Before the patch,ProcessDeviceBoundSessionsHeader()passed it toSessionService::HandleResponseHeaders. - Persistent session state for the origin The browser could then create a DBSC session for the origin with attacker-chosen parameters, which outlives the compromised connection. This might affect later session refresh or cookie handling for the real site (inferred; the patch shows only that the headers reached the service).
Impact Assessment
ENABLE_DEVICE_BOUND_SESSIONS builds with the feature active, a network position, and the user bypassing the interstitial, and the patch shows no memory-safety impact. The vendor advisory rates this low severity and does not report in-the-wild exploitation; NVD scores it CVSS 4.3.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifnet/url_request/url_request_http_job.cc |
modified |
Files Changed
net/url_request/url_request_http_job.ccnet/url_request/url_request_http_job_unittest.cc
Audit Directions
- Persistent state from click-through connectionsLook at other response-header handlers that write durable per-origin state (for example HSTS-like headers,
Clear-Site-Data, storage access headers,Origin-Trial, reporting endpoints, cookie partition hints) and check they ignore responses withIsCertStatusError(cert_status). - Secure-context checks based on scheme aloneFind code that decides trust with
SchemeIsCryptographic()or the URL scheme without consultingSSLInfo. Anhttps:URL whose certificate error was bypassed passes scheme checks but is not authenticated. - Other DBSC entry pointsCheck that DBSC registration and refresh responses fetched through other paths (refresh requests, fetches from service workers, redirects) apply the same certificate-error gate as
URLRequestHttpJob.
Patch
From 64729847f3d132c2fe56c897efc976ef1fe0b6f6 Mon Sep 17 00:00:00 2001
From: Alex Ilin <alexilin@chromium.org>
Date: Fri, 24 Jul 2026 11:37:21 -0700
Subject: [PATCH] [DSBC] Ignore DBSC headers on certificate errors
Prevents URLRequestHttpJob from processing DBSC headers when the
connection has certificate errors. Bypassing the TLS warning
interstitial does not elevate the connection to a secure context for
persistent state changes.
Fixed: 502514083
TAG=agy
CONV=3f4d55cc-e2fe-4c1f-8566-d7a4e73b6427
Change-Id: I84aaa8bceeb611ecaa97ca9a86c62ba26a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8118566
Reviewed-by: Adam Rice <ricea@chromium.org>
Commit-Queue: Alex Ilin <alexilin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1668041}
---
diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc
index ec9cb75..545d45b2 100644
--- a/net/url_request/url_request_http_job.cc
+++ b/net/url_request/url_request_http_job.cc
@@ -1162,6 +1162,13 @@
#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
void URLRequestHttpJob::ProcessDeviceBoundSessionsHeader() {
+ DCHECK(response_info_);
+ const SSLInfo& ssl_info = response_info_->ssl_info;
+ // Do not process DBSC headers on connections with certificate errors.
+ if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status)) {
+ return;
+ }
+
device_bound_sessions::SessionService* service =
request_->context()->device_bound_session_service();
if (!service) {
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc
index b55e0e65..173edd9 100644
--- a/net/url_request/url_request_http_job_unittest.cc
+++ b/net/url_request/url_request_http_job_unittest.cc
@@ -35,6 +35,7 @@
#include "net/base/proxy_string_util.h"
#include "net/base/request_priority.h"
#include "net/base/test_proxy_delegate.h"
+#include "net/cert/cert_status_flags.h"
#include "net/cert/ct_policy_status.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/cookies/cookie_monster.h"
@@ -1540,6 +1541,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1568,6 +1571,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1636,6 +1641,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1673,6 +1680,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1744,6 +1753,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1801,6 +1812,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1868,6 +1881,8 @@
MockRead(ASYNC, 0)};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1938,6 +1953,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1981,6 +1998,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -2004,6 +2023,50 @@
EXPECT_THAT(delegate_.request_status(), IsOk());
}
+// Verifies that Secure-Session-Registration headers are ignored when the
+// response is served over a connection with certificate errors.
+TEST_F(URLRequestHttpJobWithMockSocketsDeviceBoundSessionServiceTest,
+ RegistrationHeaderIgnoredOnCertError) {
+ const MockWrite writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "User-Agent: \r\n"
+ "Accept-Encoding: gzip, deflate\r\n"
+ "Accept-Language: en-us,fr\r\n\r\n")};
+
+ const MockRead reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Secure-Session-Registration: (ES256);path=\"new\";"
+ "challenge=\"test\"\r\n"
+ "Content-Length: 12\r\n\r\n"),
+ MockRead("Test Content")};
+
+ net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ // Set cert status error.
+ ssl_socket_data_provider.ssl_info.cert_status = CERT_STATUS_DATE_INVALID;
+ socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
+ StaticSocketDataProvider socket_data(reads, writes);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ EXPECT_CALL(GetMockService(), ShouldDefer)
+ .WillRepeatedly(Return(std::nullopt));
+
+ // Verify that ProcessDeviceBoundSessionsHeader() did NOT invoke the
+ // SessionService because of the certificate error.
+ EXPECT_CALL(GetMockService(), HandleResponseHeaders).Times(0);
+
+ request_->Start();
+ delegate_.RunUntilComplete();
+ EXPECT_THAT(delegate_.request_status(), IsOk());
+
+ // Verify that the connection was flagged with a certificate error.
+ EXPECT_TRUE(IsCertStatusError(request_->ssl_info().cert_status));
+}
+
#endif // BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
namespace {
Regression Test / PoC
diff --git a/net/url_request/url_request_http_job_unittest.cc b/net/url_request/url_request_http_job_unittest.cc
index b55e0e65..173edd9 100644
--- a/net/url_request/url_request_http_job_unittest.cc
+++ b/net/url_request/url_request_http_job_unittest.cc
@@ -35,6 +35,7 @@
#include "net/base/proxy_string_util.h"
#include "net/base/request_priority.h"
#include "net/base/test_proxy_delegate.h"
+#include "net/cert/cert_status_flags.h"
#include "net/cert/ct_policy_status.h"
#include "net/cookies/canonical_cookie_test_helpers.h"
#include "net/cookies/cookie_monster.h"
@@ -1540,6 +1541,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1568,6 +1571,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1636,6 +1641,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1673,6 +1680,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1744,6 +1753,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1801,6 +1812,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1868,6 +1881,8 @@
MockRead(ASYNC, 0)};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1938,6 +1953,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -1981,6 +1998,8 @@
MockRead("Test Content")};
net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem");
socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
StaticSocketDataProvider socket_data(reads, writes);
socket_factory_.AddSocketDataProvider(&socket_data);
@@ -2004,6 +2023,50 @@
EXPECT_THAT(delegate_.request_status(), IsOk());
}
+// Verifies that Secure-Session-Registration headers are ignored when the
+// response is served over a connection with certificate errors.
+TEST_F(URLRequestHttpJobWithMockSocketsDeviceBoundSessionServiceTest,
+ RegistrationHeaderIgnoredOnCertError) {
+ const MockWrite writes[] = {
+ MockWrite("GET / HTTP/1.1\r\n"
+ "Host: www.example.com\r\n"
+ "Connection: keep-alive\r\n"
+ "User-Agent: \r\n"
+ "Accept-Encoding: gzip, deflate\r\n"
+ "Accept-Language: en-us,fr\r\n\r\n")};
+
+ const MockRead reads[] = {
+ MockRead("HTTP/1.1 200 OK\r\n"
+ "Accept-Ranges: bytes\r\n"
+ "Secure-Session-Registration: (ES256);path=\"new\";"
+ "challenge=\"test\"\r\n"
+ "Content-Length: 12\r\n\r\n"),
+ MockRead("Test Content")};
+
+ net::SSLSocketDataProvider ssl_socket_data_provider(net::ASYNC, net::OK);
+ ssl_socket_data_provider.ssl_info.cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "expired_cert.pem");
+ // Set cert status error.
+ ssl_socket_data_provider.ssl_info.cert_status = CERT_STATUS_DATE_INVALID;
+ socket_factory_.AddSSLSocketDataProvider(&ssl_socket_data_provider);
+ StaticSocketDataProvider socket_data(reads, writes);
+ socket_factory_.AddSocketDataProvider(&socket_data);
+
+ EXPECT_CALL(GetMockService(), ShouldDefer)
+ .WillRepeatedly(Return(std::nullopt));
+
+ // Verify that ProcessDeviceBoundSessionsHeader() did NOT invoke the
+ // SessionService because of the certificate error.
+ EXPECT_CALL(GetMockService(), HandleResponseHeaders).Times(0);
+
+ request_->Start();
+ delegate_.RunUntilComplete();
+ EXPECT_THAT(delegate_.request_status(), IsOk());
+
+ // Verify that the connection was flagged with a certificate error.
+ EXPECT_TRUE(IsCertStatusError(request_->ssl_info().cert_status));
+}
+
#endif // BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
namespace {