grow libsmacker audio buffer when a frame exceeds the header max_buffer

The Smacker header declares a max audio chunk size, and libsmacker sized its
output buffer from it — then wrote each frame's audio trusting the frame's
own unpacked size, unchecked. The fan-localized intro videos (Chinese among
others) declare max_buffer=2304 but carry ~97KB audio frames: every decode
was a heap overflow, crashing the intro. The original SMACKW32.DLL played
these files, so treat the per-frame size as truth and grow the buffer,
bounded by a 16MB sanity cap; a chunk beyond that fails the frame as corrupt.
Covers both the raw-PCM and DPCM paths.

Verified with an ASan/UBSan harness over all 19 vanilla and Chinese intro
SMKs: previously all 8 Chinese files faulted, now all decode both passes
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Marco Antonio J. Costa
2026-07-28 21:31:13 -03:00
committed by majcosta
co-authored by Claude Fable 5
parent 858c149584
commit 95cf0621a0
+37
View File
@@ -1467,6 +1467,33 @@ static char smk_render_video(struct smk_video_t * s, unsigned char * p, unsigned
return 0;
}
/* Ensure the audio output buffer holds buffer_size bytes. The buffer is
allocated from the header's max_buffer, but some encoders understate it
(fan-localized JA2 intro SMKs declare 2304 and deliver ~97KB frames);
the original SMACKW32.DLL played those fine, so trust the per-frame
size and grow, within a sanity cap against corrupt files. */
static char smk_audio_fit_buffer(struct smk_audio_t * s)
{
void * grown;
if (s->buffer_size <= (unsigned long)s->max_buffer)
return 0;
if (s->buffer_size > 0x1000000) {
fputs("libsmacker::smk_audio_fit_buffer() - ERROR: implausibly large audio chunk.\n", stderr);
return -1;
}
if ((grown = realloc(s->buffer, s->buffer_size)) == NULL) {
perror("libsmacker::smk_audio_fit_buffer() - ERROR: failed to grow audio buffer");
return -1;
}
s->buffer = grown;
s->max_buffer = s->buffer_size;
return 0;
}
/* Decompress audio track i. */
static char smk_render_audio(struct smk_audio_t * s, unsigned char * p, unsigned long size)
{
@@ -1484,6 +1511,11 @@ static char smk_render_audio(struct smk_audio_t * s, unsigned char * p, unsigned
if (!s->compress) {
/* Raw PCM data, update buffer size and perform copy */
s->buffer_size = size;
if (smk_audio_fit_buffer(s) < 0)
goto error;
t = s->buffer;
memcpy(t, p, size);
} else if (s->compress == 1) {
/* SMACKER DPCM compression */
@@ -1498,6 +1530,11 @@ static char smk_render_audio(struct smk_audio_t * s, unsigned char * p, unsigned
((unsigned int) p[2] << 16) |
((unsigned int) p[1] << 8) |
((unsigned int) p[0]);
if (smk_audio_fit_buffer(s) < 0)
goto error;
t = s->buffer;
p += 4;
size -= 4;
/* Compressed audio: must unpack here */