Commit message: Added minimum size check for WAV header block lengh values
CVE: CVE-2018-1000223
CWE: CWE-119
Vulnerability description: soundtouch version up to and including 2.0.0 contains a Buffer Overflow vulnerability in SoundStretch/WavFile.cpp:WavInFile::readHeaderBlock() that can result in arbitrary code execution. This attack appear to be exploitable via victim must open maliocius file in soundstretch utility.

Project: soundtouch
File: WavFile.cpp

WavInFile::~WavInFile()
{
    if (fptr) fclose(fptr);
    fptr = NULL;
}

Project: soundtouch
File: WavFile.cpp

void WavInFile::rewind()
{
    int hdrsOk;

    fseek(fptr, 0, SEEK_SET);
    hdrsOk = readWavHeaders();
    assert(hdrsOk == 0);
    dataRead = 0;
}

Project: soundtouch
File: WavFile.cpp

    static inline int _swap32(int &dwData)
    {
        dwData = ((dwData >> 24) & 0x000000FF) | 
               ((dwData >> 8)  & 0x0000FF00) | 
               ((dwData << 8)  & 0x00FF0000) | 
               ((dwData << 24) & 0xFF000000);
        return dwData;
    }

Project: soundtouch
File: WavFile.cpp

int WavInFile::eof() const
{
    // return true if all data has been read or file eof has reached
    return (dataRead == header.data.data_len || feof(fptr));
}

Project: soundtouch
File: WavFile.cpp

WavFileBase::~WavFileBase()
{
    delete[] convBuff;
    convBuffSize = 0;
}

Project: soundtouch
File: WavFile.cpp

    static inline void _swap16Buffer(short *pData, int numBytes)
    {
        // do nothing
    }

Project: soundtouch
File: WavFile.cpp

void WavInFile::init()
{
    int hdrsOk;

    // assume file stream is already open
    assert(fptr);

    // Read the file headers
    hdrsOk = readWavHeaders();
    if (hdrsOk != 0) 
    {
        // Something didn't match in the wav file headers 
        ST_THROW_RT_ERROR("Input file is corrupt or not a WAV file");
    }

    // sanity check for format parameters
    if ((header.format.channel_number < 1)  || (header.format.channel_number > 9) ||
        (header.format.sample_rate < 4000)  || (header.format.sample_rate > 192000) ||
        (header.format.byte_per_sample < 1) || (header.format.byte_per_sample > 320) ||
        (header.format.bits_per_sample < 8) || (header.format.bits_per_sample > 32))
    {
        // Something didn't match in the wav file headers 
        ST_THROW_RT_ERROR("Error: Illegal wav file header format parameters.");
    }

    /* Ignore 'fixed' field value as 32bit signed linear data can have other value than 1.
    if (header.format.fixed != 1)
    {
        string msg = "Input file uses unsupported encoding.";
        ST_THROW_RT_ERROR(msg.c_str());
    }
    */

    dataRead = 0;
}

Project: soundtouch
File: WavFile.cpp

int WavInFile::read(short *buffer, int maxElems)
{
    unsigned int afterDataRead;
    int numBytes;
    int numElems;

    assert(buffer);
    switch (header.format.bits_per_sample)
    {
        case 8:
        {
            // 8 bit format
            unsigned char *temp = (unsigned char*)getConvBuffer(maxElems);
            int i;

            numElems = read(temp, maxElems);
            // convert from 8 to 16 bit
            for (i = 0; i < numElems; i ++)
            {
                buffer[i] = (short)(((short)temp[i] - 128) * 256);
            }
            break;
        }

        case 16:
        {
            // 16 bit format

            assert(sizeof(short) == 2);

            numBytes = maxElems * 2;
            afterDataRead = dataRead + numBytes;
            if (afterDataRead > header.data.data_len) 
            {
                // Don't read more samples than are marked available in header
                numBytes = (int)header.data.data_len - (int)dataRead;
                assert(numBytes >= 0);
            }

            numBytes = (int)fread(buffer, 1, numBytes, fptr);
            dataRead += numBytes;
            numElems = numBytes / 2;

            // 16bit samples, swap byte order if necessary
            _swap16Buffer((short *)buffer, numElems);
            break;
        }

        default:
        {
            stringstream ss;
            ss << "\nOnly 8/16 bit sample WAV files supported in integer compilation. Can't open WAV file with ";
            ss << (int)header.format.bits_per_sample;
            ss << " bit sample format. ";
            ST_THROW_RT_ERROR(ss.str().c_str());
        }
    };

    return numElems;
}

