11/*
22WAV audio loader and writer. Choice of public domain or MIT-0. See license statements at the end of this file.
3- dr_wav - v0.14.0 - 2025-07-23
3+ dr_wav - v0.14.5 - 2026-03-03
44
55David Reid - mackron@gmail.com
66
@@ -147,7 +147,7 @@ extern "C" {
147147
148148#define DRWAV_VERSION_MAJOR 0
149149#define DRWAV_VERSION_MINOR 14
150- #define DRWAV_VERSION_REVISION 0
150+ #define DRWAV_VERSION_REVISION 5
151151#define DRWAV_VERSION_STRING DRWAV_XSTRINGIFY(DRWAV_VERSION_MAJOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_MINOR) "." DRWAV_XSTRINGIFY(DRWAV_VERSION_REVISION)
152152
153153#include <stddef.h> /* For size_t. */
@@ -2193,6 +2193,22 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars
21932193
21942194 if (pMetadata != NULL && bytesJustRead == sizeof (smplHeaderData )) {
21952195 drwav_uint32 iSampleLoop ;
2196+ drwav_uint32 loopCount ;
2197+ drwav_uint32 calculatedLoopCount ;
2198+
2199+ /*
2200+ When we calcualted the amount of memory required for the "smpl" chunk we excluded the chunk entirely
2201+ if the loop count in the header did not match with the calculated count based on the size of the
2202+ chunk. When this happens, the second stage will still hit this path but the `pMetadata` will be
2203+ non-null, but will either be pointing at the very end of the allocation or at the start of another
2204+ chunk. We need to check the loop counts for consistency *before* dereferencing the pMetadata object
2205+ so it's consistent with how we do it in the first stage.
2206+ */
2207+ loopCount = drwav_bytes_to_u32 (smplHeaderData + 28 );
2208+ calculatedLoopCount = (pChunkHeader -> sizeInBytes - DRWAV_SMPL_BYTES ) / DRWAV_SMPL_LOOP_BYTES ;
2209+ if (loopCount != calculatedLoopCount ) {
2210+ return totalBytesRead ;
2211+ }
21962212
21972213 pMetadata -> type = drwav_metadata_type_smpl ;
21982214 pMetadata -> data .smpl .manufacturerId = drwav_bytes_to_u32 (smplHeaderData + 0 );
@@ -2209,7 +2225,7 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars
22092225 The loop count needs to be validated against the size of the chunk for safety so we don't
22102226 attempt to read over the boundary of the chunk.
22112227 */
2212- if (pMetadata -> data .smpl .sampleLoopCount == ( pChunkHeader -> sizeInBytes - DRWAV_SMPL_BYTES ) / DRWAV_SMPL_LOOP_BYTES ) {
2228+ if (pMetadata -> data .smpl .sampleLoopCount == calculatedLoopCount ) {
22132229 pMetadata -> data .smpl .pLoops = (drwav_smpl_loop * )drwav__metadata_get_memory (pParser , sizeof (drwav_smpl_loop ) * pMetadata -> data .smpl .sampleLoopCount , DRWAV_METADATA_ALIGNMENT );
22142230
22152231 for (iSampleLoop = 0 ; iSampleLoop < pMetadata -> data .smpl .sampleLoopCount ; ++ iSampleLoop ) {
@@ -2234,6 +2250,15 @@ DRWAV_PRIVATE drwav_uint64 drwav__read_smpl_to_metadata_obj(drwav__metadata_pars
22342250
22352251 drwav__metadata_parser_read (pParser , pMetadata -> data .smpl .pSamplerSpecificData , pMetadata -> data .smpl .samplerSpecificDataSizeInBytes , & totalBytesRead );
22362252 }
2253+ } else {
2254+ /*
2255+ Getting here means the loop count in the header does not match up with the size of the
2256+ chunk. Clear out the data to zero just to be safe.
2257+
2258+ This should never actually get hit because we check for it above, but keeping this here
2259+ for added safety.
2260+ */
2261+ DRWAV_ZERO_OBJECT (& pMetadata -> data .smpl );
22372262 }
22382263 }
22392264
@@ -5289,7 +5314,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_tell_stdio(void* pUserData, drwav_int64* pC
52895314 DRWAV_ASSERT (pFileStdio != NULL );
52905315 DRWAV_ASSERT (pCursor != NULL );
52915316
5292- #if defined(_WIN32 )
5317+ #if defined(_WIN32 ) && !defined( NXDK )
52935318 #if defined(_MSC_VER ) && _MSC_VER > 1200
52945319 result = _ftelli64 (pFileStdio );
52955320 #else
@@ -5496,8 +5521,6 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory(void* pUserData, int offset, dr
54965521
54975522 DRWAV_ASSERT (pWav != NULL );
54985523
5499- newCursor = pWav -> memoryStream .currentReadPos ;
5500-
55015524 if (origin == DRWAV_SEEK_SET ) {
55025525 newCursor = 0 ;
55035526 } else if (origin == DRWAV_SEEK_CUR ) {
@@ -5570,8 +5593,6 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offs
55705593
55715594 DRWAV_ASSERT (pWav != NULL );
55725595
5573- newCursor = pWav -> memoryStreamWrite .currentWritePos ;
5574-
55755596 if (origin == DRWAV_SEEK_SET ) {
55765597 newCursor = 0 ;
55775598 } else if (origin == DRWAV_SEEK_CUR ) {
@@ -5580,7 +5601,7 @@ DRWAV_PRIVATE drwav_bool32 drwav__on_seek_memory_write(void* pUserData, int offs
55805601 newCursor = (drwav_int64 )pWav -> memoryStreamWrite .dataSize ;
55815602 } else {
55825603 DRWAV_ASSERT (!"Invalid seek origin" );
5583- return DRWAV_INVALID_ARGS ;
5604+ return DRWAV_FALSE ;
55845605 }
55855606
55865607 newCursor += offset ;
@@ -6296,7 +6317,7 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
62966317 pWav -> msadpcm .cachedFrameCount = 2 ;
62976318
62986319 /* The predictor is used as an index into coeff1Table so we'll need to validate to ensure it never overflows. */
6299- if (pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff1Table )) {
6320+ if (pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff1Table ) || pWav -> msadpcm . predictor [ 0 ] >= drwav_countof ( coeff2Table ) ) {
63006321 return totalFramesRead ; /* Invalid file. */
63016322 }
63026323 } else {
@@ -6323,7 +6344,8 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
63236344 pWav -> msadpcm .cachedFrameCount = 2 ;
63246345
63256346 /* The predictor is used as an index into coeff1Table so we'll need to validate to ensure it never overflows. */
6326- if (pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff1Table ) || pWav -> msadpcm .predictor [1 ] >= drwav_countof (coeff2Table )) {
6347+ if (pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff1Table ) || pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff2Table ) ||
6348+ pWav -> msadpcm .predictor [1 ] >= drwav_countof (coeff1Table ) || pWav -> msadpcm .predictor [1 ] >= drwav_countof (coeff2Table )) {
63276349 return totalFramesRead ; /* Invalid file. */
63286350 }
63296351 }
@@ -6377,14 +6399,16 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
63776399 drwav_int32 newSample0 ;
63786400 drwav_int32 newSample1 ;
63796401
6402+ /* The predictor is read from the file and then indexed into a table. Check that it's in bounds. */
6403+ if (pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff1Table ) || pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff2Table )) {
6404+ return totalFramesRead ;
6405+ }
6406+
63806407 newSample0 = ((pWav -> msadpcm .prevFrames [0 ][1 ] * coeff1Table [pWav -> msadpcm .predictor [0 ]]) + (pWav -> msadpcm .prevFrames [0 ][0 ] * coeff2Table [pWav -> msadpcm .predictor [0 ]])) >> 8 ;
63816408 newSample0 += nibble0 * pWav -> msadpcm .delta [0 ];
63826409 newSample0 = drwav_clamp (newSample0 , -32768 , 32767 );
63836410
6384- pWav -> msadpcm .delta [0 ] = (adaptationTable [((nibbles & 0xF0 ) >> 4 )] * pWav -> msadpcm .delta [0 ]) >> 8 ;
6385- if (pWav -> msadpcm .delta [0 ] < 16 ) {
6386- pWav -> msadpcm .delta [0 ] = 16 ;
6387- }
6411+ pWav -> msadpcm .delta [0 ] = (drwav_int32 )drwav_clamp (((drwav_int64 )adaptationTable [((nibbles & 0xF0 ) >> 4 )] * pWav -> msadpcm .delta [0 ]) >> 8 , 16 , 0x7FFFFFFF );
63886412
63896413 pWav -> msadpcm .prevFrames [0 ][0 ] = pWav -> msadpcm .prevFrames [0 ][1 ];
63906414 pWav -> msadpcm .prevFrames [0 ][1 ] = newSample0 ;
@@ -6394,15 +6418,11 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
63946418 newSample1 += nibble1 * pWav -> msadpcm .delta [0 ];
63956419 newSample1 = drwav_clamp (newSample1 , -32768 , 32767 );
63966420
6397- pWav -> msadpcm .delta [0 ] = (adaptationTable [((nibbles & 0x0F ) >> 0 )] * pWav -> msadpcm .delta [0 ]) >> 8 ;
6398- if (pWav -> msadpcm .delta [0 ] < 16 ) {
6399- pWav -> msadpcm .delta [0 ] = 16 ;
6400- }
6421+ pWav -> msadpcm .delta [0 ] = (drwav_int32 )drwav_clamp (((drwav_int64 )adaptationTable [((nibbles & 0x0F ) >> 0 )] * pWav -> msadpcm .delta [0 ]) >> 8 , 16 , 0x7FFFFFFF );
64016422
64026423 pWav -> msadpcm .prevFrames [0 ][0 ] = pWav -> msadpcm .prevFrames [0 ][1 ];
64036424 pWav -> msadpcm .prevFrames [0 ][1 ] = newSample1 ;
64046425
6405-
64066426 pWav -> msadpcm .cachedFrames [2 ] = newSample0 ;
64076427 pWav -> msadpcm .cachedFrames [3 ] = newSample1 ;
64086428 pWav -> msadpcm .cachedFrameCount = 2 ;
@@ -6412,28 +6432,30 @@ DRWAV_PRIVATE drwav_uint64 drwav_read_pcm_frames_s16__msadpcm(drwav* pWav, drwav
64126432 drwav_int32 newSample1 ;
64136433
64146434 /* Left. */
6435+ if (pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff1Table ) || pWav -> msadpcm .predictor [0 ] >= drwav_countof (coeff2Table )) {
6436+ return totalFramesRead ; /* Out of bounds. Invalid file. */
6437+ }
6438+
64156439 newSample0 = ((pWav -> msadpcm .prevFrames [0 ][1 ] * coeff1Table [pWav -> msadpcm .predictor [0 ]]) + (pWav -> msadpcm .prevFrames [0 ][0 ] * coeff2Table [pWav -> msadpcm .predictor [0 ]])) >> 8 ;
64166440 newSample0 += nibble0 * pWav -> msadpcm .delta [0 ];
64176441 newSample0 = drwav_clamp (newSample0 , -32768 , 32767 );
64186442
6419- pWav -> msadpcm .delta [0 ] = (adaptationTable [((nibbles & 0xF0 ) >> 4 )] * pWav -> msadpcm .delta [0 ]) >> 8 ;
6420- if (pWav -> msadpcm .delta [0 ] < 16 ) {
6421- pWav -> msadpcm .delta [0 ] = 16 ;
6422- }
6443+ pWav -> msadpcm .delta [0 ] = (drwav_int32 )drwav_clamp (((drwav_int64 )adaptationTable [((nibbles & 0xF0 ) >> 4 )] * pWav -> msadpcm .delta [0 ]) >> 8 , 16 , 0x7FFFFFFF );
64236444
64246445 pWav -> msadpcm .prevFrames [0 ][0 ] = pWav -> msadpcm .prevFrames [0 ][1 ];
64256446 pWav -> msadpcm .prevFrames [0 ][1 ] = newSample0 ;
64266447
64276448
64286449 /* Right. */
6450+ if (pWav -> msadpcm .predictor [1 ] >= drwav_countof (coeff1Table ) || pWav -> msadpcm .predictor [1 ] >= drwav_countof (coeff2Table )) {
6451+ return totalFramesRead ; /* Out of bounds. Invalid file. */
6452+ }
6453+
64296454 newSample1 = ((pWav -> msadpcm .prevFrames [1 ][1 ] * coeff1Table [pWav -> msadpcm .predictor [1 ]]) + (pWav -> msadpcm .prevFrames [1 ][0 ] * coeff2Table [pWav -> msadpcm .predictor [1 ]])) >> 8 ;
64306455 newSample1 += nibble1 * pWav -> msadpcm .delta [1 ];
64316456 newSample1 = drwav_clamp (newSample1 , -32768 , 32767 );
64326457
6433- pWav -> msadpcm .delta [1 ] = (adaptationTable [((nibbles & 0x0F ) >> 0 )] * pWav -> msadpcm .delta [1 ]) >> 8 ;
6434- if (pWav -> msadpcm .delta [1 ] < 16 ) {
6435- pWav -> msadpcm .delta [1 ] = 16 ;
6436- }
6458+ pWav -> msadpcm .delta [1 ] = (drwav_int32 )drwav_clamp (((drwav_int64 )adaptationTable [((nibbles & 0x0F ) >> 0 )] * pWav -> msadpcm .delta [1 ]) >> 8 , 16 , 0x7FFFFFFF );
64376459
64386460 pWav -> msadpcm .prevFrames [1 ][0 ] = pWav -> msadpcm .prevFrames [1 ][1 ];
64396461 pWav -> msadpcm .prevFrames [1 ][1 ] = newSample1 ;
@@ -8057,6 +8079,12 @@ DRWAV_PRIVATE drwav_int16* drwav__read_pcm_frames_and_close_s16(drwav* pWav, uns
80578079
80588080 DRWAV_ASSERT (pWav != NULL );
80598081
8082+ /* Check for overflow before multiplication. */
8083+ if (pWav -> channels == 0 || pWav -> totalPCMFrameCount > DRWAV_SIZE_MAX / pWav -> channels / sizeof (drwav_int16 )) {
8084+ drwav_uninit (pWav );
8085+ return NULL ; /* Overflow or invalid channels. */
8086+ }
8087+
80608088 sampleDataSize = pWav -> totalPCMFrameCount * pWav -> channels * sizeof (drwav_int16 );
80618089 if (sampleDataSize > DRWAV_SIZE_MAX ) {
80628090 drwav_uninit (pWav );
@@ -8099,6 +8127,12 @@ DRWAV_PRIVATE float* drwav__read_pcm_frames_and_close_f32(drwav* pWav, unsigned
80998127
81008128 DRWAV_ASSERT (pWav != NULL );
81018129
8130+ /* Check for overflow before multiplication. */
8131+ if (pWav -> channels == 0 || pWav -> totalPCMFrameCount > DRWAV_SIZE_MAX / pWav -> channels / sizeof (float )) {
8132+ drwav_uninit (pWav );
8133+ return NULL ; /* Overflow or invalid channels. */
8134+ }
8135+
81028136 sampleDataSize = pWav -> totalPCMFrameCount * pWav -> channels * sizeof (float );
81038137 if (sampleDataSize > DRWAV_SIZE_MAX ) {
81048138 drwav_uninit (pWav );
@@ -8141,6 +8175,12 @@ DRWAV_PRIVATE drwav_int32* drwav__read_pcm_frames_and_close_s32(drwav* pWav, uns
81418175
81428176 DRWAV_ASSERT (pWav != NULL );
81438177
8178+ /* Check for overflow before multiplication. */
8179+ if (pWav -> channels == 0 || pWav -> totalPCMFrameCount > DRWAV_SIZE_MAX / pWav -> channels / sizeof (drwav_int32 )) {
8180+ drwav_uninit (pWav );
8181+ return NULL ; /* Overflow or invalid channels. */
8182+ }
8183+
81448184 sampleDataSize = pWav -> totalPCMFrameCount * pWav -> channels * sizeof (drwav_int32 );
81458185 if (sampleDataSize > DRWAV_SIZE_MAX ) {
81468186 drwav_uninit (pWav );
@@ -8521,6 +8561,23 @@ DRWAV_API drwav_bool32 drwav_fourcc_equal(const drwav_uint8* a, const char* b)
85218561/*
85228562REVISION HISTORY
85238563================
8564+ v0.14.5 - 2026-03-03
8565+ - Fix a crash when loading files with a malformed "smpl" chunk.
8566+ - Fix a signed overflow bug with the MS-ADPCM decoder.
8567+
8568+ v0.14.4 - 2026-01-17
8569+ - Fix some compilation warnings.
8570+
8571+ v0.14.3 - 2025-12-14
8572+ - Fix a possible out-of-bounds read when reading from MS-ADPCM encoded files.
8573+ - Fix a possible integer overflow error.
8574+
8575+ v0.14.2 - 2025-12-02
8576+ - Fix a compilation warning.
8577+
8578+ v0.14.1 - 2025-09-10
8579+ - Fix an error with the NXDK build.
8580+
85248581v0.14.0 - 2025-07-23
85258582 - API CHANGE: Seek origin enums have been renamed to the following:
85268583 - drwav_seek_origin_start -> DRWAV_SEEK_SET
0 commit comments