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


##########
src/iocore/cache/CacheVC.cc:
##########
@@ -318,31 +319,58 @@ 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) {
+  // hlen is unsigned and the walk below is not. Narrowing a header length 
this large would
+  // make the walk negative, skipping it and reporting success on a block 
nothing decoded.
+  if (doc->hlen > static_cast<uint32_t>(std::numeric_limits<int>::max())) {
+    Warning("CacheVC::unmarshal_http_info: header length %u exceeds the 
maximum - corrupt cache entry", doc->hlen);
+    return false;
+  }
+
+  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 >= 
sizeof(HTTPCacheAlt) &&
+                               reinterpret_cast<HTTPCacheAlt 
*>(doc->hdr())->m_magic == CacheAltMagic::MARSHALED;
+
   char *tmp = doc->hdr();
   int   len = doc->hlen;
   while (len > 0) {
+    // The decoders read the alt header before they check any length, so a 
tail too short
+    // to hold one has to be rejected here rather than passed down.
+    if (static_cast<size_t>(len) < sizeof(HTTPCacheAlt)) {
+      Warning("CacheVC::unmarshal_http_info: header block ends mid alternate - 
corrupt cache entry");
+      return false;
+    }
     int r = unmarshal_func(tmp, len, buf.get());
+
     if (r < 0) {
-      ink_assert(!"CacheVC::handleReadDone unmarshal failed");
-      okay = 0;
-      break;
+      ink_assert(!"CacheVC::unmarshal_http_info: HTTPInfo unmarshal failed");
+      return false;
+    }
+    if (needs_wks_fixup) {
+      auto *alt = reinterpret_cast<HTTPCacheAlt *>(tmp);
+      for (HTTPHdr *hdr : {&alt->m_response_hdr, &alt->m_request_hdr}) {
+        if (hdr->valid()) {
+          hdr->m_mime->recompute_accelerators_and_presence_bits();

Review Comment:
   `recompute_accelerators_and_presence_bits()` only rebuilds the MIME field 
indexes. Two more well-known-string indexes are persisted in a marshalled 
header and are not covered here:
   
   - `HTTPHdrImpl::u.req.m_method_wks_idx`
   - `URLImpl::m_scheme_wks_idx` on the request URL
   
   Both matter because the getters prefer the index over the stored string. 
`http_hdr_method_get()` returns `hdrtoken_index_to_wks(m_method_wks_idx)` when 
the index is set, and `URLImpl::get_scheme()` does the same with 
`m_scheme_wks_idx`, falling back to `m_ptr_scheme` only when the index is 
negative. So a stale index does not merely make a lookup miss — it makes the 
cached request report a different method and scheme than the one stored beside 
it, which then feeds alternate selection.
   
   I hit this in #13603 and added `HTTPHdrImpl::recompute_wks_indices()` to 
cover all three in one call. Worth pulling in here even independent of the rest 
of that PR, since the commit message says the fixup repairs "the well known 
string indices persisted in marshalled headers" and today it repairs a subset.



##########
src/iocore/cache/CacheVC.cc:
##########
@@ -318,31 +319,58 @@ 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) {
+  // hlen is unsigned and the walk below is not. Narrowing a header length 
this large would
+  // make the walk negative, skipping it and reporting success on a block 
nothing decoded.
+  if (doc->hlen > static_cast<uint32_t>(std::numeric_limits<int>::max())) {
+    Warning("CacheVC::unmarshal_http_info: header length %u exceeds the 
maximum - corrupt cache entry", doc->hlen);
+    return false;
+  }
+
+  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 >= 
sizeof(HTTPCacheAlt) &&

Review Comment:
   The `version < CACHE_DB_VERSION` gate is right for the problem this PR 
describes, but worth being explicit that it cannot catch the case where the 
writer and reader are the *same* version and their well-known string tables 
differ.
   
   That is the case #13603 is about. The indexes stored here are positions in 
`_hdrtoken_strs` in `proxy/hdrs/HdrToken.cc`, and nothing ties a table revision 
to a cache version, so two builds at the same `CACHE_DB_VERSION` can disagree 
about what index 42 means. That is exactly why the table has been frozen since 
2000 and why #9636 has sat open — changing it is what breaks the stored 
indexes, and a version comparison cannot see it.
   
   In #13603 I dropped the version test and rebuild unconditionally on the 
`MARSHALED` transition. I measured the cost at ~1.3 us for a realistic 11-field 
request plus 11-field response, against a path that has just done a disk read 
or a decompression, so under 1% there and zero on an uncompressed RAM hit.
   
   No change needed for this PR to be correct on its own terms. Flagging it so 
the gate does not read as sufficient for all stale-index cases, and so whoever 
rebases second knows this line is the one that has to change.



##########
src/iocore/cache/CacheVC.cc:
##########
@@ -318,31 +319,58 @@ 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) {
+  // hlen is unsigned and the walk below is not. Narrowing a header length 
this large would
+  // make the walk negative, skipping it and reporting success on a block 
nothing decoded.
+  if (doc->hlen > static_cast<uint32_t>(std::numeric_limits<int>::max())) {
+    Warning("CacheVC::unmarshal_http_info: header length %u exceeds the 
maximum - corrupt cache entry", doc->hlen);
+    return false;
+  }
+
+  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 >= 
sizeof(HTTPCacheAlt) &&
+                               reinterpret_cast<HTTPCacheAlt 
*>(doc->hdr())->m_magic == CacheAltMagic::MARSHALED;
+
   char *tmp = doc->hdr();
   int   len = doc->hlen;
   while (len > 0) {
+    // The decoders read the alt header before they check any length, so a 
tail too short
+    // to hold one has to be rejected here rather than passed down.
+    if (static_cast<size_t>(len) < sizeof(HTTPCacheAlt)) {
+      Warning("CacheVC::unmarshal_http_info: header block ends mid alternate - 
corrupt cache entry");
+      return false;
+    }
     int r = unmarshal_func(tmp, len, buf.get());
+
     if (r < 0) {
-      ink_assert(!"CacheVC::handleReadDone unmarshal failed");
-      okay = 0;
-      break;
+      ink_assert(!"CacheVC::unmarshal_http_info: HTTPInfo unmarshal failed");
+      return false;
+    }
+    if (needs_wks_fixup) {
+      auto *alt = reinterpret_cast<HTTPCacheAlt *>(tmp);
+      for (HTTPHdr *hdr : {&alt->m_response_hdr, &alt->m_request_hdr}) {
+        if (hdr->valid()) {

Review Comment:
   Checking `hdr->valid()` here is the right guard, and stricter than what I 
wrote in #13603 — I only tested `m_heap`. Worth noting for anyone reading later 
why it is needed at all: `HTTPInfo::marshal()` memcpys the whole live alt, so 
`m_http` in the marshalled bytes holds a pointer from the *writing* process, 
and `unmarshal` only overwrites it when the corresponding heap is non-null. 
`valid()` requires all three of `m_http`, `m_mime` and `m_heap`, so it 
correctly rejects that leftover.



-- 
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