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

Reply via email to