Skip to content

Commit ac3bd35

Browse files
committed
libmodplug: Fix misc. loader crashes and leaks found by libFuzzer:
Patchset by Alice Rowan: Konstanty/libmodplug#58 * AMF (DSMI): fix out-of-bounds reads caused by missing order list bounds checks. * DBM: fix leaks caused by duplicate instrument chunks being loaded. * FAR: fix out-of-bounds reads due to not correctly bounding the maximum pattern read size. * IT: fix out-of-bounds reads in the IT sample decompressors caused by allowing ITReadBits to read past the end of the buffer. * MED: fix out-of-bounds reads due to a faulty MMD2PLAYSEQ bounds check. * MED: fix out-of-bounds reads due to bad sample bounding. * MED: fix out-of-bounds reads due to bad block name bounding (and potential missing nul terminators). * OKT: fix out-of-bounds reads due to incorrect OKTSAMPLE bounding. * OKT: fix out-of-bounds reads due to bad chunk header and order list bounding. * OKT: fix playback errors caused by skipping the first two orders in the order list. * S3M: fix out-of-bounds reads due to missing order list bounds check. * S3M: fix out-of-bounds reads due to missing offset list bounds check. * S3M: fix out-of-bounds reads due to missing panning table check. * STM: fix pattern leaks and pattern size corruption caused by missing MAX_PATTERNS check. * ULT: fix out-of-bounds reads due to incorrect event bounding. * WAV: fix out-of-bounds reads due to not bounds checking the fmt chunk. * WAV: fix hangs caused by missing chunk length bounds check. * WAV: constify pointers derived from lpStream. * XM: fix out-of-bounds reads due to broken XMSAMPLEHEADER check. * XM: fix out-of-bounds reads due to missing pattern data checks. * XM: fix slow loads caused by bad bounding in instrument/sample loops, add other various missing bounds checks. - Fix AMS loader crash and slow load bugs found by libFuzzer: * AMS: fix AMS out-of-bounds reads due to missing song comments checks. * AMS: fix AMS out-of-bounds reads due to missing order list check. * AMS: fix AMS out-of-bounds reads due to missing pattern/track checks. * AMS: fix AMS2 out-of-bounds reads due to missing/broken instrument and envelope bounds checks. * AMS: fix AMS2 out-of-bounds reads due to missing sample bounds checks. * AMS: fix ReadSample out-of-bounds reads due to overflow in packed size bounds check. * AMS: fix AMSUnpack out-of-bounds reads due to missing RLE unpacking bounds checks. * AMS: reduce AMSUnpack slow loads by rejecting samples with truncated or invalid RLE. * AMS: reduce AMSUnpack slow loads by shrinking samples if their packed size couldn't possibly store them. * AMS: constify pointers derived from lpStream. - Fix DMF loader crash/hang/slow load bugs found by libFuzzer: * DMF: fix faulty bounds checks for INFO, SEQU, and SMPI chunks. * DMF: add numerous missing bounds checks for patterns and track data. * DMF: fix out-of-bounds reads caused by missing sample bounds check. * DMF: fix hangs caused by duplicate PATT chunks. * DMF: fix sample leaks caused by duplicate SMPD chunks. * DMF: fix slow loads caused by missing EOF check in DMFUnpack. * DMF: constify pointers derived from lpStream. - Fix MDL loader crash bugs found by libFuzzer: * MDL: fix out-of-bounds reads due to missing info chunk bounds check. * MDL: fix out-of-bounds reads due to a missing bounds check when loading instruments. * MDL: fix out-of-bounds reads and other bugs due to bad envelope bounding and missing duplicate envelope chunk checks. * MDL: fix out-of-bounds reads due to broken track bounds checks. - Fix MT2 loader crashes and hangs found by libFuzzer: * MT2: fix out-of-bounds reads due to missing nDrumDataLen check. * MT2: fix out-of-bounds reads due to missing pattern/track checks. * MT2: fix out-of-bounds reads due to broken/nonsensical instrument bounds checks. * MT2: fix out-of-bounds reads due to missing sample data length bounds check. * MT2: fix out-of-bounds reads due to bad checks on group structs. * MT2: fix hangs caused by overflows preventing the data chunk size bounds check from working. * MT2: constify pattern data pointer derived from lpStream. - Fix PSM loader crash bugs found by libFuzzer: * PSM: fix out-of-bounds reads due to dereferencing lpStream before any bounds checks. * PSM: fix out-of-bounds reads due to reading pPsmPat.data from the stack instead of the input buffer. * PSM: fix out-of-bounds reads due to invalid samples in patterns. * PSM: fix missing pattern length byte-swapping. * PSM: constify pattern data pointer derived from lpStream.
1 parent eb0342b commit ac3bd35

20 files changed

Lines changed: 325 additions & 166 deletions

