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