Commit 303b005f for openh264

commit 303b005fef767a121918ba41c14f791d23937734
Author: BenzhengZhang <140143892+BenzhengZhang@users.noreply.github.com>
Date:   Mon Sep 14 16:07:27 2026 +0800

    bound AVCC SPS/PPS walk against codec-specific data size (#3991)

    * gmp: bound AVCC SPS/PPS walk against codec-specific data size

    InitDecode() validated aCodecSpecificSize only once, as a one-time floor, then walked the AVCC blob's SPS/PPS counts and attacker-influenced 16-bit length fields via readU16BE()/copyWithStartCode() without ever re-checking that walk against the buffer end. Any codec-specific blob of exactly sizeof(GMPVideoCodecH264) bytes (the natural minimum) over-reads: with spsCount==1, readU16BE() reads 2 bytes past the end; with spsCount==0, the ppsCount read itself is 1 byte past the end. ASan-confirmed heap-buffer-overflow READ (CWE-125; root cause CWE-20).

    Track a running end pointer (aCodecSpecific + aCodecSpecificSize) and bound every subsequent read against it: before consuming spsCount/ppsCount, before each readU16BE(), and before each copyWithStartCode(). On any violation, fail closed: log an error and skip SPS/PPS priming instead of continuing with a partially-parsed buffer. Also guard the existing &*annexb.begin() call so DecodeFrame2() is only invoked when annexb is non-empty.

    Well-formed AVCC blobs are parsed identically to before (verified via the reporter's negative-control PoC mode). No public API/ABI change.

    SPARK-843503

    * Clean security comment wording

    * Update gmp-openh264.cpp

    ---------

    Co-authored-by: benzzhan <benzzhan@cisco.com>

diff --git a/module/gmp-openh264.cpp b/module/gmp-openh264.cpp
index 6354f315..940f3ab1 100644
--- a/module/gmp-openh264.cpp
+++ b/module/gmp-openh264.cpp
@@ -876,35 +876,62 @@ class OpenH264VideoDecoder : public GMPVideoDecoder, public RefCounted {
       // Convert the AVCC data, starting at the byte containing
       // numOfSequenceParameterSets, to Annex B format.
       const uint8_t* avcc = aCodecSpecific + offsetof(GMPVideoCodecH264, mAVCC.mNumSPS);
+      // aCodecSpecificSize is only validated as a floor above.
+      // The AVCC SPS/PPS counts and 16-bit length fields must be validated,
+      // so bound every read against the end of the codec-specific buffer; the
+      // walking pointer must never read past the allocation.
+      const uint8_t* const avccEnd = aCodecSpecific + aCodecSpecificSize;
+      bool bAvccValid = true;

       static const int kSPSMask = (1 << 5) - 1;
-      uint8_t spsCount = *avcc++ & kSPSMask;
-      for (int i = 0; i < spsCount; ++i) {
+      uint8_t spsCount = 0;
+      if (avcc < avccEnd) {
+        spsCount = *avcc++ & kSPSMask;
+      } else {
+        bAvccValid = false;
+      }
+      for (int i = 0; bAvccValid && i < spsCount; ++i) {
+        if (avccEnd - avcc < 2) { bAvccValid = false; break; }
         size_t size = readU16BE(avcc);
         avcc += 2;
+        if (size > static_cast<size_t> (avccEnd - avcc)) { bAvccValid = false; break; }
         copyWithStartCode(annexb, avcc, size);
         avcc += size;
       }

-      uint8_t ppsCount = *avcc++;
-      for (int i = 0; i < ppsCount; ++i) {
+      uint8_t ppsCount = 0;
+      if (bAvccValid) {
+        if (avcc < avccEnd) {
+          ppsCount = *avcc++;
+        } else {
+          bAvccValid = false;
+        }
+      }
+      for (int i = 0; bAvccValid && i < ppsCount; ++i) {
+        if (avccEnd - avcc < 2) { bAvccValid = false; break; }
         size_t size = readU16BE(avcc);
         avcc += 2;
+        if (size > static_cast<size_t> (avccEnd - avcc)) { bAvccValid = false; break; }
         copyWithStartCode(annexb, avcc, size);
         avcc += size;
       }

-      SBufferInfo decoded;
-      memset (&decoded, 0, sizeof (decoded));
-      unsigned char* data[3] = {nullptr, nullptr, nullptr};
-      DECODING_STATE dState = decoder_->DecodeFrame2 (&*annexb.begin(),
-                                                      annexb.size(),
-                                                      data,
-                                                      &decoded);
-      if (dState) {
-        GMPLOG (GL_ERROR, "Decoding error dState=" << dState);
+      if (!bAvccValid) {
+        GMPLOG (GL_ERROR, "InitDecode(): malformed AVCC extradata (size "
+                << aCodecSpecificSize << "); skipping SPS/PPS priming");
+      } else if (!annexb.empty()) {
+        SBufferInfo decoded;
+        memset (&decoded, 0, sizeof (decoded));
+        unsigned char* data[3] = {nullptr, nullptr, nullptr};
+        DECODING_STATE dState = decoder_->DecodeFrame2 (&*annexb.begin(),
+                                                        annexb.size(),
+                                                        data,
+                                                        &decoded);
+        if (dState) {
+          GMPLOG (GL_ERROR, "Decoding error dState=" << dState);
+        }
+        GMPLOG (GL_ERROR, "InitDecode iBufferStatus=" << decoded.iBufferStatus);
       }
-      GMPLOG (GL_ERROR, "InitDecode iBufferStatus=" << decoded.iBufferStatus);
     }
   }