Project: soundtouch
File: WavFile.cpp

    static inline short _swap16(short &wData)
    {
        wData = ((wData >> 8) & 0x00FF) | 
                ((wData << 8) & 0xFF00);
        return wData;
    }

Project: soundtouch
File: WavFile.cpp

WavOutFile::WavOutFile(FILE *file, int sampleRate, int bits, int channels)
{
    bytesWritten = 0;
    fptr = file;
    if (fptr == NULL) 
    {
        string msg = "Error : Unable to access output file stream.";
        ST_THROW_RT_ERROR(msg.c_str());
    }

    fillInHeader(sampleRate, bits, channels);
    writeHeader();
}

Project: soundtouch
File: WavFile.cpp

WavFileBase::WavFileBase()
{
    convBuff = NULL;
    convBuffSize = 0;
}

Project: soundtouch
File: WavFile.cpp

    static inline short _swap16(short &wData)
    {
        // do nothing
        return wData;
    }

Project: soundtouch
File: WavFile.cpp

inline int saturate(float fvalue, float minval, float maxval)
{
    if (fvalue > maxval) 
    {
        fvalue = maxval;
    } 
    else if (fvalue < minval)
    {
        fvalue = minval;
    }
    return (int)fvalue;
}

Project: soundtouch
File: WavFile.cpp

WavOutFile::WavOutFile(const char *fileName, int sampleRate, int bits, int channels)
{
    bytesWritten = 0;
    fptr = fopen(fileName, "wb");
    if (fptr == NULL) 
    {
        string msg = "Error : Unable to open file \"";
        msg += fileName;
        msg += "\" for writing.";
        //pmsg = msg.c_str;
        ST_THROW_RT_ERROR(msg.c_str());
    }

    fillInHeader(sampleRate, bits, channels);
    writeHeader();
}

Project: soundtouch
File: WavFile.cpp

int WavInFile::checkCharTags() const
{
    // header.format.fmt should equal to 'fmt '
    if (memcmp(fmtStr, header.format.fmt, 4) != 0) return -1;
    // header.data.data_field should equal to 'data'
    if (memcmp(dataStr, header.data.data_field, 4) != 0) return -1;

    return 0;
}

Project: soundtouch
File: WavFile.cpp

static int isAlpha(char c)
{
    return (c >= ' ' && c <= 'z') ? 1 : 0;
}

Project: soundtouch
File: WavFile.cpp

WavOutFile::~WavOutFile()
{
    finishHeader();
    if (fptr) fclose(fptr);
    fptr = NULL;
}

Project: soundtouch
File: WavFile.cpp

void WavOutFile::writeHeader()
{
    WavHeader hdrTemp;
    int res;

    // swap byte order if necessary
    hdrTemp = header;
    _swap32((int &)hdrTemp.riff.package_len);
    _swap32((int &)hdrTemp.format.format_len);
    _swap16((short &)hdrTemp.format.fixed);
    _swap16((short &)hdrTemp.format.channel_number);
    _swap32((int &)hdrTemp.format.sample_rate);
    _swap32((int &)hdrTemp.format.byte_rate);
    _swap16((short &)hdrTemp.format.byte_per_sample);
    _swap16((short &)hdrTemp.format.bits_per_sample);
    _swap32((int &)hdrTemp.data.data_len);
    _swap32((int &)hdrTemp.fact.fact_len);
    _swap32((int &)hdrTemp.fact.fact_sample_len);
    
    // write the supplemented header in the beginning of the file
    fseek(fptr, 0, SEEK_SET);
    res = (int)fwrite(&hdrTemp, sizeof(hdrTemp), 1, fptr);
    if (res != 1)
    {
        ST_THROW_RT_ERROR("Error while writing to a wav file.");
    }

    // jump back to the end of the file
    fseek(fptr, 0, SEEK_END);
}

