Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Network
DescriptionInsufficient validation of untrusted input in Network
ComponentNetwork
Bug ClassLogic Error
Tracker497056412
Fix commit84661918b078 (chromium/src) +36/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
services/network/chunked_data_pipe_upload_data_stream.cc
modified
TEST_F
services/network/chunked_data_pipe_upload_data_stream_unittest.cc
modified
for
services/network/chunked_data_pipe_upload_data_stream_unittest.cc
modified

Files Changed

  • services/network/chunked_data_pipe_upload_data_stream.cc
  • services/network/chunked_data_pipe_upload_data_stream_unittest.cc
From 84661918b078138f76380702748674f9dfc04ee6 Mon Sep 17 00:00:00 2001
From: Matt Menke <[email protected]>
Date: Sun, 03 May 2026 20:44:18 -0700
Subject: [PATCH] ChunkedDataPipeUploadDataStream: Add initialization status code check.

The class was not checking that OnSizeReceived() was being passed a
valid net::Error, which could lead to problems.

Bug: 497056412
Change-Id: Iddc74837dae7b602d4ba1b5ffe6129a5dea887d3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7808287
Commit-Queue: mmenke <[email protected]>
Reviewed-by: Kenichi Ishibashi <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1624483}
---

diff --git a/services/network/chunked_data_pipe_upload_data_stream.cc b/services/network/chunked_data_pipe_upload_data_stream.cc
index a7e4eee..8da5039 100644
--- a/services/network/chunked_data_pipe_upload_data_stream.cc
+++ b/services/network/chunked_data_pipe_upload_data_stream.cc
@@ -12,7 +12,9 @@
 #include "base/numerics/safe_conversions.h"
 #include "base/task/sequenced_task_runner.h"
 #include "mojo/public/c/system/types.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
 
 namespace network {
 
@@ -175,6 +177,12 @@
   DCHECK(!size_);
   DCHECK_EQ(net::OK, status_);
 
+  // `status` must be a final net error.
+  if (status > 0 || status == net::ERR_IO_PENDING) {
+    mojo::ReportBadMessage("Only net::Errors allowed.");
+    status = net::ERR_INVALID_ARGUMENT;
+  }
+
   status_ = status;
   if (status == net::OK) {
     size_ = size;
diff --git a/services/network/chunked_data_pipe_upload_data_stream_unittest.cc b/services/network/chunked_data_pipe_upload_data_stream_unittest.cc
index 981e9f8..de42af44 100644
--- a/services/network/chunked_data_pipe_upload_data_stream_unittest.cc
+++ b/services/network/chunked_data_pipe_upload_data_stream_unittest.cc
@@ -18,11 +18,14 @@
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "net/base/completion_once_callback.h"
 #include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
 #include "net/log/net_log_with_source.h"
+#include "net/test/gtest_util.h"
 #include "services/network/public/cpp/resource_request_body.h"
 #include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
 #include "services/network/test_chunked_data_pipe_getter.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 // Most tests of this class are at the URLLoader layer. These tests focus on
@@ -464,6 +467,31 @@
                                          net::NetLogWithSource()));
 }
 
+// Test the case where GetSize() is passed an invalid error code. Tests both the
+// case of a positive value, and ERR_IO_PENDING.
+TEST_F(ChunkedDataPipeUploadDataStreamTest, GetSizePassedInvalidErrorCode) {
+  const std::string kData = "1234567890";
+
+  const int kTestCases[] = {net::ERR_IO_PENDING, 1};
+
+  for (int test_case : kTestCases) {
+    SCOPED_TRACE(test_case);
+
+    // Initialization succeeds.
+    CreateAndInitChunkedUploadStream();
+
+    // Pass in bad data.
+    std::move(get_size_callback_).Run(test_case, 0);
+
+    net::TestCompletionCallback read_callback;
+    auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(1);
+    // Reading fails.
+    EXPECT_THAT(read_callback.GetResult(chunked_upload_stream_->Read(
+                    io_buffer.get(), 1, read_callback.callback())),
+                net::test::IsError(net::ERR_INVALID_ARGUMENT));
+  }
+}
+
 // Three variations on when the stream can be closed before a request succeeds.
 
 // Stream is closed, then a read attempted, then the GetSizeCallback is invoked.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/services/network/chunked_data_pipe_upload_data_stream_unittest.cc b/services/network/chunked_data_pipe_upload_data_stream_unittest.cc
index 981e9f8..de42af44 100644
--- a/services/network/chunked_data_pipe_upload_data_stream_unittest.cc
+++ b/services/network/chunked_data_pipe_upload_data_stream_unittest.cc
@@ -18,11 +18,14 @@
 #include "mojo/public/cpp/system/data_pipe_utils.h"
 #include "net/base/completion_once_callback.h"
 #include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
 #include "net/base/test_completion_callback.h"
 #include "net/log/net_log_with_source.h"
+#include "net/test/gtest_util.h"
 #include "services/network/public/cpp/resource_request_body.h"
 #include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
 #include "services/network/test_chunked_data_pipe_getter.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 // Most tests of this class are at the URLLoader layer. These tests focus on
