CVE-2021-32439: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow') (CWE-120)
value: Buffer overflow in the stbl_AppendSize function in MP4Box in GPAC 1.0.1 allows attackers to cause a denial of service or execute arbitrary code via a crafted file.
CWE description: The product copies an input buffer to an output buffer without verifying that the size of the input buffer is less than the size of the output buffer, leading to a buffer overflow.
CVSS v3 base score: 7.8
Published: 2021-08-11T20:15Z
Repository: https://github.com/gpac/gpac
Language: C
Files changed: src/isomedia/stbl_write.c
Commit date: 2021-04-30 11:30:41 +0200
Commit message: fixed #1774 (fuzz)

Diff: diff --git a/src/isomedia/stbl_write.c b/src/isomedia/stbl_write.c
index 843a30857a8..1e4780d657f 100644
--- a/src/isomedia/stbl_write.c
+++ b/src/isomedia/stbl_write.c
@@ -35,15 +35,24 @@ gf_realloc low, which greatly impacts performances for large files*/
 		a = new_a;\
 	}
 
 #ifndef GPAC_DISABLE_ISOM_WRITE
 
+
+#define CHECK_PACK(_e) \
+	if (!nb_pack) nb_pack = 1; \
+	else if ((s32) nb_pack < 0) { \
+		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Too many samples %u in packed sample\n", nb_pack)); \
+		return _e; \
+	}
+
+
 //adds a DTS in the table and get the sample number of this new sample
 //we could return an error if a sample with the same DTS already exists
 //but this is not true for QT or MJ2K, only for MP4...
 //we assume the authoring tool tries to create a compliant MP4 file.
-GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 LastAUDefDuration, u32 nb_packed_samples)
+GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 LastAUDefDuration, u32 nb_pack)
 {
 	u32 i, j, sampNum;
 	u64 *DTSs, curDTS;
 	Bool inserted;
 	GF_SttsEntry *ent;
@@ -52,25 +61,26 @@ GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 Last
 
 	//reset the reading cache when adding a sample
 	stts->r_FirstSampleInEntry = 0;
 
 	*sampleNumber = 0;
-	if (!nb_packed_samples)
-		nb_packed_samples=1;
+
+	CHECK_PACK(GF_BAD_PARAM)
+
 
 	//if we don't have an entry, that's the first one...
 	if (!stts->nb_entries) {
 		//assert the first DTS is 0. If not, that will break the whole file
 		if (DTS) return GF_BAD_PARAM;
 		stts->alloc_size = 1;
 		stts->nb_entries = 1;
 		stts->entries = gf_malloc(sizeof(GF_SttsEntry));
 		if (!stts->entries) return GF_OUT_OF_MEM;
-		stts->entries[0].sampleCount = nb_packed_samples;
-		stts->entries[0].sampleDelta = (nb_packed_samples>1) ? 0 : LastAUDefDuration;
+		stts->entries[0].sampleCount = nb_pack;
+		stts->entries[0].sampleDelta = (nb_pack>1) ? 0 : LastAUDefDuration;
 		(*sampleNumber) = 1;
-		stts->w_currentSampleNum = nb_packed_samples;
+		stts->w_currentSampleNum = nb_pack;
 		return GF_OK;
 	}
 	//check the last DTS - we allow 0-duration samples (same DTS)
 	if (DTS >= stts->w_LastDTS) {
 		u32 nb_extra = 0;
@@ -81,16 +91,16 @@ GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 Last
 		}
 		//OK, we're adding at the end
 		if ((DTS == stts->w_LastDTS + ent->sampleDelta)
 			//for raw audio, consider (dts==last_dts) and (dts==last_dts+2*delta) as sample append to cope with
 			//timescale vs samplerate precision
-			|| ((nb_packed_samples>1) && ((DTS == stts->w_LastDTS) || (DTS == stts->w_LastDTS + 2*ent->sampleDelta) ))
+			|| ((nb_pack>1) && ((DTS == stts->w_LastDTS) || (DTS == stts->w_LastDTS + 2*ent->sampleDelta) ))
 		) {
 			(*sampleNumber) = stts->w_currentSampleNum + 1;
-			ent->sampleCount += nb_packed_samples;
-			stts->w_currentSampleNum += nb_packed_samples;
-			stts->w_LastDTS = DTS + ent->sampleDelta * (nb_packed_samples-1);
+			ent->sampleCount += nb_pack;
+			stts->w_currentSampleNum += nb_pack;
+			stts->w_LastDTS = DTS + ent->sampleDelta * (nb_pack-1);
 			return GF_OK;
 		}
 		//we need to split the entry
 		if (ent->sampleCount == 1) {
 			//FIXME - we need more tests with timed text
@@ -116,11 +126,11 @@ GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 Last
 			return GF_OK;
 		}
 		//we definitely need to split the entry ;)
 		ent->sampleCount --;
 
-		if (nb_packed_samples>1)
+		if (nb_pack>1)
 			nb_extra = 1;
 
 		if (stts->alloc_size <= stts->nb_entries + nb_extra) {
 			ALLOC_INC(stts->alloc_size);
 			stts->entries = gf_realloc(stts->entries, sizeof(GF_SttsEntry)*stts->alloc_size);
@@ -132,11 +142,11 @@ GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 Last
 			nb_extra = stts->entries[stts->nb_entries-1].sampleDelta;
 
 		ent = &stts->entries[stts->nb_entries];
 		stts->nb_entries++;
 
-		if (nb_packed_samples==1) {
+		if (nb_pack==1) {
 			ent->sampleCount = 2;
 			ent->sampleDelta = (u32) (DTS - stts->w_LastDTS);
 			stts->w_LastDTS = DTS;
 			(*sampleNumber) = stts->w_currentSampleNum+1;
 			stts->w_currentSampleNum += 1;
@@ -147,15 +157,15 @@ GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 Last
 		ent->sampleDelta = (u32) (DTS - stts->w_LastDTS);
 
 		ent = &stts->entries[stts->nb_entries];
 		stts->nb_entries++;
 
-		ent->sampleCount = nb_packed_samples;
+		ent->sampleCount = nb_pack;
 		ent->sampleDelta = nb_extra;
 		stts->w_LastDTS = DTS;
 		(*sampleNumber) = stts->w_currentSampleNum + 1;
-		stts->w_currentSampleNum += nb_packed_samples;
+		stts->w_currentSampleNum += nb_pack;
 		return GF_OK;
 	}
 
 
 	//unpack the DTSs and locate new sample...
@@ -391,36 +401,38 @@ GF_Err stbl_unpackCTS(GF_SampleTableBox *stbl)
 	}
 	return GF_OK;
 }
 
 //add size
-GF_Err stbl_AddSize(GF_SampleSizeBox *stsz, u32 sampleNumber, u32 size, u32 nb_pack_samples)
+GF_Err stbl_AddSize(GF_SampleSizeBox *stsz, u32 sampleNumber, u32 size, u32 nb_pack)
 {
 	u32 i, k;
 	u32 *newSizes;
 	if (!stsz /*|| !size */ || !sampleNumber) return GF_BAD_PARAM;
 
 	if (sampleNumber > stsz->sampleCount + 1) return GF_BAD_PARAM;
 
-	if (!nb_pack_samples) nb_pack_samples = 1;
-	else if (nb_pack_samples>1)
-		size /= nb_pack_samples;
+	CHECK_PACK(GF_BAD_PARAM)
+
+	if (nb_pack>1)
+		size /= nb_pack;
+
 
 	//all samples have the same size
 	if (stsz->sizes == NULL) {
 		//1 first sample added in NON COMPACT MODE
 		if (! stsz->sampleCount && (stsz->type != GF_ISOM_BOX_TYPE_STZ2) ) {
-			stsz->sampleCount = nb_pack_samples;
+			stsz->sampleCount = nb_pack;
 			stsz->sampleSize = size;
 			return GF_OK;
 		}
 		//2- sample has the same size
 		if (stsz->sampleSize == size) {
-			stsz->sampleCount += nb_pack_samples;
+			stsz->sampleCount += nb_pack;
 			return GF_OK;
 		}
-		if (nb_pack_samples>1) {
+		if (nb_pack>1) {
 			GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Inserting packed samples with different sizes is not yet supported\n" ));
 			return GF_NOT_SUPPORTED;
 		}
 		//3- no, need to alloc a size table
 		stsz->sizes = (u32*)gf_malloc(sizeof(u32) * (stsz->sampleCount + 1));
@@ -696,11 +708,11 @@ GF_Err stbl_AddShadow(GF_ShadowSyncBox *stsh, u32 sampleNumber, u32 shadowNumber
 		return gf_list_insert(stsh->entries, ent, i ? i-1 : 0);
 	}
 }
 
 //used in edit/write, where sampleNumber == chunkNumber
-GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIndex, u64 offset, u32 nb_pack_samples)
+GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIndex, u64 offset, u32 nb_pack)
 {
 	GF_SampleTableBox *stbl;
 	GF_ChunkOffsetBox *stco;
 	GF_SampleToChunkBox *stsc;
 	GF_ChunkLargeOffsetBox *co64;
@@ -711,12 +723,11 @@ GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIn
 
 	stbl = mdia->information->sampleTable;
 	stsc = stbl->SampleToChunk;
 
 //	if (stsc->w_lastSampleNumber + 1 < sampleNumber ) return GF_BAD_PARAM;
-	if (!nb_pack_samples)
-		nb_pack_samples = 1;
+	CHECK_PACK(GF_BAD_PARAM)
 
 	if (!stsc->nb_entries || (stsc->nb_entries + 2 >= stsc->alloc_size)) {
 		if (!stsc->alloc_size) stsc->alloc_size = 1;
 		ALLOC_INC(stsc->alloc_size);
 		stsc->entries = gf_realloc(stsc->entries, sizeof(GF_StscEntry)*stsc->alloc_size);
@@ -728,11 +739,11 @@ GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIn
 		stsc->w_lastChunkNumber ++;
 		ent->firstChunk = stsc->w_lastChunkNumber;
 		if (stsc->nb_entries) stsc->entries[stsc->nb_entries-1].nextChunk = stsc->w_lastChunkNumber;
 
 		new_chunk_idx = stsc->w_lastChunkNumber;
-		stsc->w_lastSampleNumber = sampleNumber + nb_pack_samples-1;
+		stsc->w_lastSampleNumber = sampleNumber + nb_pack-1;
 		stsc->nb_entries += 1;
 	} else {
 		u32 cur_samp = 1;
 		u32 samples_in_next_entry = 0;
 		u32 next_entry_first_chunk = 1;
@@ -783,15 +794,15 @@ GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIn
 		}
 		new_chunk_idx = next_entry_first_chunk;
 	}
 	ent->isEdited = (Media_IsSelfContained(mdia, StreamDescIndex)) ? 1 : 0;
 	ent->sampleDescriptionIndex = StreamDescIndex;
-	ent->samplesPerChunk = nb_pack_samples;
+	ent->samplesPerChunk = nb_pack;
 	ent->nextChunk = ent->firstChunk+1;
 
 	//OK, now if we've inserted a chunk, update the sample to chunk info...
-	if (sampleNumber + nb_pack_samples - 1 == stsc->w_lastSampleNumber) {
+	if (sampleNumber + nb_pack - 1 == stsc->w_lastSampleNumber) {
 		if (stsc->nb_entries)
 			stsc->entries[stsc->nb_entries-1].nextChunk = ent->firstChunk;
 
 		stbl->SampleToChunk->currentIndex = stsc->nb_entries-1;
 		stbl->SampleToChunk->firstSampleInCurrentChunk = sampleNumber;
@@ -1594,11 +1605,12 @@ GF_Err stbl_SampleSizeAppend(GF_SampleSizeBox *stsz, u32 data_size)
 
 GF_Err stbl_AppendTime(GF_SampleTableBox *stbl, u32 duration, u32 nb_pack)
 {
 	GF_TimeToSampleBox *stts = stbl->TimeToSample;
 
-	if (!nb_pack) nb_pack = 1;
+	CHECK_PACK(GF_ISOM_INVALID_FILE)
+
 	if (stts->nb_entries) {
 		if (stts->entries[stts->nb_entries-1].sampleDelta == duration) {
 			stts->entries[stts->nb_entries-1].sampleCount += nb_pack;
 			return GF_OK;
 		}
@@ -1617,26 +1629,28 @@ GF_Err stbl_AppendTime(GF_SampleTableBox *stbl, u32 duration, u32 nb_pack)
 }
 
 GF_Err stbl_AppendSize(GF_SampleTableBox *stbl, u32 size, u32 nb_pack)
 {
 	u32 i;
-	if (!nb_pack) nb_pack = 1;
+	CHECK_PACK(GF_ISOM_INVALID_FILE)
 
 	if (!stbl->SampleSize->sampleCount) {
 		stbl->SampleSize->sampleSize = size;
 		stbl->SampleSize->sampleCount += nb_pack;
 		return GF_OK;
 	}
 	if (stbl->SampleSize->sampleSize && (stbl->SampleSize->sampleSize==size)) {
 		stbl->SampleSize->sampleCount += nb_pack;
 		return GF_OK;
 	}
+
 	if (!stbl->SampleSize->sizes || (stbl->SampleSize->sampleCount+nb_pack > stbl->SampleSize->alloc_size)) {
 		Bool init_table = (stbl->SampleSize->sizes==NULL) ? 1 : 0;
 		ALLOC_INC(stbl->SampleSize->alloc_size);
-		if (stbl->SampleSize->sampleCount+nb_pack > stbl->SampleSize->alloc_size)
+		if (stbl->SampleSize->sampleCount+nb_pack > stbl->SampleSize->alloc_size) {
 			stbl->SampleSize->alloc_size = stbl->SampleSize->sampleCount+nb_pack;
+		}
 
 		stbl->SampleSize->sizes = (u32 *)gf_realloc(stbl->SampleSize->sizes, sizeof(u32)*stbl->SampleSize->alloc_size);
 		if (!stbl->SampleSize->sizes) return GF_OUT_OF_MEM;
 		memset(&stbl->SampleSize->sizes[stbl->SampleSize->sampleCount], 0, sizeof(u32) * (stbl->SampleSize->alloc_size - stbl->SampleSize->sampleCount) );
 

Vulnerable code: GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 LastAUDefDuration, u32 nb_packed_samples)
	if (!nb_packed_samples)
		nb_packed_samples=1;
		stts->entries[0].sampleCount = nb_packed_samples;
		stts->entries[0].sampleDelta = (nb_packed_samples>1) ? 0 : LastAUDefDuration;
		stts->w_currentSampleNum = nb_packed_samples;
			|| ((nb_packed_samples>1) && ((DTS == stts->w_LastDTS) || (DTS == stts->w_LastDTS + 2*ent->sampleDelta) ))
			ent->sampleCount += nb_packed_samples;
			stts->w_currentSampleNum += nb_packed_samples;
			stts->w_LastDTS = DTS + ent->sampleDelta * (nb_packed_samples-1);
		if (nb_packed_samples>1)
		if (nb_packed_samples==1) {
		ent->sampleCount = nb_packed_samples;
		stts->w_currentSampleNum += nb_packed_samples;
GF_Err stbl_AddSize(GF_SampleSizeBox *stsz, u32 sampleNumber, u32 size, u32 nb_pack_samples)
	if (!nb_pack_samples) nb_pack_samples = 1;
	else if (nb_pack_samples>1)
		size /= nb_pack_samples;
			stsz->sampleCount = nb_pack_samples;
			stsz->sampleCount += nb_pack_samples;
		if (nb_pack_samples>1) {
GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIndex, u64 offset, u32 nb_pack_samples)
	if (!nb_pack_samples)
		nb_pack_samples = 1;
		stsc->w_lastSampleNumber = sampleNumber + nb_pack_samples-1;
	ent->samplesPerChunk = nb_pack_samples;
	if (sampleNumber + nb_pack_samples - 1 == stsc->w_lastSampleNumber) {
	if (!nb_pack) nb_pack = 1;
	if (!nb_pack) nb_pack = 1;
		if (stbl->SampleSize->sampleCount+nb_pack > stbl->SampleSize->alloc_size)
Fixed code: 
#define CHECK_PACK(_e) \
	if (!nb_pack) nb_pack = 1; \
	else if ((s32) nb_pack < 0) { \
		GF_LOG(GF_LOG_ERROR, GF_LOG_CONTAINER, ("[iso file] Too many samples %u in packed sample\n", nb_pack)); \
		return _e; \
	}


GF_Err stbl_AddDTS(GF_SampleTableBox *stbl, u64 DTS, u32 *sampleNumber, u32 LastAUDefDuration, u32 nb_pack)

	CHECK_PACK(GF_BAD_PARAM)

		stts->entries[0].sampleCount = nb_pack;
		stts->entries[0].sampleDelta = (nb_pack>1) ? 0 : LastAUDefDuration;
		stts->w_currentSampleNum = nb_pack;
			|| ((nb_pack>1) && ((DTS == stts->w_LastDTS) || (DTS == stts->w_LastDTS + 2*ent->sampleDelta) ))
			ent->sampleCount += nb_pack;
			stts->w_currentSampleNum += nb_pack;
			stts->w_LastDTS = DTS + ent->sampleDelta * (nb_pack-1);
		if (nb_pack>1)
		if (nb_pack==1) {
		ent->sampleCount = nb_pack;
		stts->w_currentSampleNum += nb_pack;
GF_Err stbl_AddSize(GF_SampleSizeBox *stsz, u32 sampleNumber, u32 size, u32 nb_pack)
	CHECK_PACK(GF_BAD_PARAM)

	if (nb_pack>1)
		size /= nb_pack;

			stsz->sampleCount = nb_pack;
			stsz->sampleCount += nb_pack;
		if (nb_pack>1) {
GF_Err stbl_AddChunkOffset(GF_MediaBox *mdia, u32 sampleNumber, u32 StreamDescIndex, u64 offset, u32 nb_pack)
	CHECK_PACK(GF_BAD_PARAM)
		stsc->w_lastSampleNumber = sampleNumber + nb_pack-1;
	ent->samplesPerChunk = nb_pack;
	if (sampleNumber + nb_pack - 1 == stsc->w_lastSampleNumber) {
	CHECK_PACK(GF_ISOM_INVALID_FILE)

	CHECK_PACK(GF_ISOM_INVALID_FILE)

		if (stbl->SampleSize->sampleCount+nb_pack > stbl->SampleSize->alloc_size) {
		}