external/libmodplug-0.8.9.0/src/load_amf.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,12 @@ BOOL CSoundFile::ReadAMF(LPCBYTE lpStream, const DWORD dwMemLength)
315315
PatternSize[iOrd] = 64;
316316
if (pfh->version >= 14)
317317
{
318+
if (dwMemPos + m_nChannels * sizeof(USHORT) + 2 > dwMemLength) return FALSE;
318319
PatternSize[iOrd] = bswapLE16(*(USHORT *)(lpStream+dwMemPos));
319320
dwMemPos += 2;
321+
} else
322+
{
323+
if (dwMemPos + m_nChannels * sizeof(USHORT) > dwMemLength) return FALSE;
320324
}
321325
ptracks[iOrd] = (USHORT *)(lpStream+dwMemPos);
322326
dwMemPos += m_nChannels * sizeof(USHORT);

external/libmodplug-0.8.9.0/src/load_ams.cpp

Lines changed: 62 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,16 @@ typedef struct AMSSAMPLEHEADER
3939

4040
#pragma pack()
4141

42+
static BOOL AMSUnpackCheck(const BYTE *lpStream, DWORD dwMemLength, MODINSTRUMENT *ins);
4243

4344
BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
4445
//-----------------------------------------------------------
4546
{
4647
// BYTE pkinf[MAX_SAMPLES];
47-
AMSFILEHEADER *pfh = (AMSFILEHEADER *)lpStream;
48+
const AMSFILEHEADER *pfh = (AMSFILEHEADER *)lpStream;
4849
DWORD dwMemPos;
4950
UINT tmp, tmp2;
50-
51+
5152
if ((!lpStream) || (dwMemLength < 1024)) return FALSE;
5253
if ((pfh->verhi != 0x01) || (strncmp(pfh->szHeader, "Extreme", 7))
5354
|| (!pfh->patterns) || (!pfh->orders) || (!pfh->samples) || (pfh->samples >= MAX_SAMPLES)
@@ -63,7 +64,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
6364
m_nSamples = pfh->samples;
6465
for (UINT nSmp=1; nSmp<=m_nSamples; nSmp++, dwMemPos += sizeof(AMSSAMPLEHEADER))
6566
{
66-
AMSSAMPLEHEADER *psh = (AMSSAMPLEHEADER *)(lpStream + dwMemPos);
67+
const AMSSAMPLEHEADER *psh = (AMSSAMPLEHEADER *)(lpStream + dwMemPos);
6768
MODINSTRUMENT *pins = &Ins[nSmp];
6869
pins->nLength = psh->length;
6970
pins->nLoopStart = psh->loopstart;
@@ -115,9 +116,10 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
115116
dwMemPos += tmp;
116117
}
117118
// Read Song Comments
119+
if (dwMemPos + 2 > dwMemLength) return TRUE;
118120
tmp = *((WORD *)(lpStream+dwMemPos));
119121
dwMemPos += 2;
120-
if (dwMemPos + tmp >= dwMemLength) return TRUE;
122+
if (tmp >= dwMemLength || dwMemPos > dwMemLength - tmp) return TRUE;
121123
if (tmp)
122124
{
123125
m_lpszSongComments = new char[tmp+1]; // changed from CHAR
@@ -127,6 +129,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
127129
dwMemPos += tmp;
128130
}
129131
// Read Order List
132+
if (2*pfh->orders >= dwMemLength || dwMemPos > dwMemLength - 2*pfh->orders) return TRUE;
130133
for (UINT iOrd=0; iOrd<pfh->orders; iOrd++, dwMemPos += 2)
131134
{
132135
UINT n = *((WORD *)(lpStream+dwMemPos));
@@ -138,7 +141,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
138141
if (dwMemPos + 4 >= dwMemLength) return TRUE;
139142
UINT len = *((DWORD *)(lpStream + dwMemPos));
140143
dwMemPos += 4;
141-
if ((len >= dwMemLength) || (dwMemPos + len > dwMemLength)) return TRUE;
144+
if ((len >= dwMemLength) || (dwMemPos > dwMemLength - len)) return TRUE;
142145
PatternSize[iPat] = 64;
143146
MODCOMMAND *m = AllocatePattern(PatternSize[iPat], m_nChannels);
144147
if (!m) return TRUE;
@@ -154,6 +157,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
154157
// Note+Instr
155158
if (!(b0 & 0x40))
156159
{
160+
if (i+1 > len) break;
157161
b2 = p[i++];
158162
if (ch < m_nChannels)
159163
{
@@ -162,6 +166,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
162166
}
163167
if (b1 & 0x80)
164168
{
169+
if (i+1 > len) break;
165170
b0 |= 0x40;
166171
b1 = p[i++];
167172
}
@@ -179,6 +184,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
179184
}
180185
} else
181186
{
187+
if (i+1 > len) break;
182188
b2 = p[i++];
183189
if (ch < m_nChannels)
184190
{
@@ -221,6 +227,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
221227
}
222228
if (b1 & 0x80)
223229
{
230+
if (i+1 > len) break;
224231
b1 = p[i++];
225232
if (i <= len) goto anothercommand;
226233
}
@@ -238,7 +245,8 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength)
238245
{
239246
if (dwMemPos >= dwMemLength - 9) return TRUE;
240247
UINT flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8;
241-
dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos);
248+
if (!AMSUnpackCheck(lpStream+dwMemPos, dwMemLength-dwMemPos, &Ins[iSmp])) break;
249+
dwMemPos += ReadSample(&Ins[iSmp], flags, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos);
242250
}
243251
return TRUE;
244252
}
@@ -301,15 +309,14 @@ typedef struct AMS2SAMPLE
301309
BYTE flags;
302310
} AMS2SAMPLE;
303311

