You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by dm...@apache.org on 2026/09/11 08:01:20 UTC

(trafficserver) branch master updated: HTTP/3: bound the DATA payload copy to the frame (#13629)

This is an automated email from the ASF dual-hosted git repository.

brbzull0 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 902a2432f3 HTTP/3: bound the DATA payload copy to the frame (#13629)
902a2432f3 is described below

commit 902a2432f394a09c17c49cb3adb908fc81e9a771
Author: Damian Meden <dm...@apache.org>
AuthorDate: Fri Sep 11 10:01:14 2026 +0200

    HTTP/3: bound the DATA payload copy to the frame (#13629)
    
    Http3FrameDispatcher hands each frame a cloned reader whose size_limit
    is that frame's length, but handle_frame() called
    MIOBuffer::write(reader) with no length. That defaults to INT64_MAX and
    walks the raw block chain, so anything already buffered behind the DATA
    frame -- a following frame header and its payload -- was copied into the
    body too. Pass read_avail(), which honours size_limit.
---
 src/proxy/http3/Http3StreamDataVIOAdaptor.cc      |  9 ++-
 src/proxy/http3/test/test_Http3FrameDispatcher.cc | 72 +++++++++++++++++++++++
 2 files changed, 79 insertions(+), 2 deletions(-)

diff --git a/src/proxy/http3/Http3StreamDataVIOAdaptor.cc b/src/proxy/http3/Http3StreamDataVIOAdaptor.cc
index cdfd1ee374..f59e303e31 100644
--- a/src/proxy/http3/Http3StreamDataVIOAdaptor.cc
+++ b/src/proxy/http3/Http3StreamDataVIOAdaptor.cc
@@ -48,8 +48,13 @@ Http3StreamDataVIOAdaptor::handle_frame(std::shared_ptr<const Http3Frame> frame,
   ink_assert(frame->type() == Http3FrameType::DATA);
   const Http3DataFrame *dframe = dynamic_cast<const Http3DataFrame *>(frame.get());
 
-  // Need to wait for headers to be written
-  int64_t written           = this->_buffer->write(dframe->data());
+  // The frame's reader is bounded to this frame by size_limit, but the default
+  // length for MIOBuffer::write is INT64_MAX, which walks the raw block chain
+  // and copies whatever follows the frame in the stream read buffer. Pass
+  // read_avail(), which honours size_limit, to bound the copy to the payload.
+  IOBufferReader *reader  = dframe->data();
+  int64_t         written = this->_buffer->write(reader, reader->read_avail());
+
   this->_total_data_length += written;
 
   return Http3ErrorUPtr(nullptr);
diff --git a/src/proxy/http3/test/test_Http3FrameDispatcher.cc b/src/proxy/http3/test/test_Http3FrameDispatcher.cc
index e4466ea175..c1cb5920d2 100644
--- a/src/proxy/http3/test/test_Http3FrameDispatcher.cc
+++ b/src/proxy/http3/test/test_Http3FrameDispatcher.cc
@@ -23,8 +23,13 @@
 
 #include <catch2/catch_test_macros.hpp>
 
+#include <array>
+#include <string_view>
+
+#include "iocore/eventsystem/VIO.h"
 #include "proxy/http3/Http3FrameDispatcher.h"
 #include "proxy/http3/Http3ProtocolEnforcer.h"
+#include "proxy/http3/Http3StreamDataVIOAdaptor.h"
 #include "Mock.h"
 
 namespace
@@ -385,6 +390,73 @@ TEST_CASE("ignore unknown frames", "[http3]")
   }
 }
 
+TEST_CASE("Http3StreamDataVIOAdaptor buffers only the current DATA frame payload", "[http3]")
+{
+  // The dispatcher hands each handler a reader whose size_limit covers just
+  // one frame. The adaptor must respect that limit, so the body it collects
+  // is the concatenated payloads and nothing else, no matter how the frames
+  // are split across reads.
+  MIOBuffer      *sink_buffer = new_MIOBuffer(BUFFER_SIZE_INDEX_512);
+  IOBufferReader *sink_reader = sink_buffer->alloc_reader();
+  VIO             sink_vio;
+
+  sink_vio.mutex = new_ProxyMutex();
+  sink_vio.set_writer(sink_buffer);
+
+  Http3FrameDispatcher      http3FrameDispatcher;
+  Http3StreamDataVIOAdaptor adaptor(&sink_vio);
+  http3FrameDispatcher.add_handler(&adaptor);
+
+  MIOBuffer      *buf    = new_MIOBuffer(BUFFER_SIZE_INDEX_512);
+  IOBufferReader *reader = buf->alloc_reader();
+  uint64_t        nread  = 0;
+
+  uint8_t input[] = {// 1st frame (DATA)
+                     0x00, 0x04, 'A', 'A', 'A', 'A',
+                     // 2nd frame (DATA)
+                     0x00, 0x04, 'B', 'B', 'B', 'B'};
+
+  constexpr std::string_view expected_body = "AAAABBBB";
+
+  SECTION("Both frames arrive in a single read")
+  {
+    // The first frame's payload is followed in the same buffer by the second
+    // frame, so an unbounded copy would pull the trailing header and payload
+    // into the body as well.
+    buf->write(input, sizeof(input));
+
+    Http3ErrorUPtr error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::UNKNOWN, *reader, nread);
+    CHECK(!error);
+    CHECK(nread == sizeof(input));
+  }
+
+  SECTION("Frames arrive one byte at a time")
+  {
+    // on_read_ready resets nread on every call, so total it up as we go.
+    uint64_t consumed = 0;
+
+    for (uint8_t *it{input}; it < input + sizeof(input); ++it) {
+      buf->write(it, 1);
+
+      Http3ErrorUPtr error = http3FrameDispatcher.on_read_ready(0, Http3StreamType::UNKNOWN, *reader, nread);
+      CHECK(!error);
+      consumed += nread;
+    }
+    CHECK(consumed == sizeof(input));
+  }
+
+  adaptor.finalize();
+  REQUIRE(sink_reader->read_avail() == static_cast<int64_t>(expected_body.size()));
+
+  std::array<char, expected_body.size()> body;
+
+  sink_reader->memcpy(body.data(), body.size());
+  CHECK(std::string_view(body.data(), body.size()) == expected_body);
+
+  free_MIOBuffer(buf);
+  free_MIOBuffer(sink_buffer);
+}
+
 TEST_CASE("HTTP/2 reserved frame type not allowed", "[http3]")
 {
   SECTION("Reject reserved frame type in non control stream")