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

traeak 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 cee0b3e52d Validate m_frag_offset_count and offset during cache 
unmarshal (#13165)
cee0b3e52d is described below

commit cee0b3e52d4932072545cf9d2575af6c4f5b03ae
Author: Brian Olsen <[email protected]>
AuthorDate: Wed May 20 06:33:54 2026 -0600

    Validate m_frag_offset_count and offset during cache unmarshal (#13165)
    
    * Validate m_frag_offset_count and offset during cache unmarshal
    
    * HTTP.cc: remove unecessary <0 check, add unit tests
    
    * change cache corruption ink_asserts into run time Warnings
    
    * for test warning helper add indicator that variable may be unused
---
 src/proxy/hdrs/HTTP.cc                 | 27 ++++++++--
 src/proxy/hdrs/unit_tests/test_Hdrs.cc | 95 ++++++++++++++++++++++++++++++++++
 2 files changed, 117 insertions(+), 5 deletions(-)

diff --git a/src/proxy/hdrs/HTTP.cc b/src/proxy/hdrs/HTTP.cc
index b67534d6de..f7f80d7117 100644
--- a/src/proxy/hdrs/HTTP.cc
+++ b/src/proxy/hdrs/HTTP.cc
@@ -2212,9 +2212,16 @@ HTTPInfo::unmarshal(char *buf, int len, RefCountObj 
*block_ref)
   len -= HTTP_ALT_MARSHAL_SIZE;
 
   if (alt->m_frag_offset_count > HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS) {
-    alt->m_frag_offsets  = reinterpret_cast<FragOffset *>(buf + 
reinterpret_cast<intptr_t>(alt->m_frag_offsets));
-    len                 -= sizeof(FragOffset) * alt->m_frag_offset_count;
-    ink_assert(len >= 0);
+    // Validate that m_frag_offset_count is sane: the fragment offset table 
must fit within the remaining buffer.
+    int64_t  frag_table_size = static_cast<int64_t>(sizeof(FragOffset)) * 
alt->m_frag_offset_count;
+    intptr_t frag_offset     = reinterpret_cast<intptr_t>(alt->m_frag_offsets);
+
+    if (frag_offset < 0 || len < frag_table_size || 
static_cast<int64_t>(orig_len) - frag_offset < frag_table_size) {
+      Warning("HTTPInfo::unmarshal: m_frag_offset_count or offset exceeds 
buffer - corrupt cache entry");
+      return -1;
+    }
+    alt->m_frag_offsets  = reinterpret_cast<FragOffset *>(buf + frag_offset);
+    len                 -= static_cast<int>(frag_table_size);
   } else if (alt->m_frag_offset_count > 0) {
     alt->m_frag_offsets = alt->m_integral_frag_offsets;
   } else {
@@ -2279,9 +2286,19 @@ HTTPInfo::unmarshal_v24_1(char *buf, int len, 
RefCountObj *block_ref)
   len -= HTTP_ALT_MARSHAL_SIZE;
 
   if (alt->m_frag_offset_count > HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS) {
+    // Validate that m_frag_offset_count is sane before computing sizes.
+    int64_t  frag_table_size = static_cast<int64_t>(sizeof(FragOffset)) * 
alt->m_frag_offset_count;
+    int64_t  extra64         = frag_table_size - 
static_cast<int64_t>(sizeof(alt->m_integral_frag_offsets));
+    intptr_t frag_offset     = reinterpret_cast<intptr_t>(alt->m_frag_offsets);
+
+    if (frag_offset < 0 || len < extra64 || static_cast<int64_t>(orig_len) - 
frag_offset < extra64) {
+      Warning("HTTPInfo::unmarshal_v24_1: m_frag_offset_count or offset 
exceeds buffer - corrupt cache entry");
+      return -1;
+    }
+
     // stuff that didn't fit in the integral slots.
-    int   extra     = sizeof(FragOffset) * alt->m_frag_offset_count - 
sizeof(alt->m_integral_frag_offsets);
-    char *extra_src = buf + reinterpret_cast<intptr_t>(alt->m_frag_offsets);
+    int   extra     = static_cast<int>(extra64);
+    char *extra_src = buf + frag_offset;
     // Actual buffer size, which must be a power of two.
     // Well, technically not, because we never modify an unmarshalled fragment
     // offset table, but it would be a nasty bug should that be done in the
diff --git a/src/proxy/hdrs/unit_tests/test_Hdrs.cc 
b/src/proxy/hdrs/unit_tests/test_Hdrs.cc
index 4e90846edb..093e0fb7b3 100644
--- a/src/proxy/hdrs/unit_tests/test_Hdrs.cc
+++ b/src/proxy/hdrs/unit_tests/test_Hdrs.cc
@@ -44,6 +44,7 @@ using namespace std::literals;
 
 #include "proxy/hdrs/HTTP.h"
 #include "proxy/hdrs/HttpCompat.h"
+#include "tscore/Diags.h"
 
 // replaces test_http_parser_eos_boundary_cases
 TEST_CASE("HdrTestHttpParse", "[proxy][hdrtest]")
@@ -2696,3 +2697,97 @@ TEST_CASE("HdrPromotesOnlyValidHostHeaderMutations", 
"[proxy][hdrtest]")
   http_parser_clear(&parser);
   req_hdr.destroy();
 }
+
+// ---------------------------------------------------------------------------
+// Helpers for HTTPInfo::unmarshal frag-offset bounds-check tests.
+// Builds a minimal marshalled HTTPCacheAlt buffer.  All header-heap pointers
+// are left null so unmarshal() returns after the frag-offset check without
+// requiring a real heap.
+// ---------------------------------------------------------------------------
+static std::vector<char>
+make_marshalled_alt(int frag_offset_count, intptr_t frag_ptr_value)
+{
+  // Ensure diags are initialized so Warning() calls in unmarshal() don't 
crash.
+  [[maybe_unused]] static bool diags_initialized = []() {
+    if (diags() == nullptr) {
+      DiagsPtr::set(new Diags("test_hdrs", nullptr, nullptr, new 
BaseLogFile("stderr")));
+    }
+    return true;
+  }();
+  std::vector<char> buf(sizeof(HTTPCacheAlt) + 4096, 0);
+  auto             *alt    = reinterpret_cast<HTTPCacheAlt *>(buf.data());
+  alt->m_magic             = CacheAltMagic::MARSHALED;
+  alt->m_writeable         = 0;
+  alt->m_unmarshal_len     = -1;
+  alt->m_frag_offset_count = frag_offset_count;
+  // Store the raw offset value in the pointer field (mirrors what marshal() 
does).
+  *reinterpret_cast<intptr_t *>(&alt->m_frag_offsets) = frag_ptr_value;
+  return buf;
+}
+
+TEST_CASE("HTTPInfo::unmarshal frag bounds checks", 
"[proxy][hdrtest][unmarshal]")
+{
+  SECTION("huge frag_offset_count rejected")
+  {
+    auto buf = make_marshalled_alt(INT_MAX, 0);
+    int  len = static_cast<int>(buf.size());
+    CHECK(HTTPInfo::unmarshal(buf.data(), len, nullptr) == -1);
+  }
+
+  SECTION("negative frag_offset pointer value rejected")
+  {
+    // count > N_INTEGRAL_FRAG_OFFSETS but the stored offset is negative
+    auto buf = make_marshalled_alt(5, -1);
+    int  len = static_cast<int>(buf.size());
+    CHECK(HTTPInfo::unmarshal(buf.data(), len, nullptr) == -1);
+  }
+
+  SECTION("frag pointer near INTPTR_MAX rejected (overflow-safe check)")
+  {
+    // Without the subtraction-form check, frag_offset + frag_table_size wraps.
+    auto buf = make_marshalled_alt(5, std::numeric_limits<intptr_t>::max() - 
1);
+    int  len = static_cast<int>(buf.size());
+    CHECK(HTTPInfo::unmarshal(buf.data(), len, nullptr) == -1);
+  }
+
+  SECTION("frag offset beyond orig_len rejected")
+  {
+    auto buf = make_marshalled_alt(5, 0);
+    // Set offset to one byte past the end of the buffer.
+    int  buf_len = static_cast<int>(buf.size());
+    auto buf2    = make_marshalled_alt(5, static_cast<intptr_t>(buf_len + 1));
+    CHECK(HTTPInfo::unmarshal(buf2.data(), buf_len, nullptr) == -1);
+  }
+}
+
+TEST_CASE("HTTPInfo::unmarshal_v24_1 frag bounds checks", 
"[proxy][hdrtest][unmarshal]")
+{
+  SECTION("huge frag_offset_count rejected")
+  {
+    auto buf = make_marshalled_alt(INT_MAX, 0);
+    int  len = static_cast<int>(buf.size());
+    CHECK(HTTPInfo::unmarshal_v24_1(buf.data(), len, nullptr) == -1);
+  }
+
+  SECTION("negative frag_offset pointer value rejected")
+  {
+    auto buf = make_marshalled_alt(5, -1);
+    int  len = static_cast<int>(buf.size());
+    CHECK(HTTPInfo::unmarshal_v24_1(buf.data(), len, nullptr) == -1);
+  }
+
+  SECTION("frag pointer near INTPTR_MAX rejected (overflow-safe check)")
+  {
+    auto buf = make_marshalled_alt(5, std::numeric_limits<intptr_t>::max() - 
1);
+    int  len = static_cast<int>(buf.size());
+    CHECK(HTTPInfo::unmarshal_v24_1(buf.data(), len, nullptr) == -1);
+  }
+
+  SECTION("frag offset beyond orig_len rejected")
+  {
+    auto buf     = make_marshalled_alt(5, 0);
+    int  buf_len = static_cast<int>(buf.size());
+    auto buf2    = make_marshalled_alt(5, static_cast<intptr_t>(buf_len + 1));
+    CHECK(HTTPInfo::unmarshal_v24_1(buf2.data(), buf_len, nullptr) == -1);
+  }
+}

Reply via email to