304-
305312
#pragma pack()
306313

307314

308315
BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength)
309316
//------------------------------------------------------------
310317
{
311318
const AMS2FILEHEADER *pfh = (AMS2FILEHEADER *)lpStream;
312-
AMS2SONGHEADER *psh;
319+
const AMS2SONGHEADER *psh;
313320
DWORD dwMemPos;
314321
BYTE smpmap[16];
315322
BYTE packedsamples[MAX_SAMPLES];
@@ -335,19 +342,23 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength)
335342
if (psh->flags & 0x40) m_dwSongFlags |= SONG_LINEARSLIDES;
336343
for (UINT nIns=1; nIns<=m_nInstruments; nIns++)
337344
{
345+
if (dwMemPos >= dwMemLength) return TRUE;
338346
UINT insnamelen = lpStream[dwMemPos];
339-
CHAR *pinsname = (CHAR *)(lpStream+dwMemPos+1);
347+
const CHAR *pinsname = (CHAR *)(lpStream+dwMemPos+1);
340348
dwMemPos += insnamelen + 1;
341-
AMS2INSTRUMENT *pins = (AMS2INSTRUMENT *)(lpStream + dwMemPos);
349+
const AMS2INSTRUMENT *pins = (AMS2INSTRUMENT *)(lpStream + dwMemPos);
342350
dwMemPos += sizeof(AMS2INSTRUMENT);
343-
if (dwMemPos + 1024 >= dwMemLength) return TRUE;
344-
AMS2ENVELOPE *volenv, *panenv, *pitchenv;
351+
const AMS2ENVELOPE *volenv, *panenv, *pitchenv;
352+
if (dwMemPos + sizeof(AMS2ENVELOPE) > dwMemLength) return TRUE;
345353
volenv = (AMS2ENVELOPE *)(lpStream+dwMemPos);
346354
dwMemPos += 5 + volenv->points*3;
355+
if (dwMemPos + sizeof(AMS2ENVELOPE) > dwMemLength) return TRUE;
347356
panenv = (AMS2ENVELOPE *)(lpStream+dwMemPos);
348357
dwMemPos += 5 + panenv->points*3;
358+
if (dwMemPos + sizeof(AMS2ENVELOPE) > dwMemLength) return TRUE;
349359
pitchenv = (AMS2ENVELOPE *)(lpStream+dwMemPos);
350360
dwMemPos += 5 + pitchenv->points*3;
361+
if (dwMemPos >= dwMemLength) return TRUE;
351362
INSTRUMENTHEADER *penv = new INSTRUMENTHEADER;
352363
if (!penv) return TRUE;
353364
memset(smpmap, 0, sizeof(smpmap));
@@ -387,6 +398,7 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength)
387398
penv->VolPoints[i] = (WORD)pos;
388399
}
389400
}
401+
if (dwMemPos + 5 > dwMemLength) return TRUE;
390402
penv->nFadeOut = (((lpStream[dwMemPos+2] & 0x0F) << 8) | (lpStream[dwMemPos+1])) << 3;
391403
UINT envflags = lpStream[dwMemPos+3];
392404
if (envflags & 0x01) penv->dwFlags |= ENV_VOLLOOP;
@@ -396,16 +408,19 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength)
396408
// Read Samples
397409
for (UINT ismp=0; ismp<pins->samples; ismp++)
398410
{
411+
if (dwMemPos + 1 > dwMemLength) return TRUE;
399412
MODINSTRUMENT *psmp = ((ismp < 16) && (smpmap[ismp])) ? &Ins[smpmap[ismp]] : NULL;
400413
UINT smpnamelen = lpStream[dwMemPos];
414+
if (dwMemPos + smpnamelen + 1 > dwMemLength) return TRUE;
401415
if ((psmp) && (smpnamelen) && (smpnamelen <= 22))
402416
{
403417
memcpy(m_szNames[smpmap[ismp]], lpStream+dwMemPos+1, smpnamelen);
404418
}
405419
dwMemPos += smpnamelen + 1;
420+
if (dwMemPos + sizeof(AMS2SAMPLE) > dwMemLength) return TRUE;
406421
if (psmp)
407422
{
408-
AMS2SAMPLE *pams = (AMS2SAMPLE *)(lpStream+dwMemPos);
423+
const AMS2SAMPLE *pams = (AMS2SAMPLE *)(lpStream+dwMemPos);
409424
psmp->nGlobalVol = 64;
410425
psmp->nPan = 128;
411426
psmp->nLength = pams->length;
@@ -545,24 +560,48 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength)
545560
if (packedsamples[iSmp] & 0x03)
546561
{
547562
flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8;
563+
if (!AMSUnpackCheck(lpStream+dwMemPos, dwMemLength-dwMemPos, &Ins[iSmp])) break;
548564
} else
549565
{
550566
flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S;
551567
}
552-
dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos);
568+
dwMemPos += ReadSample(&Ins[iSmp], flags, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos);
553569
}
554570
return TRUE;
555571
}
556572

