Commit 2ab0a534fd for freeswitch.com

commit 2ab0a534fdc25301daf95590e90a756a76bf0950
Author: Dmitry Verenitsin <morbit85@gmail.com>
Date:   Sat Aug 8 22:04:15 2026 +0500

    Merge commit from fork

    In rtmp_rtp2rtmpH264 the STAP-A (type 24) aggregation loop read a
    2-byte NALU length prefix at the end of the payload without ensuring
    both bytes were in bounds, and copied each NALU without checking its
    declared size against the remaining aggregation payload. Stop the loop
    one byte earlier so the length prefix is always present, and skip any
    NALU whose size exceeds the bytes left.

    When building the AVC sequence header, validate the SPS and PPS sizes
    before writing: require at least the 4 SPS profile bytes copied into
    the header, and confirm the fixed framing plus both payloads fit `buf`
    before emitting. Oversized SPS/PPS are logged and skipped instead of
    overrunning the stack buffer.

diff --git a/src/mod/endpoints/mod_rtmp/rtmp_video.c b/src/mod/endpoints/mod_rtmp/rtmp_video.c
index 002d4e890b..3ec75725e6 100644
--- a/src/mod/endpoints/mod_rtmp/rtmp_video.c
+++ b/src/mod/endpoints/mod_rtmp/rtmp_video.c
@@ -448,13 +448,15 @@ switch_status_t rtmp_rtp2rtmpH264(rtp2rtmp_helper_t *helper, switch_frame_t *fra

 		}
 		break;
-	case 24:
-		 {// for aggregated SPS and PPSs
+	case 24: //STAP-A
+		 {/* single-time aggregation packet carrying several NAL units (e.g. SPS and PPS) */
 			uint8_t *q = payload + 1;
 			uint16_t nalu_size = 0;
 			int nt = 0;
 			int nidx = 0;
-			while (nidx < datalen - 1) {
+			/* q spans datalen - 1 bytes; the loop body reads a 2-byte length prefix
+			   (q[nidx] and q[nidx + 1]), so both must be in bounds before entering */
+			while (nidx < datalen - 2) {
 				/* get NALU size */
 				nalu_size = (q[nidx] << 8) | (q[nidx + 1]);

@@ -465,6 +467,13 @@ switch_status_t rtmp_rtp2rtmpH264(rtp2rtmp_helper_t *helper, switch_frame_t *fra
 					continue;
 				}

+				/* declared NALU size must fit the remaining aggregation payload */
+				if (nalu_size > (datalen - 1) - nidx) {
+					switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING,
+						"STAP-A NALU size %u exceeds remaining %d bytes\n", nalu_size, (datalen - 1) - nidx);
+					break;
+				}
+
 				/* write NALU data */
 				nt = q[nidx] & 0x1f;
 				switch (nt) {
@@ -513,44 +522,55 @@ switch_status_t rtmp_rtp2rtmpH264(rtp2rtmp_helper_t *helper, switch_frame_t *fra

 		int i = 0;
 		uint16_t size;
+		uint16_t sps_size = amf0_string_get_size(helper->sps);
+		uint16_t pps_size = amf0_string_get_size(helper->pps);
 		uint8_t *sps = amf0_string_get_uint8_ts(helper->sps);
 		unsigned char buf[AMF_MAX_SIZE * 2]; /* make sure the buffer is big enough */
-
-		buf[i++] = 0x17;   // i = 0
-		buf[i++] = 0;      // 0 for sps/pps packet
-		buf[i++] = 0;      // timestamp
-		buf[i++] = 0;      // timestamp
-		buf[i++] = 0;      // timestamp
-		buf[i++] = 1;      // AVC Decode Configuration Version
-		buf[i++] = sps[1]; // H264 profile 0x42 = Baseline
-		buf[i++] = sps[2]; // Compatiable Level
-		buf[i++] = sps[3]; // H264 profile 0x1e = profile 30, 0x1f = profile 31
-		buf[i++] = 0xff;   // 111111 11   0B11 = 3 = lengthSizeMinusOne, LengtSize = 4
-		buf[i++] = 0xe1;   // i = 10, number of sps = 1
-
-		// 2 bytes sps size
-		size = htons(amf0_string_get_size(helper->sps));
-		memcpy(buf + i, &size, 2);
-		i += 2;
-		// sps data
-		memcpy(buf + i, sps, amf0_string_get_size(helper->sps));
-		buf[i] = 0x67; // set sps header, eyebeam sends 0x27, we set nri = 3, set it to be most important
-		i += amf0_string_get_size(helper->sps);
-
-		buf[i++] = 0x01; // number of pps
-
-		// 2 bytes pps size
-		size = htons(amf0_string_get_size(helper->pps));
-		memcpy(buf + i, &size, 2);
-		i += 2;
-		// pps data
-		memcpy(buf + i, amf0_string_get_uint8_ts(helper->pps), amf0_string_get_size(helper->pps));
-		buf[i] = 0x68; // set pps header
-		i += amf0_string_get_size(helper->pps);
-
-		amf0_data_free(helper->avc_conf);
-		helper->avc_conf = amf0_string_new(buf, i);
-		helper->send_avc = SWITCH_TRUE;
+		/* fixed framing around the payloads: 11 header + 2 sps len + 1 pps count + 2 pps len */
+		const size_t avc_seq_overhead = 11 + 2 + 1 + 2;
+
+		/* header + sps + pps must fit buf; the SPS must also carry the 4 profile
+		   bytes copied into the header below */
+		if (sps_size < 4 || avc_seq_overhead + sps_size + pps_size > sizeof(buf)) {
+			switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_WARNING,
+				"SPS/PPS too large for AVC sequence header (sps=%u pps=%u), skipping\n", sps_size, pps_size);
+		} else {
+			buf[i++] = 0x17;   // i = 0
+			buf[i++] = 0;      // 0 for sps/pps packet
+			buf[i++] = 0;      // timestamp
+			buf[i++] = 0;      // timestamp
+			buf[i++] = 0;      // timestamp
+			buf[i++] = 1;      // AVC Decode Configuration Version
+			buf[i++] = sps[1]; // H264 profile 0x42 = Baseline
+			buf[i++] = sps[2]; // Compatiable Level
+			buf[i++] = sps[3]; // H264 profile 0x1e = profile 30, 0x1f = profile 31
+			buf[i++] = 0xff;   // 111111 11   0B11 = 3 = lengthSizeMinusOne, LengtSize = 4
+			buf[i++] = 0xe1;   // i = 10, number of sps = 1
+
+			// 2 bytes sps size
+			size = htons(sps_size);
+			memcpy(buf + i, &size, 2);
+			i += 2;
+			// sps data
+			memcpy(buf + i, sps, sps_size);
+			buf[i] = 0x67; // set sps header, eyebeam sends 0x27, we set nri = 3, set it to be most important
+			i += sps_size;
+
+			buf[i++] = 0x01; // number of pps
+
+			// 2 bytes pps size
+			size = htons(pps_size);
+			memcpy(buf + i, &size, 2);
+			i += 2;
+			// pps data
+			memcpy(buf + i, amf0_string_get_uint8_ts(helper->pps), pps_size);
+			buf[i] = 0x68; // set pps header
+			i += pps_size;
+
+			amf0_data_free(helper->avc_conf);
+			helper->avc_conf = amf0_string_new(buf, i);
+			helper->send_avc = SWITCH_TRUE;
+		}
 	}

 	if (frame->m) {