Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package libmodplug for openSUSE:Factory checked in at 2026-10-21 21:02:53 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/libmodplug (Old) and /work/SRC/openSUSE:Factory/.libmodplug.new.2252680 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "libmodplug" Wed Oct 21 21:02:53 2026 rev:4 rq:1386960 version:0.8.9.0+git20170610.f6dd59a Changes: -------- --- /work/SRC/openSUSE:Factory/libmodplug/libmodplug.changes 2024-03-03 20:19:43.070691166 +0100 +++ /work/SRC/openSUSE:Factory/.libmodplug.new.2252680/libmodplug.changes 2026-10-21 21:03:59.842444995 +0200 @@ -1,0 +2,14 @@ +Wed Oct 21 17:45:16 UTC 2026 - Dave Plater <[email protected]> + +- Added patches from Fedora git: + libmodplug-0.8.9.0-CVE-2026-75904.patch + libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch + libmodplug-0.8.9.0-abc-drum-bounds.patch + libmodplug-0.8.9.0-CVE-2026-108577.patch + libmodplug-0.8.9.0-CVE-2026-111270.patch + libmodplug-0.8.9.0-CVE-2026-111271.patch + which fix boo#1285765, boo#1285766, boo#1284977 and boo#1275624 +- Fixes: CVE-2026-75904, CVE-2026-108577, CVE-2026-111270 and + CVE-2026-111271 + +------------------------------------------------------------------- New: ---- libmodplug-0.8.9.0-CVE-2026-108577.patch libmodplug-0.8.9.0-CVE-2026-111270.patch libmodplug-0.8.9.0-CVE-2026-111271.patch libmodplug-0.8.9.0-CVE-2026-75904.patch libmodplug-0.8.9.0-abc-drum-bounds.patch libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch ----------(New B)---------- New: libmodplug-0.8.9.0-abc-drum-bounds.patch libmodplug-0.8.9.0-CVE-2026-108577.patch libmodplug-0.8.9.0-CVE-2026-111270.patch New: libmodplug-0.8.9.0-CVE-2026-108577.patch libmodplug-0.8.9.0-CVE-2026-111270.patch libmodplug-0.8.9.0-CVE-2026-111271.patch New: libmodplug-0.8.9.0-CVE-2026-111270.patch libmodplug-0.8.9.0-CVE-2026-111271.patch which fix boo#1285765, boo#1285766, boo#1284977 and boo#1275624 New:- Added patches from Fedora git: libmodplug-0.8.9.0-CVE-2026-75904.patch libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch New: libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch libmodplug-0.8.9.0-abc-drum-bounds.patch libmodplug-0.8.9.0-CVE-2026-108577.patch New: libmodplug-0.8.9.0-CVE-2026-75904.patch libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch libmodplug-0.8.9.0-abc-drum-bounds.patch ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ libmodplug.spec ++++++ --- /var/tmp/diff_new_pack.M4uu8W/_old 2026-10-21 21:04:00.692481079 +0200 +++ /var/tmp/diff_new_pack.M4uu8W/_new 2026-10-21 21:04:00.694481164 +0200 @@ -1,7 +1,7 @@ # # spec file for package libmodplug # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -30,6 +30,13 @@ Patch1: libmodplug-timidity.patch # PATCH-FIX-UPSTREAM libmodplug-0.8.8.5-fix-missing-include-path.patch -- set includedir in pc file Patch2: libmodplug-0.8.8.5-fix-missing-include-path.patch +Patch3: libmodplug-0.8.9.0-abc-drum-bounds.patch +Patch4: libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch +Patch5: libmodplug-0.8.9.0-CVE-2026-108577.patch +Patch6: libmodplug-0.8.9.0-CVE-2026-111270.patch +Patch7: libmodplug-0.8.9.0-CVE-2026-111271.patch +Patch8: libmodplug-0.8.9.0-CVE-2026-75904.patch + BuildRequires: autoconf BuildRequires: automake BuildRequires: gcc-c++ @@ -43,8 +50,8 @@ Olivier Lapicque, found at http://www.modplug.com/. %package -n libmodplug%{soname} -Summary: Shared library part of libmodplug -Group: System/Libraries +Summary: Development files for libmodplug +Group: Development/Libraries/C and C++ %description -n libmodplug%{soname} Modplug library based on the ModPlug sound engine. @@ -64,8 +71,7 @@ %prep %setup -q -%patch -P 1 -%patch -P 2 -p1 +%autopatch -p1 sed -i 's/\r$//' ChangeLog %build ++++++ libmodplug-0.8.8.5-fix-missing-include-path.patch ++++++ --- /var/tmp/diff_new_pack.M4uu8W/_old 2026-10-21 21:04:00.757483838 +0200 +++ /var/tmp/diff_new_pack.M4uu8W/_new 2026-10-21 21:04:00.762484050 +0200 @@ -1,6 +1,6 @@ diff -urN libmodplug-0.8.8.5.old/libmodplug.pc.in libmodplug-0.8.8.5/libmodplug.pc.in ---- libmodplug-0.8.8.5.old/libmodplug.pc.in 2014-03-27 13:04:13.803895733 +0100 -+++ libmodplug-0.8.8.5/libmodplug.pc.in 2014-03-27 13:04:26.425896900 +0100 +--- a/libmodplug.pc.in 2014-03-27 13:04:13.803895733 +0100 ++++ b/libmodplug.pc.in 2014-03-27 13:04:26.425896900 +0100 @@ -9,4 +9,4 @@ Requires: Libs: -L${libdir} -lmodplug ++++++ libmodplug-0.8.9.0-CVE-2026-108577.patch ++++++ Description: Bound chord accompaniment generation in abc_add_gchord() abc_add_gchord() derives its loop bound from attacker-controlled input, stime = (tracktime - etime) * steps, with no upper limit, and allocates an ABCEVENT per iteration via abc_add_chordnote()/abc_add_noteoff(). A crafted ABC file (large tracktime, barticks of 1) makes the loop allocate without bound, exhausting memory. Cap the gap the accompaniment may cover. . Two related defects in the same loops are fixed along with it: . * gsteps counted characters instead of (note, length) pairs, so g ran up to strlen() and indexed h->gchord[2*g+1] past the terminating NUL and, for a long gchord, past the 80 byte array into the adjacent h->drum[] fields. The sibling abc_add_drum() already uses strlen(h->drum)/2. * A gchord whose even positions hold digits rather than note characters (e.g. "%%MIDI gchord 1") matches no case in the switch, so rtime never advances and the loop spins forever. Bail out instead. . Applies unchanged to 0.8.9.0 and to upstream git master. Author: Karel Volný <[email protected]> Bug-RedHat: https://bugzilla.redhat.com/show_bug.cgi?id=2550240 Forwarded: https://github.com/Konstanty/libmodplug/pull/108 Last-Update: 2026-10-21 Index: b/src/load_abc.cpp =================================================================== --- a/src/load_abc.cpp 2026-10-21 19:11:09.218432165 +0200 +++ b/src/load_abc.cpp 2026-10-21 19:12:45.520803598 +0200 @@ -3193,6 +3193,10 @@ static void abc_add_drum(ABCHANDLE *h, u } } +// A crafted file can stretch the gchord gap to millions of bars, and the +// loop in abc_add_gchord() allocates an event per step. CVE-2026-108577. +#define ABC_MAX_GCHORD_BARS 256 + static int abc_gchord_steps(const char *gch) { const char *p; @@ -3229,15 +3233,20 @@ static void abc_add_gchord(ABCHANDLE *h, } if( etime > tracktime ) return; modbarticks = h->barticks ? h->barticks : 1; + if( (tracktime - etime) / modbarticks > ABC_MAX_GCHORD_BARS ) return; if( etime < bartime ) rtime = h->barticks - ((bartime - etime) % modbarticks); else rtime = (etime - bartime) % modbarticks; stime = ctime * steps; rtime *= steps; rtime += stime; - gsteps = strlen(h->gchord); + // gchord holds (note, length) pairs; stepping per character runs g past + // the terminating NUL and, for a long gchord, past the 80 byte array. + gsteps = strlen(h->gchord) / 2; + if( !gsteps ) return; g = 0; while( rtime > stime ) { glen = h->gchord[2*g+1] - '0'; + if( glen < 1 ) return; // malformed: rtime would never reach stime rtime -= ctime * glen; if( ++g == gsteps ) g = 0; } @@ -3246,6 +3255,7 @@ static void abc_add_gchord(ABCHANDLE *h, while( rtime < stime ) { gnote = h->gchord[2*g]; glen = h->gchord[2*g+1] - '0'; + if( glen < 1 || !strchr("bcfghijGHIJz", gnote) ) return; // no progress if( ++g == gsteps ) g = 0; switch(gnote) { case 'b': ++++++ libmodplug-0.8.9.0-CVE-2026-111270.patch ++++++ Description: Bound the pattern count and fix the length overflows in ReadDSM() CSoundFile::ReadDSM() increments nPat for every PATT chunk it reads and uses it to index Patterns[] and PatternSize[], which hold MAX_PATTERNS (240) entries each, without ever checking the bound. A DSM file with more than 240 PATT chunks therefore writes past both arrays inside the heap allocated CSoundFile object. Check nPat before using it. . The report names a second trigger, a wrapping patt_len. patt_len is a DWORD and the guard was "dwMemPos + ppatt->patt_len >= dwMemLength", so a large value wraps, the guard passes, and the following "dwMemPos += patt_len" moves dwMemPos backwards. The loop then re-reads the same bytes and keeps incrementing nPat, which reaches the overflow above from a small file. Rewritten as a subtraction, which cannot wrap here: the loop condition guarantees dwMemPos < dwMemLength - 8 and dwMemPos has just been advanced by 8, so dwMemLength - dwMemPos is positive. . The sample branch three lines further down had the identical overflow in "dwMemPos + pins->inst_len >= dwMemLength - 8"; a wrap there lets dwPos run past the buffer and ReadSample() is then handed a wrapped, huge length. That is not named in the report but it is the same expression in the same loop, so it is fixed here rather than left for a follow-up. Drop that hunk if you want this patch to cover only what the CVE describes. . Applies to 0.8.9.0 and to upstream git master; src/load_dsm.cpp is byte identical between the two. Author: Karel Volný <[email protected]> Bug-RedHat: https://bugzilla.redhat.com/show_bug.cgi?id=2554073 Forwarded: https://github.com/Konstanty/libmodplug/pull/109 Last-Update: 2026-10-21 Index: b/src/load_dsm.cpp =================================================================== --- a/src/load_dsm.cpp 2017-06-09 23:03:24.000000000 +0200 +++ b/src/load_dsm.cpp 2026-10-21 19:13:42.153433956 +0200 @@ -129,8 +129,12 @@ BOOL CSoundFile::ReadDSM(LPCBYTE lpStrea // Reading Patterns if (ppatt->id_PATT == DSMID_PATT) { + // Patterns[] and PatternSize[] only hold MAX_PATTERNS entries + if (nPat >= MAX_PATTERNS) break; dwMemPos += 8; - if (dwMemPos + ppatt->patt_len >= dwMemLength) break; + // dwMemPos < dwMemLength here, so the subtraction is safe, while + // adding patt_len wraps for a large DWORD and passes the check + if (ppatt->patt_len >= dwMemLength - dwMemPos) break; DWORD dwPos = dwMemPos; dwMemPos += ppatt->patt_len; MODCOMMAND *m = AllocatePattern(64, m_nChannels); @@ -209,7 +213,8 @@ BOOL CSoundFile::ReadDSM(LPCBYTE lpStrea // Reading Samples if ((nSmp <= m_nSamples) && (pins->id_INST == DSMID_INST)) { - if (dwMemPos + pins->inst_len >= dwMemLength - 8) break; + // same wrap as patt_len above; dwMemPos < dwMemLength - 8 here + if (pins->inst_len >= dwMemLength - 8 - dwMemPos) break; DWORD dwPos = dwMemPos + sizeof(DSMINST); dwMemPos += 8 + pins->inst_len; memcpy(m_szNames[nSmp], pins->samplename, 28); ++++++ libmodplug-0.8.9.0-CVE-2026-111271.patch ++++++ Description: Bound the pattern decoder reads in ReadAMS2() The pattern unpack loop in CSoundFile::ReadAMS2() tests "pos < packedlen" only at the top of each iteration, but the body advances pos up to five times. The inner effect loop is the worst of them: . while (byte2 & 0x80) { byte2 = psrc[pos++]; ... UINT param = psrc[pos++]; . It runs for as long as every byte it fetches has bit 7 set, with no bound of its own, so a pattern whose effect bytes all keep bit 7 set walks pos past packedlen and on past the end of the input buffer, reading adjacent heap memory until it finds a byte without bit 7 or hits an unmapped page. Check pos against packedlen at each read instead. packedlen already satisfies packedlen < dwMemLength - dwMemPos, so staying within it keeps every read inside the buffer. . This is the v2 counterpart of upstream 70d73b9, which added exactly this bound to the four reads in the ReadAMS() v1 decoder and left ReadAMS2() alone. The "pos+1 > packedlen" spelling is upstream's from that commit. . The two byte note/instrument read just above has the same problem on a smaller scale and is bounded here too. . The first hunk is not named in the report: the pattern name memcpy reads patnamlen bytes from dwMemPos+3, with patnamlen up to MAX_PATTERNNAME-1, but at that point only dwMemPos+4 is known to be inside the buffer, so a pattern at the end of the file reads up to 30 bytes past it. The upstream dwMemPos checks backported in the previous patch do not cover this: they all sit in the instrument and sample loops, which finish before the pattern section starts. Drop that hunk if you want this patch to cover only what the CVE describes. . Applies on top of libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch, and unchanged to upstream git master. Author: Karel Volný <[email protected]> Bug-RedHat: https://bugzilla.redhat.com/show_bug.cgi?id=2554079 Forwarded: https://github.com/Konstanty/libmodplug/pull/107 Last-Update: 2026-10-21 Index: b/src/load_ams.cpp =================================================================== --- a/src/load_ams.cpp 2026-10-21 19:12:00.578096937 +0200 +++ b/src/load_ams.cpp 2026-10-21 19:14:15.640215391 +0200 @@ -495,7 +495,10 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre dwMemPos += 4; if ((ipat < MAX_PATTERNS) && (packedlen < dwMemLength-dwMemPos) && (numrows >= 8)) { - if ((patnamlen) && (patnamlen < MAX_PATTERNNAME)) + // the name is read from dwMemPos+3, but only dwMemPos+4 is + // known to be inside the buffer at this point + if ((patnamlen) && (patnamlen < MAX_PATTERNNAME) + && (dwMemPos+3+patnamlen <= dwMemLength)) { char s[MAX_PATTERNNAME]; // changed from CHAR memcpy(s, lpStream+dwMemPos+3, patnamlen); @@ -517,6 +520,8 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre // Read Note + Instr if (!(byte1 & 0x40)) { + // note and instrument are two further bytes + if (pos+2 > packedlen) break; UINT byte2 = psrc[pos++]; UINT note = byte2 & 0x7F; if (note) m[ch].note = (note > 1) ? (note-1) : 0xFF; @@ -524,6 +529,7 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre // Read Effect while (byte2 & 0x80) { + if (pos+1 > packedlen) break; byte2 = psrc[pos++]; if (byte2 & 0x40) { @@ -532,6 +538,7 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre } else { UINT command = byte2 & 0x3F; + if (pos+1 > packedlen) break; UINT param = psrc[pos++]; if (command == 0x0C) { ++++++ libmodplug-0.8.9.0-CVE-2026-75904.patch ++++++ Description: Fix out-of-bounds read in pat_smplooped Author: Përparim Mjeku Origin: https://github.com/Konstanty/libmodplug/issues/103 Bug-Debian: https://bugs.debian.org/1144933 Forwarded: not-needed Index: b/src/load_pat.cpp =================================================================== --- a/src/load_pat.cpp 2026-10-21 19:08:46.052363874 +0200 +++ b/src/load_pat.cpp 2026-10-21 19:14:56.063951345 +0200 @@ -208,7 +208,7 @@ int pat_gmtosmp(int gm) int pat_smplooped(int smp) { - if( smp < MAXSMP ) return pat_loops[smp - 1]; + if( smp > 0 && smp < MAXSMP ) return pat_loops[smp - 1]; return 1; } ++++++ libmodplug-0.8.9.0-abc-drum-bounds.patch ++++++ Description: Bound drum accompaniment generation in abc_add_drum() abc_add_drum() is built like abc_add_gchord() and carries the same defects, driven by "%%MIDI drum" instead of "%%MIDI gchord". No CVE is assigned for this one; it was found while fixing CVE-2026-108577 in the sibling function. . * stime = (tracktime - etime) * steps has no upper bound and the loop allocates an ABCEVENT per iteration via abc_add_drumnote()/ abc_add_noteoff(), so a crafted file makes it allocate without bound. This is the same flaw as CVE-2026-108577. * h->barticks is used directly as a modulo divisor. It is 0 until an M: field is parsed, and notelen_notediv_to_ticks() can round a bar down to 0 ticks for a large meter divisor, which is why three other call sites in this file carry an explicit "if (barticks == 0) barticks = 1". Here it divides by zero. abc_add_gchord() already returns early on this. * gsteps is strlen(h->drum)/2, so a one character drum string (reachable with "%%MIDI drum 1", a leading digit is accepted as if it were a note) makes it 0. The "if( ++g == gsteps ) g = 0" wrap then never fires, g grows without limit and h->drum[g*2] reads far past the 80 byte array. * A drum step with a zero length, or an even position holding a digit rather than 'd' or 'z', leaves rtime unchanged, so the loop spins forever. . Applies to 0.8.9.0 and to upstream git master, in either order with the CVE-2026-108577 patch. Author: Karel Volný <[email protected]> Forwarded: https://github.com/Konstanty/libmodplug/pull/106 Last-Update: 2026-10-21 Index: b/src/load_abc.cpp =================================================================== --- a/src/load_abc.cpp 2017-06-09 23:03:24.000000000 +0200 +++ b/src/load_abc.cpp 2026-10-21 19:11:09.218432165 +0200 @@ -3134,6 +3134,10 @@ static int abc_drum_steps(const char *dc return i; } +// Same cap as abc_add_gchord(): bound the accompaniment one call may +// generate, every step allocates an event. +#define ABC_MAX_DRUM_BARS 256 + static void abc_add_drum(ABCHANDLE *h, uint32_t tracktime, uint32_t bartime) { ABCEVENT *e; @@ -3142,20 +3146,29 @@ static void abc_add_drum(ABCHANDLE *h, u int i, g, steps, gnote, gsteps, nnum; steps = abc_drum_steps(h->drum); ctime = h->barticks; + // barticks is 0 until an M: field is seen, and a meter whose divisor + // rounds the bar to zero ticks leaves it 0; the modulo below would + // divide by zero. abc_add_gchord() guards this already. + if( !ctime ) return; // look up the last event in tpr drumtrack tp = abc_locate_track(h, h->tpr->v, DRUMPOS); e = tp->tail; etime = e? e->tracktick: bartime; if( etime > tracktime ) return; + if( (tracktime - etime) / ctime > ABC_MAX_DRUM_BARS ) return; if( etime < bartime ) rtime = h->barticks - ((bartime - etime) % h->barticks); else rtime = (etime - bartime) % h->barticks; stime = ctime*steps; rtime *= steps; rtime += stime; gsteps = strlen(h->drum)/2; + // with gsteps 0 the wrap below never fires and g walks off the array + if( !gsteps ) return; g = 0; while( rtime > stime ) { - rtime -= ctime*(h->drum[g*2+1] - '0'); + i = h->drum[g*2+1] - '0'; + if( i < 1 ) return; // malformed: rtime would never reach stime + rtime -= ctime * i; if( ++g == gsteps ) g = 0; } stime = (tracktime - etime) * steps; @@ -3168,6 +3181,7 @@ static void abc_add_drum(ABCHANDLE *h, u while( rtime < stime ) { gnote = h->drum[g*2]; i = h->drum[g*2+1] - '0'; + if( i < 1 || (gnote != 'd' && gnote != 'z') ) return; // no progress if( gnote=='d') { tp->instr = pat_gm_drumnr(h->drumins[g]-1); nnum = pat_gm_drumnote(h->drumins[g]); ++++++ libmodplug-0.8.9.0-ams-upstream-bounds-checks.patch ++++++ Description: Sync src/load_ams.cpp with upstream master Fedora builds the 0.8.9.0 tarball from 2017, which predates all of the AMS loader hardening upstream did in 2021. This brings src/load_ams.cpp up to the current upstream state so the CVE-2026-111271 fix applies on top of it rather than beside it. . Upstream commits, oldest first: . 1bef4e1 Add bounds checks to Extreme's Tracker AMS loader. 11871de Fix Velvet Studio AMS crash, constify pointers. 31910e1 Fix another Extreme's AMS crash. ecf8df0 Fix AMS slow loads due to large garbage samples. 6948105 Fix AMS2 instrument bounding checks. 4282f68 Add more missing consts in AMS loaders. 5e1443f Add missing AMSUnpack bounds checks. cd6baf2 Shrink AMS samples if the packed size can't possibly fit them. 70d73b9 Fix off-by-one AMS pattern bounds checks. f768b60 Add extra AMS2 pitchenv bounds check in case support is ever added. 63133e8 multiple warning fixes and build updates (load_ams.cpp part only) . The backport is self contained: MAX_SAMPLE_LENGTH is already defined in 0.8.9.0's sndfile.h, ReadSample() already takes LPCSTR there, and the new AMSUnpackCheck() is file local. No header changes are needed. . Note that none of this touches the ReadAMS2() pattern decoder. 70d73b9 added exactly this class of bound to the ReadAMS() v1 decoder and the v2 decoder was left alone; that is what CVE-2026-111271 is about, fixed separately. Origin: upstream, https://github.com/Konstanty/libmodplug Author: AliceLR, Ozkan Sezer Applied-Upstream: yes Last-Update: 2026-10-20 Index: b/src/load_ams.cpp =================================================================== --- a/src/load_ams.cpp 2017-06-09 23:03:24.000000000 +0200 +++ b/src/load_ams.cpp 2026-10-21 19:12:00.578096937 +0200 @@ -37,19 +37,19 @@ typedef struct AMSSAMPLEHEADER BYTE infobyte; } AMSSAMPLEHEADER; - #pragma pack() +static BOOL AMSUnpackCheck(const BYTE *lpStream, DWORD dwMemLength, MODINSTRUMENT *ins); BOOL CSoundFile::ReadAMS(LPCBYTE lpStream, DWORD dwMemLength) //----------------------------------------------------------- { - BYTE pkinf[MAX_SAMPLES]; - AMSFILEHEADER *pfh = (AMSFILEHEADER *)lpStream; +// BYTE pkinf[MAX_SAMPLES]; + const AMSFILEHEADER *pfh = (AMSFILEHEADER *)lpStream; DWORD dwMemPos; UINT tmp, tmp2; - + if ((!lpStream) || (dwMemLength < 1024)) return FALSE; if ((pfh->verhi != 0x01) || (strncmp(pfh->szHeader, "Extreme", 7)) || (!pfh->patterns) || (!pfh->orders) || (!pfh->samples) || (pfh->samples >= MAX_SAMPLES) @@ -65,7 +65,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea m_nSamples = pfh->samples; for (UINT nSmp=1; nSmp<=m_nSamples; nSmp++, dwMemPos += sizeof(AMSSAMPLEHEADER)) { - AMSSAMPLEHEADER *psh = (AMSSAMPLEHEADER *)(lpStream + dwMemPos); + const AMSSAMPLEHEADER *psh = (AMSSAMPLEHEADER *)(lpStream + dwMemPos); MODINSTRUMENT *pins = &Ins[nSmp]; pins->nLength = psh->length; pins->nLoopStart = psh->loopstart; @@ -78,7 +78,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea pins->nFineTune = MOD2XMFineTune(psh->finetune_and_pan & 0x0F); pins->uFlags = (psh->infobyte & 0x80) ? CHN_16BIT : 0; if ((pins->nLoopEnd <= pins->nLength) && (pins->nLoopStart+4 <= pins->nLoopEnd)) pins->uFlags |= CHN_LOOP; - pkinf[nSmp] = psh->infobyte; + // pkinf[nSmp] = psh->infobyte; } // Read Song Name tmp = lpStream[dwMemPos++]; @@ -117,9 +117,10 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea dwMemPos += tmp; } // Read Song Comments + if (dwMemPos + 2 > dwMemLength) return TRUE; tmp = *((WORD *)(lpStream+dwMemPos)); dwMemPos += 2; - if (dwMemPos + tmp >= dwMemLength) return TRUE; + if (tmp >= dwMemLength || dwMemPos > dwMemLength - tmp) return TRUE; if (tmp) { m_lpszSongComments = new char[tmp+1]; // changed from CHAR @@ -129,6 +130,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea dwMemPos += tmp; } // Read Order List + if (2*pfh->orders >= dwMemLength || dwMemPos > dwMemLength - 2*pfh->orders) return TRUE; for (UINT iOrd=0; iOrd<pfh->orders; iOrd++, dwMemPos += 2) { UINT n = *((WORD *)(lpStream+dwMemPos)); @@ -140,7 +142,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea if (dwMemPos + 4 >= dwMemLength) return TRUE; UINT len = *((DWORD *)(lpStream + dwMemPos)); dwMemPos += 4; - if ((len >= dwMemLength) || (dwMemPos + len > dwMemLength)) return TRUE; + if ((len >= dwMemLength) || (dwMemPos > dwMemLength - len)) return TRUE; PatternSize[iPat] = 64; MODCOMMAND *m = AllocatePattern(PatternSize[iPat], m_nChannels); if (!m) return TRUE; @@ -156,6 +158,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea // Note+Instr if (!(b0 & 0x40)) { + if (i+1 > len) break; b2 = p[i++]; if (ch < m_nChannels) { @@ -164,6 +167,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea } if (b1 & 0x80) { + if (i+1 > len) break; b0 |= 0x40; b1 = p[i++]; } @@ -181,6 +185,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea } } else { + if (i+1 > len) break; b2 = p[i++]; if (ch < m_nChannels) { @@ -223,6 +228,7 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea } if (b1 & 0x80) { + if (i+1 > len) break; b1 = p[i++]; if (i <= len) goto anothercommand; } @@ -240,7 +246,8 @@ BOOL CSoundFile::ReadAMS(LPCBYTE lpStrea { if (dwMemPos >= dwMemLength - 9) return TRUE; UINT flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8; - dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + if (!AMSUnpackCheck(lpStream+dwMemPos, dwMemLength-dwMemPos, &Ins[iSmp])) break; + dwMemPos += ReadSample(&Ins[iSmp], flags, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); } return TRUE; } @@ -307,11 +314,12 @@ typedef struct AMS2SAMPLE #pragma pack() + BOOL CSoundFile::ReadAMS2(LPCBYTE lpStream, DWORD dwMemLength) //------------------------------------------------------------ { const AMS2FILEHEADER *pfh = (AMS2FILEHEADER *)lpStream; - AMS2SONGHEADER *psh; + const AMS2SONGHEADER *psh; DWORD dwMemPos; BYTE smpmap[16]; BYTE packedsamples[MAX_SAMPLES]; @@ -337,19 +345,23 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre if (psh->flags & 0x40) m_dwSongFlags |= SONG_LINEARSLIDES; for (UINT nIns=1; nIns<=m_nInstruments; nIns++) { + if (dwMemPos >= dwMemLength) return TRUE; UINT insnamelen = lpStream[dwMemPos]; - CHAR *pinsname = (CHAR *)(lpStream+dwMemPos+1); + const CHAR *pinsname = (CHAR *)(lpStream+dwMemPos+1); dwMemPos += insnamelen + 1; - AMS2INSTRUMENT *pins = (AMS2INSTRUMENT *)(lpStream + dwMemPos); + const AMS2INSTRUMENT *pins = (AMS2INSTRUMENT *)(lpStream + dwMemPos); dwMemPos += sizeof(AMS2INSTRUMENT); - if (dwMemPos + 1024 >= dwMemLength) return TRUE; - AMS2ENVELOPE *volenv, *panenv, *pitchenv; + const AMS2ENVELOPE *volenv, *panenv, *pitchenv; + if (dwMemPos + sizeof(AMS2ENVELOPE) > dwMemLength) return TRUE; volenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); dwMemPos += 5 + volenv->points*3; + if (dwMemPos + sizeof(AMS2ENVELOPE) > dwMemLength) return TRUE; panenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); dwMemPos += 5 + panenv->points*3; + if (dwMemPos + sizeof(AMS2ENVELOPE) > dwMemLength) return TRUE; pitchenv = (AMS2ENVELOPE *)(lpStream+dwMemPos); dwMemPos += 5 + pitchenv->points*3; + if (dwMemPos >= dwMemLength) return TRUE; INSTRUMENTHEADER *penv = new INSTRUMENTHEADER; if (!penv) return TRUE; memset(smpmap, 0, sizeof(smpmap)); @@ -389,6 +401,7 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre penv->VolPoints[i] = (WORD)pos; } } + if (dwMemPos + 5 > dwMemLength) return TRUE; penv->nFadeOut = (((lpStream[dwMemPos+2] & 0x0F) << 8) | (lpStream[dwMemPos+1])) << 3; UINT envflags = lpStream[dwMemPos+3]; if (envflags & 0x01) penv->dwFlags |= ENV_VOLLOOP; @@ -398,16 +411,19 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre // Read Samples for (UINT ismp=0; ismp<pins->samples; ismp++) { + if (dwMemPos + 1 > dwMemLength) return TRUE; MODINSTRUMENT *psmp = ((ismp < 16) && (smpmap[ismp])) ? &Ins[smpmap[ismp]] : NULL; UINT smpnamelen = lpStream[dwMemPos]; + if (dwMemPos + smpnamelen + 1 > dwMemLength) return TRUE; if ((psmp) && (smpnamelen) && (smpnamelen <= 22)) { memcpy(m_szNames[smpmap[ismp]], lpStream+dwMemPos+1, smpnamelen); } dwMemPos += smpnamelen + 1; + if (dwMemPos + sizeof(AMS2SAMPLE) > dwMemLength) return TRUE; if (psmp) { - AMS2SAMPLE *pams = (AMS2SAMPLE *)(lpStream+dwMemPos); + const AMS2SAMPLE *pams = (AMS2SAMPLE *)(lpStream+dwMemPos); psmp->nGlobalVol = 64; psmp->nPan = 128; psmp->nLength = pams->length; @@ -547,16 +563,40 @@ BOOL CSoundFile::ReadAMS2(LPCBYTE lpStre if (packedsamples[iSmp] & 0x03) { flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_AMS16 : RS_AMS8; + if (!AMSUnpackCheck(lpStream+dwMemPos, dwMemLength-dwMemPos, &Ins[iSmp])) break; } else { flags = (Ins[iSmp].uFlags & CHN_16BIT) ? RS_PCM16S : RS_PCM8S; } - dwMemPos += ReadSample(&Ins[iSmp], flags, (LPSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); + dwMemPos += ReadSample(&Ins[iSmp], flags, (LPCSTR)(lpStream+dwMemPos), dwMemLength-dwMemPos); } return TRUE; } +// Precheck AMS packed sample size to determine whether or not it could fit the actual size. +static BOOL AMSUnpackCheck(const BYTE *lpStream, DWORD dwMemLength, MODINSTRUMENT *ins) +// ----------------------------------------------------------------------------------- +{ + if (dwMemLength < 9) return FALSE; + DWORD packedbytes = *((DWORD *)(lpStream + 4)); + + DWORD samplebytes = ins->nLength; + if (samplebytes > MAX_SAMPLE_LENGTH) samplebytes = MAX_SAMPLE_LENGTH; + if (ins->uFlags & CHN_16BIT) samplebytes *= 2; + + // RLE can pack a run of up to 255 bytes into 3 bytes. + DWORD packedmin = (samplebytes * 3) >> 8; + if (packedbytes < packedmin) + { + samplebytes = packedbytes * (255 / 3) + 2; + ins->nLength = samplebytes; + if (ins->uFlags & CHN_16BIT) ins->nLength >>= 1; + } + + return TRUE; +} + ///////////////////////////////////////////////////////////////////// // AMS Sample unpacking @@ -564,7 +604,7 @@ void AMSUnpack(const char *psrc, UINT in { UINT tmplen = dmax; signed char *amstmp = new signed char[tmplen]; - + if (!amstmp) return; // Unpack Loop { @@ -575,9 +615,11 @@ void AMSUnpack(const char *psrc, UINT in signed char ch = psrc[i++]; if (ch == packcharacter) { + if (i >= inputlen) break; BYTE ch2 = psrc[i++]; if (ch2) { + if (i >= inputlen) break; ch = psrc[i++]; while (ch2--) { @@ -587,6 +629,12 @@ void AMSUnpack(const char *psrc, UINT in } else p[j++] = packcharacter; } else p[j++] = ch; } + if (j < tmplen) + { + // Truncated or invalid; don't try to unpack this. + delete[] amstmp; + return; + } } // Bit Unpack Loop { ++++++ libmodplug-timidity.patch ++++++ --- /var/tmp/diff_new_pack.M4uu8W/_old 2026-10-21 21:04:00.864488381 +0200 +++ /var/tmp/diff_new_pack.M4uu8W/_new 2026-10-21 21:04:00.867488508 +0200 @@ -1,7 +1,7 @@ Index: src/load_pat.cpp =================================================================== ---- src/load_pat.cpp.orig -+++ src/load_pat.cpp +--- a/src/load_pat.cpp.orig ++++ b/src/load_pat.cpp @@ -59,8 +59,8 @@ typedef UWORD WORD; #define PATHFORPAT "C:\\TIMIDITY\\INSTRUMENTS" #else