557573

574+
// Precheck AMS packed sample size to determine whether or not it could fit the actual size.
575+
static BOOL AMSUnpackCheck(const BYTE *lpStream, DWORD dwMemLength, MODINSTRUMENT *ins)
576+
// -----------------------------------------------------------------------------------
577+
{
578+
if (dwMemLength < 9) return FALSE;
579+
DWORD packedbytes = *((DWORD *)(lpStream + 4));
580+
581+
DWORD samplebytes = ins->nLength;
582+
if (samplebytes > MAX_SAMPLE_LENGTH) samplebytes = MAX_SAMPLE_LENGTH;
583+
if (ins->uFlags & CHN_16BIT) samplebytes *= 2;
584+
585+
// RLE can pack a run of up to 255 bytes into 3 bytes.
586+
DWORD packedmin = (samplebytes * 3) >> 8;
587+
if (packedbytes < packedmin)
588+
{
589+
samplebytes = packedbytes * (255 / 3) + 2;
590+
ins->nLength = samplebytes;
591+
if (ins->uFlags & CHN_16BIT) ins->nLength >>= 1;
592+
}
593+
594+
return TRUE;
595+
}
596+
558597
/////////////////////////////////////////////////////////////////////
559598
// AMS Sample unpacking
560599

561600
void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char packcharacter)
562601
{
563602
UINT tmplen = dmax;
564603
signed char *amstmp = new signed char[tmplen];
565-
604+
566605
if (!amstmp) return;
567606
// Unpack Loop
568607
{
@@ -573,9 +612,11 @@ void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char pac
573612
signed char ch = psrc[i++];
574613
if (ch == packcharacter)
575614
{
615+
if (i >= inputlen) break;
576616
BYTE ch2 = psrc[i++];
577617
if (ch2)
578618
{
619+
if (i >= inputlen) break;
579620
ch = psrc[i++];
580621
while (ch2--)
581622
{
@@ -585,6 +626,12 @@ void AMSUnpack(const char *psrc, UINT inputlen, char *pdest, UINT dmax, char pac
585626
} else p[j++] = packcharacter;
586627
} else p[j++] = ch;
587628
}
629+
if (j < tmplen)
630+
{
631+
// Truncated or invalid; don't try to unpack this.
632+
delete[] amstmp;
633+
return;
634+
}
588635
}
589636
// Bit Unpack Loop
590637
{

external/libmodplug-0.8.9.0/src/load_dbm.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,9 @@ BOOL CSoundFile::ReadDBM(const BYTE *lpStream, DWORD dwMemLength)
136136
// Instruments
137137
if (chunk_id == bswapLE32(DBM_ID_INST))
138138
{
139+
// Skip duplicate chunks.
140+
if (m_nInstruments) continue;
141+
139142
if (nInstruments >= MAX_INSTRUMENTS) nInstruments = MAX_INSTRUMENTS-1;
140143
for (UINT iIns=0; iIns<nInstruments; iIns++)
141144
{
@@ -239,6 +242,9 @@ BOOL CSoundFile::ReadDBM(const BYTE *lpStream, DWORD dwMemLength)
239242
DWORD pksize;
240243
UINT nRows;
241244

245+
// Skip duplicate chunks.
246+
if (Patterns[iPat]) break;
247+
242248
if (chunk_pos + sizeof(DBMPATTERN) > dwMemPos) break;
243249
pph = (DBMPATTERN *)(lpStream+chunk_pos);
244250
pksize = bswapBE32(pph->packedsize);

0 commit comments

Comments
 (0)