September 26, 2026
How 5 Characters of C Code Put Millions of Apps at Risk
A technical breakdown of CVE-2026–89266 and CVE-2026–75904, two memory safety bugs in audio libraries that millions of applications…
By Perparim Mjeku
4 min read
A technical breakdown of CVE-2026–89266 and CVE-2026–75904, two memory safety bugs in audio libraries that millions of applications silently depend on, how they work at the code level, and why the blast radius is wider than expected.
Somewhere in the dependency tree of most game engines, media converters, and audio tools sits a single C file called stb_vorbis.c. It decodes Ogg Vorbis audio. It has no build system, no package manager entry, no update mechanism. It gets copied into projects and forgotten.
That file contains a heap buffer overflow triggered by a single crafted .ogg file. CVSS 8.2. The root cause is five characters wide: a cast from size_t to int.
A second bug, in a different audio library, libmodplug — does something subtler. A missing lower-bound check lets a 32-byte MIDI file read one byte of memory it shouldn't. Lower severity, but the same pattern: a C library parsing untrusted binary input, with arithmetic that was never quite right.
Both bugs were hiding in plain sight. Here's how they work.
CVE-2026–89266: A Truncated Integer Turns Audio Playback Into Heap Corruption
CVSS 8.2 — HIGH
Library: stb_vorbis ≤ 1.22 · Weakness: CWE-190 · Disclosed: Sep 12, 2026 · Patched: v1.23
The Mechanism
The start_decoder() function parses a Vorbis stream's codebook header. Two fields — entriesand dimensions — are read directly from the stream data and multiplied together to determine how much memory to allocate for codebook multiplicands.
The product is computed as a size_t (64-bit on most modern systems). But before reaching the allocator, it gets truncated to a signed int (32-bit). When the product exceeds INT_MAX, the value wraps. The allocator receives a much smaller number. A tiny buffer gets allocated. The decoder then writes the full, un-truncated amount of data into it.
// The vulnerable path, simplified:
size_t total = entries * dimensions;
int alloc_size = (int)total; // ← truncation
c->multiplicands = malloc(alloc_size * sizeof(codetype));
for (int i = 0; i < entries * dimensions; i++)
c->multiplicands[i] = /* decoded value */; // writes past end// The vulnerable path, simplified:
size_t total = entries * dimensions;
int alloc_size = (int)total; // ← truncation
c->multiplicands = malloc(alloc_size * sizeof(codetype));
for (int i = 0; i < entries * dimensions; i++)
c->multiplicands[i] = /* decoded value */; // writes past endBoth entries and dimensions come from the Vorbis stream. An attacker controls them. The overflow geometry — how far past the buffer the writes go and what data lands there — is fully controllable.
Impact
This is not a theoretical crash. Heap buffer overflows with controlled write data are a well-understood path to arbitrary code execution. The attacker shapes the heap layout by choosing entries and dimensions values that wrap to a specific small integer, then writes decoded codebook values over adjacent heap structures — function pointers, vtable entries, allocator metadata.
The trigger is the simplest possible attack surface: open an audio file. Any application that decodes untrusted .ogg files through stb_vorbis ≤ 1.22 is exposed. Game asset pipelines, media upload handlers, audio preview features, voice chat decoders, mod loading systems.
Why the Blast Radius Is Enormous
stb_vorbis has no package manager. No update channel. No way to know who's using it. Every project that copied
stb_vorbis.cinto its source tree must find and replace the file manually.
The STB library collection is one of the most widely-vendored pieces of open-source C code in existence. The nothings/stb repository on GitHub has tens of thousands of stars, but that number drastically understates real-world adoption. The entire value proposition of STB is "copy this one file into your project." Most consumers did exactly that — years ago — and never looked back.
The library shows up in indie game engines, commercial audio tools, Android and iOS apps, embedded devices, CI pipelines that process user-uploaded media, and countless internal tools. There is no apt upgrade that fixes this. Each project is its own island.
CVE-2026–75904: A 32-Byte MIDI File Reads Memory It Shouldn't
CVSS 3.3 — LOW
Library: libmodplug ≤ 0.8.9.1 · Weakness: CWE-125 · Disclosed: Aug 18, 2026 · **Patched:**v0.8.9.2
The Mechanism
The pat_smplooped() function in src/load_pat.cpp looks up a sample number in a 191-byte static array called pat_loops to decide whether a note should loop. The bounds check only validates the upper limit — the index must not exceed MAXSMP. Then it subtracts one.
static int pat_smplooped(int smpno) {
if (smpno >= MAXSMP) return 0;
return pat_loops[smpno - 1]; // smpno=0 → pat_loops[-1]
}static int pat_smplooped(int smpno) {
if (smpno >= MAXSMP) return 0;
return pat_loops[smpno - 1]; // smpno=0 → pat_loops[-1]
}When smpno is zero, the access becomes pat_loops[-1]: one byte before the array. And zero isn't an edge case — it's the default. The smpno field of a parsed MIDI event starts at zero and only gets overwritten when a program-change message assigns an instrument. Any note event that fires before that assignment carries a zero index straight into this function.
A MIDI file as small as 32 bytes is enough to walk the path: ModPlug_Load → CSoundFile::ReadMID → MID_ReadPatterns → pat_smplooped(0).
Impact
This is an out-of-bounds read, not a write. It reads exactly one byte from whatever static storage the linker placed adjacent to pat_loops. No code execution. No crash (usually). The leaked byte determines whether a note loops, so adjacent static data silently influences playback behavior.
The severity is low, and appropriately so. But "low severity" and "no consequence" are different things. In an information-disclosure context, even a single-byte read primitive has value — especially one that doesn't crash, leaving no trace it ever fired. It can serve as a building block in a larger chain.
Blast Radius
Unlike stb_vorbis, libmodplug is a proper system package. It ships on Debian, Ubuntu, Fedora, and Gentoo. It's a dependency of GStreamer's module playback pipeline, which means default music players, file manager audio previews, and media indexing daemons all inherit the bug.
The good news: the fix propagates through normal package manager channels. Most Linux systems will pick it up through routine updates. The attack surface is also narrower — MIDI and tracker module files are niche formats. But for the environments that do process them (game development, demoscene archives, retro music platforms, media conversion pipelines), libmodplug is the standard library.
The Pattern Worth Noticing
These two bugs look unrelated on the surface. Different libraries, different severity, different exploitation potential. But the underlying pattern is the same: a C library parsing an untrusted binary format, with arithmetic that was never validated against the full range of its inputs.
In one case, a type narrowing from size_t to int turns a large allocation into a small one. In the other, a missing > 0 check lets a default zero walk past a bounds guard. Neither bug is complex. Both sat in widely-shipped code for years.
Media parsers run early in the processing pipeline, on attacker-supplied input, often with no sandboxing. The blast radius of a bug in one of these libraries isn't determined by the library's name recognition — it's determined by how many applications silently linked against it and never looked at it again.
CVE-2026–89266 · CVE-2026–75904 · Disclosed responsibly · Details from NVD, VulDB, and upstream advisories.