@@ -464,6 +467,31 @@
                                          net::NetLogWithSource()));
 }
 
+// Test the case where GetSize() is passed an invalid error code. Tests both the
+// case of a positive value, and ERR_IO_PENDING.
+TEST_F(ChunkedDataPipeUploadDataStreamTest, GetSizePassedInvalidErrorCode) {
+  const std::string kData = "1234567890";
+
+  const int kTestCases[] = {net::ERR_IO_PENDING, 1};
+
+  for (int test_case : kTestCases) {
+    SCOPED_TRACE(test_case);
+
+    // Initialization succeeds.
+    CreateAndInitChunkedUploadStream();
+
+    // Pass in bad data.
+    std::move(get_size_callback_).Run(test_case, 0);
+
+    net::TestCompletionCallback read_callback;
+    auto io_buffer = base::MakeRefCounted<net::IOBufferWithSize>(1);
+    // Reading fails.
+    EXPECT_THAT(read_callback.GetResult(chunked_upload_stream_->Read(
+                    io_buffer.get(), 1, read_callback.callback())),
+                net::test::IsError(net::ERR_INVALID_ARGUMENT));
+  }
+}
+
 // Three variations on when the stream can be closed before a request succeeds.
 
 // Stream is closed, then a read attempted, then the GetSizeCallback is invoked.
Loading diff…

Original Bug Report

reported by [email protected]

Uninitialized heap disclosure in Network process via ChunkedDataPipeUploadDataStream

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A compromised renderer can exfiltrate uninitialized heap memory from the Network process by sending a positive status code in a Mojo GetSize() reply. This tricks the network stack into treating the status as a successful byte count, transmitting up to 16KB of uninitialized buffer data to the attacker’s server. This could leak sensitive cross-origin data such as cookies and authentication headers.

Affected files:

  • services/network/chunked_data_pipe_upload_data_stream.cc
  • net/base/upload_data_stream.cc
  • net/http/http_stream_parser.cc
  • net/spdy/spdy_http_stream.cc
  • services/network/public/mojom/chunked_data_pipe_getter.mojom

Estimated timestamp from git blame: 2024-06-24

Summary

A potential vulnerability exists in ChunkedDataPipeUploadDataStream where an unvalidated Mojo reply can cause the Network process to transmit uninitialized heap memory over the network. A compromised renderer can return a positive status value from ChunkedDataPipeGetter::GetSize(), which the network stack misinterprets as the number of bytes successfully read from an uninitialized upload buffer.

Vulnerability Details

When handling a chunked streaming upload, HttpStreamParser allocates an approximately 16KB SeekableIOBuffer (request_body_read_buf_) to hold chunk data. This buffer is backed by base::HeapArray<uint8_t>::Uninit(), meaning it contains uninitialized heap memory from the Network process.

If the renderer withholds data on the Mojo data pipe, the network read operation pends, and ChunkedDataPipeUploadDataStream stashes the uninitialized buffer in its buf_ member variable.

The ChunkedDataPipeGetter::GetSize() Mojo interface expects a reply of (int32 status, uint64 size). ChunkedDataPipeUploadDataStream::OnSizeReceived handles this reply but fails to validate that status is a valid error code (i.e., <= 0):

void ChunkedDataPipeUploadDataStream::OnSizeReceived(int32_t status, uint64_t size) {
  // ...
  status_ = status; 
  // ...
  if (buf_ && (IsEOF() || status_ != net::OK)) {
    // ...
    OnReadCompleted(status_); // Passes attacker-controlled positive value
  }
}

If a compromised renderer provides a positive status (e.g., 16000), OnReadCompleted is called with that value. In net/base/upload_data_stream.cc, UploadDataStream::OnReadCompleted interprets any positive result as the number of bytes successfully read into the buffer:

void UploadDataStream::OnReadCompleted(int result) {
  if (result > 0) {
    current_position_ += result;
    // ...
  }
  // ... runs callback with result
}

HttpStreamParser then takes result (16000) bytes from the uninitialized request_body_read_buf_, encodes them into an HTTP chunk, and sends the raw Network process heap memory to the destination server.

Potential Reproduction Steps

Note: These are suggested steps; our tooling does not yet run code to provide a working PoC.

  1. From a compromised renderer, initiate a fetch() request to an attacker-controlled server with a streaming body.
  2. Provide a custom ChunkedDataPipeGetter Mojo implementation.
  3. When the Network process attempts to read the upload body, withhold all data from the Mojo data pipe so the read returns net::ERR_IO_PENDING.
  4. Reply to the GetSize() Mojo call with a positive status value, such as 16000 (this must be <= 16372 to avoid crashing the span::first() bounds check inside HttpStreamParser).
  5. Observe that the Network process encodes 16,000 bytes of uninitialized heap memory and transmits it to the attacker’s server.

Proposed Fix

Validate the status parameter in ChunkedDataPipeUploadDataStream::OnSizeReceived. If status > 0, it should be treated as an invalid IPC message (e.g., triggering mojo::ReportBadMessage) and the upload should be aborted with a standard error code like net::ERR_FAILED. Alternatively, add validation directly in the Mojo traits for GetSize to reject positive status codes.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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.

View on issue tracker