From 98f3934c48e6fbc03f29d58827f7cfb03d46c6a0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Sebastian=20Dr=C3=B6ge?= <sebastian@centricular.com>
Date: Fri, 27 Sep 2024 00:12:57 +0300
Subject: [PATCH 07/12] qtdemux: Fix length checks and offsets in stsd entry
 parsing

Thanks to Antonio Morales for finding and reporting the issue.

Fixes GHSL-2024-242
Fixes https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/3845

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/8059>
---
diff -urp gst-plugins-good-1.8.3.orig/gst/isomp4/qtdemux.c gst-plugins-good-1.8.3/gst/isomp4/qtdemux.c
--- gst-plugins-good-1.8.3.orig/gst/isomp4/qtdemux.c	2025-01-02 15:52:51.480255398 -0600
+++ gst-plugins-good-1.8.3/gst/isomp4/qtdemux.c	2025-01-03 10:26:27.872738529 -0600
@@ -9282,37 +9282,36 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
         case FOURCC_avc1:
         case FOURCC_avc3:
         {
-          gint len = QT_UINT32 (stsd_data) - 0x66;
+          guint32 len = QT_UINT32 (stsd_data);
+          len = len <= 0x66 ? 0 : len - 0x66;
           const guint8 *avc_data = stsd_data + 0x66;
 
           /* find avcC */
-          while (len >= 0x8) {
-            gint size;
-
-            if (QT_UINT32 (avc_data) <= len)
-              size = QT_UINT32 (avc_data) - 0x8;
-            else
-              size = len - 0x8;
+          while (len >= 8) {
+            guint32 size = QT_UINT32 (avc_data);
 
-            if (size < 1)
+            if (size < 8 || size > len)
               /* No real data, so break out */
               break;
 
-            switch (QT_FOURCC (avc_data + 0x4)) {
+            switch (QT_FOURCC (avc_data + 4)) {
               case FOURCC_avcC:
               {
                 /* parse, if found */
                 GstBuffer *buf;
 
+                if (size < 8 + 1)
+                  break;
+
                 GST_DEBUG_OBJECT (qtdemux, "found avcC codec_data in stsd");
 
                 /* First 4 bytes are the length of the atom, the next 4 bytes
                  * are the fourcc, the next 1 byte is the version, and the
                  * subsequent bytes are profile_tier_level structure like data. */
                 gst_codec_utils_h264_caps_set_level_and_profile (stream->caps,
-                    avc_data + 8 + 1, size - 1);
-                buf = gst_buffer_new_and_alloc (size);
-                gst_buffer_fill (buf, 0, avc_data + 0x8, size);
+                    avc_data + 8 + 1, size - 8 - 1);
+                buf = gst_buffer_new_and_alloc (size - 8);
+                gst_buffer_fill (buf, 0, avc_data + 8, size - 8);
                 gst_caps_set_simple (stream->caps,
                     "codec_data", GST_TYPE_BUFFER, buf, NULL);
                 gst_buffer_unref (buf);
@@ -9323,6 +9322,9 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
               {
                 GstBuffer *buf;
 
+                if (size < 8 + 40 + 1)
+                  break;
+
                 GST_DEBUG_OBJECT (qtdemux, "found strf codec_data in stsd");
 
                 /* First 4 bytes are the length of the atom, the next 4 bytes
@@ -9330,17 +9332,14 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
                  * next 1 byte is the version, and the
                  * subsequent bytes are sequence parameter set like data. */
 
-                size -= 40;     /* we'll be skipping BITMAPINFOHEADER */
-                if (size > 1) {
-                  gst_codec_utils_h264_caps_set_level_and_profile (stream->caps,
-                      avc_data + 8 + 40 + 1, size - 1);
-
-                  buf = gst_buffer_new_and_alloc (size);
-                  gst_buffer_fill (buf, 0, avc_data + 8 + 40, size);
-                  gst_caps_set_simple (stream->caps,
-                      "codec_data", GST_TYPE_BUFFER, buf, NULL);
-                  gst_buffer_unref (buf);
-                }
+                gst_codec_utils_h264_caps_set_level_and_profile
+                    (stream->caps, avc_data + 8 + 40 + 1, size - 8 - 40 - 1);
+
+                buf = gst_buffer_new_and_alloc (size - 8 - 40);
+                gst_buffer_fill (buf, 0, avc_data + 8 + 40, size - 8 - 40);
+                gst_caps_set_simple (stream->caps,
+                    "codec_data", GST_TYPE_BUFFER, buf, NULL);
+                gst_buffer_unref (buf);
                 break;
               }
               case FOURCC_btrt:
@@ -9348,11 +9347,11 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
                 guint avg_bitrate, max_bitrate;
 
                 /* bufferSizeDB, maxBitrate and avgBitrate - 4 bytes each */
-                if (size < 12)
+                if (size < 8 + 12)
                   break;
 
-                max_bitrate = QT_UINT32 (avc_data + 0xc);
-                avg_bitrate = QT_UINT32 (avc_data + 0x10);
+                  max_bitrate = QT_UINT32 (avc_data + 8 + 4);
+                  avg_bitrate = QT_UINT32 (avc_data + 8 + 8);
 
                 if (!max_bitrate && !avg_bitrate)
                   break;
@@ -9382,8 +9381,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
                 break;
             }
 
-            len -= size + 8;
-            avc_data += size + 8;
+            len -= size;
+            avc_data += size;
           }
 
           break;
@@ -9392,38 +9391,37 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
         case FOURCC_hvc1:
         case FOURCC_hev1:
         {
-          gint len = QT_UINT32 (stsd_data) - 0x66;
+          guint32 len = QT_UINT32 (stsd_data);
+          len = len <= 0x66 ? 0 : len - 0x66;
           const guint8 *hevc_data = stsd_data + 0x66;
 
           /* find hevc */
-          while (len >= 0x8) {
-            gint size;
-
-            if (QT_UINT32 (hevc_data) <= len)
-              size = QT_UINT32 (hevc_data) - 0x8;
-            else
-              size = len - 0x8;
+          while (len >= 8) {
+              guint32 size = QT_UINT32 (hevc_data);
 
-            if (size < 1)
+            if (size < 8 || size > len)
               /* No real data, so break out */
               break;
 
-            switch (QT_FOURCC (hevc_data + 0x4)) {
+            switch (QT_FOURCC (hevc_data + 4)) {
               case FOURCC_hvcC:
               {
                 /* parse, if found */
                 GstBuffer *buf;
 
+                if (size < 8 + 1)
+                  break;
+
                 GST_DEBUG_OBJECT (qtdemux, "found avcC codec_data in stsd");
 
                 /* First 4 bytes are the length of the atom, the next 4 bytes
                  * are the fourcc, the next 1 byte is the version, and the
                  * subsequent bytes are sequence parameter set like data. */
                 gst_codec_utils_h265_caps_set_level_tier_and_profile
-                    (stream->caps, hevc_data + 8 + 1, size - 1);
+                    (stream->caps, hevc_data + 8 + 1, size - 8 - 1);
 
-                buf = gst_buffer_new_and_alloc (size);
-                gst_buffer_fill (buf, 0, hevc_data + 0x8, size);
+                buf = gst_buffer_new_and_alloc (size - 8);
+                gst_buffer_fill (buf, 0, hevc_data + 8, size - 8);
                 gst_caps_set_simple (stream->caps,
                     "codec_data", GST_TYPE_BUFFER, buf, NULL);
                 gst_buffer_unref (buf);
@@ -9432,8 +9430,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
               default:
                 break;
             }
-            len -= size + 8;
-            hevc_data += size + 8;
+            len -= size;
+            hevc_data += size;
           }
           break;
         }
@@ -9733,30 +9731,26 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
         }
         case FOURCC_vc_1:
         {
-          gint len = QT_UINT32 (stsd_data) - 0x66;
+          guint32 len = QT_UINT32 (stsd_data);
+          len = len <= 0x66 ? 0 : len - 0x66;
           const guint8 *vc1_data = stsd_data + 0x66;
 
           /* find dvc1 */
           while (len >= 8) {
-            gint size;
+              guint32 size = QT_UINT32 (vc1_data);
 
-            if (QT_UINT32 (vc1_data) <= len)
-              size = QT_UINT32 (vc1_data) - 8;
-            else
-              size = len - 8;
-
-            if (size < 1)
+            if (size < 8 || size > len)
               /* No real data, so break out */
               break;
 
-            switch (QT_FOURCC (vc1_data + 0x4)) {
+            switch (QT_FOURCC (vc1_data + 4)) {
               case GST_MAKE_FOURCC ('d', 'v', 'c', '1'):
               {
                 GstBuffer *buf;
 
                 GST_DEBUG_OBJECT (qtdemux, "found dvc1 codec_data in stsd");
-                buf = gst_buffer_new_and_alloc (size);
-                gst_buffer_fill (buf, 0, vc1_data + 8, size);
+                  buf = gst_buffer_new_and_alloc (size - 8);
+                  gst_buffer_fill (buf, 0, vc1_data + 8, size - 8);
                 gst_caps_set_simple (stream->caps,
                     "codec_data", GST_TYPE_BUFFER, buf, NULL);
                 gst_buffer_unref (buf);
@@ -9765,8 +9759,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
               default:
                 break;
             }
-            len -= size + 8;
-            vc1_data += size + 8;
+            len -= size;
+            vc1_data += size;
           }
           break;
         }
@@ -10028,7 +10022,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
       }
       case FOURCC_wma_:
       {
-        gint len = QT_UINT32 (stsd_data) - offset;
+        guint32 len = QT_UINT32 (stsd_data);
+        len = len <= offset ? 0 : len - offset;
         const guint8 *wfex_data = stsd_data + offset;
         const gchar *codec_name = NULL;
         gint version = 1;
@@ -10052,14 +10047,9 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
 
         /* find wfex */
         while (len >= 8) {
-          gint size;
-
-          if (QT_UINT32 (wfex_data) <= len)
-            size = QT_UINT32 (wfex_data) - 8;
-          else
-            size = len - 8;
+          guint32 size = QT_UINT32 (wfex_data);
 
-          if (size < 1)
+          if (size < 8 || size > len)
             /* No real data, so break out */
             break;
 
@@ -10106,12 +10096,12 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
                   "width", G_TYPE_INT, wfex.wBitsPerSample,
                   "depth", G_TYPE_INT, wfex.wBitsPerSample, NULL);
 
-              if (size > wfex.cbSize) {
+              if (size > 8 + wfex.cbSize) {
                 GstBuffer *buf;
 
-                buf = gst_buffer_new_and_alloc (size - wfex.cbSize);
+                buf = gst_buffer_new_and_alloc (size - 8 - wfex.cbSize);
                 gst_buffer_fill (buf, 0, wfex_data + 8 + wfex.cbSize,
-                    size - wfex.cbSize);
+                    size - 8 - wfex.cbSize);
                 gst_caps_set_simple (stream->caps,
                     "codec_data", GST_TYPE_BUFFER, buf, NULL);
                 gst_buffer_unref (buf);
@@ -10128,8 +10118,8 @@ qtdemux_parse_trak (GstQTDemux * qtdemux
             default:
               break;
           }
-          len -= size + 8;
-          wfex_data += size + 8;
+          len -= size;
+          wfex_data += size;
         }
         break;
       }
