Copilot commented on code in PR #13637:
URL: https://github.com/apache/trafficserver/pull/13637#discussion_r3933401541


##########
src/iocore/cache/CacheVC.cc:
##########
@@ -318,31 +318,45 @@ CacheVC::dead(int /* event ATS_UNUSED */, Event * /*e 
ATS_UNUSED */)
   return EVENT_DONE;
 }
 
-static void
-unmarshal_helper(Doc *doc, Ptr<IOBufferData> &buf, int &okay)
+bool
+CacheVC::unmarshal_http_info(Doc *doc, Ptr<IOBufferData> &buf)
 {
   using UnmarshalFunc              = int(char *buf, int len, RefCountObj 
*block_ref);
   UnmarshalFunc    *unmarshal_func = &HTTPInfo::unmarshal;
   ts::VersionNumber version(doc->v_major, doc->v_minor);
 
-  // introduced by https://github.com/apache/trafficserver/pull/4874, this is 
used to distinguish the doc version
-  // before and after #4847
-  if (version < CACHE_DB_VERSION) {
+  if (version < CACHE_DB_VERSION_HTTPINFO_V24_2) {
     unmarshal_func = &HTTPInfo::unmarshal_v24_1;
   }
 
+  // Objects written by an older version can carry stale well known string 
indices and
+  // presence bits. Repair them only on the MARSHALED to ALIVE transition, 
since an already
+  // ALIVE block may be shared with other readers. All alts of a doc 
transition together, so
+  // the first one answers for the whole header block.
+  bool const needs_wks_fixup = version < CACHE_DB_VERSION && doc->hlen > 0 &&
+                               reinterpret_cast<HTTPCacheAlt 
*>(doc->hdr())->m_magic == CacheAltMagic::MARSHALED;
+
   char *tmp = doc->hdr();
   int   len = doc->hlen;

Review Comment:
   `unmarshal_http_info()` walks the header block with `while (len > 0)` and 
calls `HTTPInfo::unmarshal*()`, which assumes at least a full marshalled-alt 
header is available. If `doc->hlen` is truncated/corrupt (non-zero but smaller 
than the marshalled header), this can read past the declared header block 
and/or leave a partially-unmarshalled alt in-place. Consider guarding on the 
marshalled-alt size (same rounding as `HTTP_ALT_MARSHAL_SIZE`) and treating any 
leftover bytes as failure.



##########
src/iocore/cache/unit_tests/test_Unmarshal_Compat.cc:
##########
@@ -0,0 +1,249 @@
+/** @file
+
+  Version compatibility tests for CacheVC::unmarshal_http_info.
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+#include "main.h"
+
+#include "../P_CacheDoc.h"
+#include "iocore/cache/CacheDefs.h"
+#include "proxy/hdrs/HdrHeap.h"
+#include "tscore/ink_memory.h"
+
+#include <cstring>
+#include <vector>
+
+int  cache_vols           = 1;
+bool reuse_existing_cache = false;
+
+namespace
+{
+int constexpr ALT_MARSHAL_SIZE = 
HdrHeapMarshalBlocks{swoc::round_up(sizeof(HTTPCacheAlt))};
+int constexpr N_INTEGRAL       = HTTPCacheAlt::N_INTEGRAL_FRAG_OFFSETS;
+int constexpr FRAG_COUNT       = N_INTEGRAL + 2;
+
+using FragOffset = HTTPInfo::FragOffset;
+
+/** A Doc followed by a header block, versioned as an on-disk object would be. 
*/
+class DocBuffer
+{
+public:
+  DocBuffer(uint8_t major, uint8_t minor, int hlen) : _storage((sizeof(Doc) + 
hlen + 7) / sizeof(uint64_t) + 1, 0)
+  {
+    Doc *d = this->doc();
+
+    d->magic    = DOC_MAGIC;
+    d->doc_type = CACHE_FRAG_TYPE_HTTP;
+    d->v_major  = major;
+    d->v_minor  = minor;
+    d->hlen     = hlen;
+    d->len      = sizeof(Doc) + hlen;
+  }
+
+  Doc *
+  doc()
+  {
+    return reinterpret_cast<Doc *>(_storage.data());
+  }
+
+  HTTPCacheAlt *
+  alt()
+  {
+    return reinterpret_cast<HTTPCacheAlt *>(this->doc()->hdr());
+  }
+
+private:
+  std::vector<uint64_t> _storage;
+};
+
+/** Initialize the alt header the way HTTPInfo::marshal leaves it, with no 
header heaps. */
+void
+init_marshalled_alt(HTTPCacheAlt *alt, int frag_count, intptr_t 
frag_table_offset)
+{
+  alt->m_magic             = CacheAltMagic::MARSHALED;
+  alt->m_writeable         = 0;
+  alt->m_unmarshal_len     = -1;
+  alt->m_frag_offset_count = frag_count;
+
+  *reinterpret_cast<intptr_t *>(&alt->m_frag_offsets) = frag_table_offset;
+}
+
+/** The 24.2 layout: the whole fragment offset table follows the alt. */
+DocBuffer
+make_v24_2_doc(uint8_t major, uint8_t minor)
+{
+  DocBuffer buffer{major, minor, static_cast<int>(ALT_MARSHAL_SIZE + 
FRAG_COUNT * sizeof(FragOffset))};
+
+  init_marshalled_alt(buffer.alt(), FRAG_COUNT, ALT_MARSHAL_SIZE);
+
+  auto *table = reinterpret_cast<FragOffset *>(buffer.doc()->hdr() + 
ALT_MARSHAL_SIZE);
+
+  for (int i = 0; i < FRAG_COUNT; ++i) {
+    table[i] = i;
+  }
+  return buffer;
+}
+
+/** The 24.1 layout: the first N_INTEGRAL offsets are inline, only the rest 
follow. */
+DocBuffer
+make_v24_1_doc(uint8_t major, uint8_t minor)
+{
+  int constexpr extra = FRAG_COUNT - N_INTEGRAL;
+  DocBuffer buffer{major, minor, static_cast<int>(ALT_MARSHAL_SIZE + extra * 
sizeof(FragOffset))};
+
+  init_marshalled_alt(buffer.alt(), FRAG_COUNT, ALT_MARSHAL_SIZE);
+
+  for (int i = 0; i < N_INTEGRAL; ++i) {
+    buffer.alt()->m_integral_frag_offsets[i] = i;
+  }
+
+  auto *table = reinterpret_cast<FragOffset *>(buffer.doc()->hdr() + 
ALT_MARSHAL_SIZE);
+
+  for (int i = 0; i < extra; ++i) {
+    table[i] = N_INTEGRAL + i;
+  }
+  return buffer;
+}
+
+void
+check_offsets_are_sequential(HTTPCacheAlt *alt)
+{
+  REQUIRE(alt->m_frag_offsets != nullptr);
+  for (int i = 0; i < FRAG_COUNT; ++i) {
+    CHECK(alt->m_frag_offsets[i] == static_cast<FragOffset>(i));
+  }
+}
+
+/** unmarshal_v24_1 copies the table onto the heap; the caller owns it. */
+void
+free_frag_offsets(HTTPCacheAlt *alt)
+{
+  if (alt->m_frag_offsets != nullptr && alt->m_frag_offsets != 
alt->m_integral_frag_offsets) {
+    ats_free(alt->m_frag_offsets);
+    alt->m_frag_offsets = nullptr;
+  }
+}
+
+} // end anonymous namespace
+
+TEST_CASE("unmarshal_http_info selects the decoder matching the object 
version", "[cache][unmarshal][compat]")
+{
+  Ptr<IOBufferData> buf;
+
+  SECTION("an object at the layout boundary is read with the current decoder")
+  {
+    DocBuffer doc{make_v24_2_doc(CACHE_DB_VERSION_HTTPINFO_V24_2._major, 
CACHE_DB_VERSION_HTTPINFO_V24_2._minor)};
+
+    REQUIRE(CacheVC::unmarshal_http_info(doc.doc(), buf));
+    check_offsets_are_sequential(doc.alt());
+    // Only the current decoder leaves the table in the buffer; the 24.1 one 
copies it out,
+    // so this is what says which of the two ran.
+    CHECK(reinterpret_cast<char *>(doc.alt()->m_frag_offsets) == 
doc.doc()->hdr() + ALT_MARSHAL_SIZE);
+  }
+
+  SECTION("an object below the layout boundary is read with the 24.1 decoder")
+  {
+    DocBuffer doc{make_v24_1_doc(CACHE_DB_VERSION_HTTPINFO_V24_2._major, 
CACHE_DB_VERSION_HTTPINFO_V24_2._minor - 1)};
+
+    // The current decoder would reject this layout outright: it expects the 
whole table
+    // to follow the alt, and only the offsets past the integral ones are 
there.
+    REQUIRE(CacheVC::unmarshal_http_info(doc.doc(), buf));
+    check_offsets_are_sequential(doc.alt());
+    CHECK(reinterpret_cast<char *>(doc.alt()->m_frag_offsets) != 
doc.doc()->hdr() + ALT_MARSHAL_SIZE);
+    free_frag_offsets(doc.alt());
+  }
+}
+
+TEST_CASE("unmarshal_http_info repairs stale accelerators only when it owns 
the block", "[cache][unmarshal][compat]")
+{
+  HTTPInfo info;
+
+  info.create();
+  build_hdrs(info, "http://www.example.com/test.html";);
+
+  int const             hlen = info.marshal_length();
+  std::vector<uint64_t> marshalled(hlen / sizeof(uint64_t) + 1, 0);
+
+  REQUIRE(info.marshal(reinterpret_cast<char *>(marshalled.data()), hlen) == 
hlen);
+
+  auto load = [&](DocBuffer &doc) { memcpy(doc.doc()->hdr(), 
marshalled.data(), hlen); };
+
+  // Unmarshal an untouched copy to learn where the response MIME header lands 
and what
+  // its presence bits should be. Every copy below is byte identical, so the 
offset holds.
+  ptrdiff_t mime_offset  = 0;
+  uint64_t  correct_bits = 0;
+  {
+    DocBuffer         doc{CACHE_DB_MAJOR_VERSION, CACHE_DB_MINOR_VERSION, 
hlen};
+    Ptr<IOBufferData> buf;
+
+    load(doc);
+    REQUIRE(CacheVC::unmarshal_http_info(doc.doc(), buf));
+    REQUIRE(doc.alt()->m_response_hdr.valid());
+
+    mime_offset  = reinterpret_cast<char *>(doc.alt()->m_response_hdr.m_mime) 
- doc.doc()->hdr();
+    correct_bits = doc.alt()->m_response_hdr.m_mime->m_presence_bits;
+    REQUIRE(correct_bits != 0);
+  }
+
+  auto corrupt_presence_bits = [&](DocBuffer &doc) {
+    reinterpret_cast<MIMEHdrImpl *>(doc.doc()->hdr() + 
mime_offset)->m_presence_bits = 0;
+  };
+
+  SECTION("an older object is repaired")
+  {
+    DocBuffer         doc{CACHE_DB_MAJOR_VERSION, CACHE_DB_MINOR_VERSION - 1, 
hlen};

Review Comment:
   `CACHE_DB_MINOR_VERSION` is a `uint8_t`; `CACHE_DB_MINOR_VERSION - 1` will 
underflow if the project ever bumps the cache DB major and resets minor to 0 
(e.g. 25.0), making this test version "newer" than `CACHE_DB_VERSION`. Consider 
computing a previous-version pair without unsigned underflow.
   
   This issue also appears on line 236 of the same file.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to