Project: soundtouch
File: WavFile.cpp

int WavInFile::readWavHeaders()
{
    int res;

    memset(&header, 0, sizeof(header));

    res = readRIFFBlock();
    if (res) return 1;
    // read header blocks until data block is found
    do
    {
        // read header blocks
        res = readHeaderBlock();
        if (res < 0) return 1;  // error in file structure
    } while (res == 0);
    // check that all required tags are legal
    return checkCharTags();
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getSampleRate() const
{
    return header.format.sample_rate;
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getNumChannels() const
{
    return header.format.channel_number;
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getDataSizeInBytes() const
{
    return header.data.data_len;
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getBytesPerSample() const
{
    return getNumChannels() * getNumBits() / 8;
}

Project: soundtouch
File: WavFile.cpp

WavInFile::WavInFile(FILE *file)
{
    // Try to open the file for reading
    fptr = file;
    if (!file) 
    {
        // didn't succeed
        string msg = "Error : Unable to access input stream for reading";
        ST_THROW_RT_ERROR(msg.c_str());
    }

    init();
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getLengthMS() const
{
    double numSamples;
    double sampleRate;

    numSamples = (double)getNumSamples();
    sampleRate = (double)getSampleRate();

    return (uint)(1000.0 * numSamples / sampleRate + 0.5);
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getNumBits() const
{
    return header.format.bits_per_sample;
}

Project: soundtouch
File: WavFile.cpp

static int isAlphaStr(const char *str)
{
    char c;

    c = str[0];
    while (c) 
    {
        if (isAlpha(c) == 0) return 0;
        str ++;
        c = str[0];
    }

    return 1;
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getNumSamples() const
{
    if (header.format.byte_per_sample == 0) return 0;
    if (header.format.fixed > 1) return header.fact.fact_sample_len;
    return header.data.data_len / (unsigned short)header.format.byte_per_sample;
}

Project: soundtouch
File: WavFile.cpp

int WavInFile::read(unsigned char *buffer, int maxElems)
{
    int numBytes;
    uint afterDataRead;

    // ensure it's 8 bit format
    if (header.format.bits_per_sample != 8)
    {
        ST_THROW_RT_ERROR("Error: WavInFile::read(char*, int) works only with 8bit samples.");
    }
    assert(sizeof(char) == 1);

    numBytes = maxElems;
    afterDataRead = dataRead + numBytes;
    if (afterDataRead > header.data.data_len) 
    {
        // Don't read more samples than are marked available in header
        numBytes = (int)header.data.data_len - (int)dataRead;
        assert(numBytes >= 0);
    }

    assert(buffer);
    numBytes = (int)fread(buffer, 1, numBytes, fptr);
    dataRead += numBytes;

    return numBytes;
}

Project: soundtouch
File: WavFile.cpp

    static inline int _swap32(int &dwData)
    {
        // do nothing
        return dwData;
    }

Project: soundtouch
File: WavFile.cpp

    static inline void _swap16Buffer(short *pData, int numWords)
    {
        int i;

        for (i = 0; i < numWords; i ++)
        {
            pData[i] = _swap16(pData[i]);
        }
    }

Project: soundtouch
File: WavFile.cpp

int WavInFile::readRIFFBlock()
{
    if (fread(&(header.riff), sizeof(WavRiff), 1, fptr) != 1) return -1;

    // swap 32bit data byte order if necessary
    _swap32((int &)header.riff.package_len);

    // header.riff.riff_char should equal to 'RIFF');
    if (memcmp(riffStr, header.riff.riff_char, 4) != 0) return -1;
    // header.riff.wave should equal to 'WAVE'
    if (memcmp(waveStr, header.riff.wave, 4) != 0) return -1;

    return 0;
}

Project: soundtouch
File: WavFile.cpp

int WavInFile::read(float *buffer, int maxElems)
{
    unsigned int afterDataRead;
    int numBytes;
    int numElems;
    int bytesPerSample;

    assert(buffer);

    bytesPerSample = header.format.bits_per_sample / 8;
    if ((bytesPerSample < 1) || (bytesPerSample > 4))
    {
        stringstream ss;
        ss << "\nOnly 8/16/24/32 bit sample WAV files supported. Can't open WAV file with ";
        ss << (int)header.format.bits_per_sample;
        ss << " bit sample format. ";
        ST_THROW_RT_ERROR(ss.str().c_str());
    }

    numBytes = maxElems * bytesPerSample;
    afterDataRead = dataRead + numBytes;
    if (afterDataRead > header.data.data_len) 
    {
        // Don't read more samples than are marked available in header
        numBytes = (int)header.data.data_len - (int)dataRead;
        assert(numBytes >= 0);
    }

    // read raw data into temporary buffer
    char *temp = (char*)getConvBuffer(numBytes);
    numBytes = (int)fread(temp, 1, numBytes, fptr);
    dataRead += numBytes;

    numElems = numBytes / bytesPerSample;

    // swap byte ordert & convert to float, depending on sample format
    switch (bytesPerSample)
    {
        case 1:
        {
            unsigned char *temp2 = (unsigned char*)temp;
            double conv = 1.0 / 128.0;
            for (int i = 0; i < numElems; i ++)
            {
                buffer[i] = (float)(temp2[i] * conv - 1.0);
            }
            break;
        }

        case 2:
        {
            short *temp2 = (short*)temp;
            double conv = 1.0 / 32768.0;
            for (int i = 0; i < numElems; i ++)
            {
                short value = temp2[i];
                buffer[i] = (float)(_swap16(value) * conv);
            }
            break;
        }

        case 3:
        {
            char *temp2 = (char *)temp;
            double conv = 1.0 / 8388608.0;
            for (int i = 0; i < numElems; i ++)
            {
                int value = *((int*)temp2);
                value = _swap32(value) & 0x00ffffff;             // take 24 bits
                value |= (value & 0x00800000) ? 0xff000000 : 0;  // extend minus sign bits
                buffer[i] = (float)(value * conv);
                temp2 += 3;
            }
            break;
        }

        case 4:
        {
            int *temp2 = (int *)temp;
            double conv = 1.0 / 2147483648.0;
            assert(sizeof(int) == 4);
            for (int i = 0; i < numElems; i ++)
            {
                int value = temp2[i];
                buffer[i] = (float)(_swap32(value) * conv);
            }
            break;
        }
    }

    return numElems;
}

Project: soundtouch
File: WavFile.cpp

uint WavInFile::getElapsedMS() const
{
    return (uint)(1000.0 * (double)dataRead / (double)header.format.byte_rate);
}

Project: soundtouch
File: WavFile.cpp

void *WavFileBase::getConvBuffer(int sizeBytes)
{
    if (convBuffSize < sizeBytes)
    {
        delete[] convBuff;

        convBuffSize = (sizeBytes + 15) & -8;   // round up to following 8-byte bounday
        convBuff = new char[convBuffSize];
    }
    return convBuff;
}

Project: soundtouch
File: WavFile.cpp

WavInFile::WavInFile(const char *fileName)
{
    // Try to open the file for reading
    fptr = fopen(fileName, "rb");
    if (fptr == NULL) 
    {
        // didn't succeed
        string msg = "Error : Unable to open file \"";
        msg += fileName;
        msg += "\" for reading.";
        ST_THROW_RT_ERROR(msg.c_str());
    }

    init();
}