Commit message: issue #33, sanitize size of unknown chunks before malloc()
CVE: CVE-2018-10539
CWE: CWE-787
Vulnerability description: An issue was discovered in WavPack 5.1.0 and earlier for DSDiff input. Out-of-bounds writes can occur because ParseDsdiffHeaderConfig in dsdiff.c does not validate the sizes of unknown chunks before attempting memory allocation, related to a lack of integer-overflow protection within a bytes_to_copy calculation and subsequent malloc call, leading to insufficient memory allocation.

Project: WavPack
File: dsdiff.c

int WriteDsdiffHeader (FILE *outfile, WavpackContext *wpc, int64_t total_samples, int qmode)
{
    uint32_t chan_mask = WavpackGetChannelMask (wpc);
    int num_channels = WavpackGetNumChannels (wpc);
    DFFFileHeader file_header, prop_header;
    DFFChunkHeader data_header;
    DFFVersionChunk ver_chunk;
    DFFSampleRateChunk fs_chunk;
    DFFChannelsHeader chan_header;
    DFFCompressionHeader cmpr_header;
    char *cmpr_name = "\016not compressed", *chan_ids;
    int64_t file_size, prop_chunk_size, data_size;
    int cmpr_name_size, chan_ids_size;
    uint32_t bcount;

    if (debug_logging_mode)
        error_line ("WriteDsdiffHeader (), total samples = %lld, qmode = 0x%02x\n",
            (long long) total_samples, qmode);

    cmpr_name_size = (strlen (cmpr_name) + 1) & ~1;
    chan_ids_size = num_channels * 4;
    chan_ids = malloc (chan_ids_size);

    if (chan_ids) {
        uint32_t scan_mask = 0x1;
        char *cptr = chan_ids;
        int ci, uci = 0;

        for (ci = 0; ci < num_channels; ++ci) {
            while (scan_mask && !(scan_mask & chan_mask))
                scan_mask <<= 1;

            if (scan_mask & 0x1)
                memcpy (cptr, num_channels <= 2 ? "SLFT" : "MLFT", 4);
            else if (scan_mask & 0x2)
                memcpy (cptr, num_channels <= 2 ? "SRGT" : "MRGT", 4);
            else if (scan_mask & 0x4)
                memcpy (cptr, "C   ", 4);
            else if (scan_mask & 0x8)
                memcpy (cptr, "LFE ", 4);
            else if (scan_mask & 0x10)
                memcpy (cptr, "LS  ", 4);
            else if (scan_mask & 0x20)
                memcpy (cptr, "RS  ", 4);
            else {
                cptr [0] = 'C';
                cptr [1] = (uci / 100) + '0';
                cptr [2] = ((uci % 100) / 10) + '0';
                cptr [3] = (uci % 10) + '0';
                uci++;
            }

            scan_mask <<= 1;
            cptr += 4;
        }
    }
    else {
        error_line ("can't allocate memory!");
        return FALSE;
    }

    data_size = total_samples * num_channels;
    prop_chunk_size = sizeof (prop_header) + sizeof (fs_chunk) + sizeof (chan_header) + chan_ids_size + sizeof (cmpr_header) + cmpr_name_size;
    file_size = sizeof (file_header) + sizeof (ver_chunk) + prop_chunk_size + sizeof (data_header) + ((data_size + 1) & ~(int64_t)1);

    memcpy (file_header.ckID, "FRM8", 4);
    file_header.ckDataSize = file_size - 12;
    memcpy (file_header.formType, "DSD ", 4);

    memcpy (prop_header.ckID, "PROP", 4);
    prop_header.ckDataSize = prop_chunk_size - 12;
    memcpy (prop_header.formType, "SND ", 4);

    memcpy (ver_chunk.ckID, "FVER", 4);
    ver_chunk.ckDataSize = sizeof (ver_chunk) - 12;
    ver_chunk.version = 0x01050000;

    memcpy (fs_chunk.ckID, "FS  ", 4);
    fs_chunk.ckDataSize = sizeof (fs_chunk) - 12;
    fs_chunk.sampleRate = WavpackGetSampleRate (wpc) * 8;

    memcpy (chan_header.ckID, "CHNL", 4);
    chan_header.ckDataSize = sizeof (chan_header) + chan_ids_size - 12;
    chan_header.numChannels = num_channels;

    memcpy (cmpr_header.ckID, "CMPR", 4);
    cmpr_header.ckDataSize = sizeof (cmpr_header) + cmpr_name_size - 12;
    memcpy (cmpr_header.compressionType, "DSD ", 4);

    memcpy (data_header.ckID, "DSD ", 4);
    data_header.ckDataSize = data_size;

    WavpackNativeToBigEndian (&file_header, DFFFileHeaderFormat);
    WavpackNativeToBigEndian (&ver_chunk, DFFVersionChunkFormat);
    WavpackNativeToBigEndian (&prop_header, DFFFileHeaderFormat);
    WavpackNativeToBigEndian (&fs_chunk, DFFSampleRateChunkFormat);
    WavpackNativeToBigEndian (&chan_header, DFFChannelsHeaderFormat);
    WavpackNativeToBigEndian (&cmpr_header, DFFCompressionHeaderFormat);
    WavpackNativeToBigEndian (&data_header, DFFChunkHeaderFormat);

    if (!DoWriteFile (outfile, &file_header, sizeof (file_header), &bcount) || bcount != sizeof (file_header) ||
        !DoWriteFile (outfile, &ver_chunk, sizeof (ver_chunk), &bcount) || bcount != sizeof (ver_chunk) ||
        !DoWriteFile (outfile, &prop_header, sizeof (prop_header), &bcount) || bcount != sizeof (prop_header) ||
        !DoWriteFile (outfile, &fs_chunk, sizeof (fs_chunk), &bcount) || bcount != sizeof (fs_chunk) ||
        !DoWriteFile (outfile, &chan_header, sizeof (chan_header), &bcount) || bcount != sizeof (chan_header) ||
        !DoWriteFile (outfile, chan_ids, chan_ids_size, &bcount) || bcount != chan_ids_size ||
        !DoWriteFile (outfile, &cmpr_header, sizeof (cmpr_header), &bcount) || bcount != sizeof (cmpr_header) ||
        !DoWriteFile (outfile, cmpr_name, cmpr_name_size, &bcount) || bcount != cmpr_name_size ||
        !DoWriteFile (outfile, &data_header, sizeof (data_header), &bcount) || bcount != sizeof (data_header)) {
            error_line ("can't write .DSF data, disk probably full!");
            free (chan_ids);
            return FALSE;
    }

    free (chan_ids);
    return TRUE;
}

Project: WavPack

int WriteRiffHeader (FILE *outfile, WavpackContext *wpc, int64_t total_samples, int qmode)
{
    int do_rf64 = 0, write_junk = 1, table_length = 0;
    ChunkHeader ds64hdr, datahdr, fmthdr;
    RiffChunkHeader riffhdr;
    DS64Chunk ds64_chunk;
    CS64Chunk cs64_chunk;
    JunkChunk junkchunk;
    WaveHeader wavhdr;
    uint32_t bcount;

    int64_t total_data_bytes, total_riff_bytes;
    int num_channels = WavpackGetNumChannels (wpc);
    int32_t channel_mask = WavpackGetChannelMask (wpc);
    int32_t sample_rate = WavpackGetSampleRate (wpc);
    int bytes_per_sample = WavpackGetBytesPerSample (wpc);
    int bits_per_sample = WavpackGetBitsPerSample (wpc);
    int format = WavpackGetFloatNormExp (wpc) ? 3 : 1;
    int wavhdrsize = 16;

    if (format == 3 && WavpackGetFloatNormExp (wpc) != 127) {
        error_line ("can't create valid RIFF wav header for non-normalized floating data!");
        return FALSE;
    }

    if (total_samples == -1)
        total_samples = 0x7ffff000 / (bytes_per_sample * num_channels);

    total_data_bytes = total_samples * bytes_per_sample * num_channels;

    if (total_data_bytes > 0xff000000) {
        if (debug_logging_mode)
            error_line ("total_data_bytes = %lld, so rf64", total_data_bytes);
        write_junk = 0;
        do_rf64 = 1;
    }
    else if (debug_logging_mode)
        error_line ("total_data_bytes = %lld, so riff", total_data_bytes);

    CLEAR (wavhdr);

    wavhdr.FormatTag = format;
    wavhdr.NumChannels = num_channels;
    wavhdr.SampleRate = sample_rate;
    wavhdr.BytesPerSecond = sample_rate * num_channels * bytes_per_sample;
    wavhdr.BlockAlign = bytes_per_sample * num_channels;
    wavhdr.BitsPerSample = bits_per_sample;

    if (num_channels > 2 || channel_mask != 0x5 - num_channels) {
        wavhdrsize = sizeof (wavhdr);
        wavhdr.cbSize = 22;
        wavhdr.ValidBitsPerSample = bits_per_sample;
        wavhdr.SubFormat = format;
        wavhdr.ChannelMask = channel_mask;
        wavhdr.FormatTag = 0xfffe;
        wavhdr.BitsPerSample = bytes_per_sample * 8;
        wavhdr.GUID [4] = 0x10;
        wavhdr.GUID [6] = 0x80;
        wavhdr.GUID [9] = 0xaa;
        wavhdr.GUID [11] = 0x38;
        wavhdr.GUID [12] = 0x9b;
        wavhdr.GUID [13] = 0x71;
    }

    strncpy (riffhdr.ckID, do_rf64 ? "RF64" : "RIFF", sizeof (riffhdr.ckID));
    strncpy (riffhdr.formType, "WAVE", sizeof (riffhdr.formType));
    total_riff_bytes = sizeof (riffhdr) + wavhdrsize + sizeof (datahdr) + ((total_data_bytes + 1) & ~(int64_t)1);
    if (do_rf64) total_riff_bytes += sizeof (ds64hdr) + sizeof (ds64_chunk);
    total_riff_bytes += table_length * sizeof (CS64Chunk);
    if (write_junk) total_riff_bytes += sizeof (junkchunk);
    strncpy (fmthdr.ckID, "fmt ", sizeof (fmthdr.ckID));
    strncpy (datahdr.ckID, "data", sizeof (datahdr.ckID));
    fmthdr.ckSize = wavhdrsize;

    if (write_junk) {
        CLEAR (junkchunk);
        strncpy (junkchunk.ckID, "junk", sizeof (junkchunk.ckID));
        junkchunk.ckSize = sizeof (junkchunk) - 8;
        WavpackNativeToLittleEndian (&junkchunk, ChunkHeaderFormat);
    }

    if (do_rf64) {
        strncpy (ds64hdr.ckID, "ds64", sizeof (ds64hdr.ckID));
        ds64hdr.ckSize = sizeof (ds64_chunk) + (table_length * sizeof (CS64Chunk));
        CLEAR (ds64_chunk);
        ds64_chunk.riffSize64 = total_riff_bytes;
        ds64_chunk.dataSize64 = total_data_bytes;
        ds64_chunk.sampleCount64 = total_samples;
        ds64_chunk.tableLength = table_length;
        riffhdr.ckSize = (uint32_t) -1;
        datahdr.ckSize = (uint32_t) -1;
        WavpackNativeToLittleEndian (&ds64hdr, ChunkHeaderFormat);
        WavpackNativeToLittleEndian (&ds64_chunk, DS64ChunkFormat);
    }
    else {
        riffhdr.ckSize = (uint32_t) total_riff_bytes;
        datahdr.ckSize = (uint32_t) total_data_bytes;
    }


    if (table_length) {
        strncpy (cs64_chunk.ckID, "dmmy", sizeof (cs64_chunk.ckID));
        cs64_chunk.chunkSize64 = 12345678;
        WavpackNativeToLittleEndian (&cs64_chunk, CS64ChunkFormat);
    }


    WavpackNativeToLittleEndian (&riffhdr, ChunkHeaderFormat);
    WavpackNativeToLittleEndian (&fmthdr, ChunkHeaderFormat);
    WavpackNativeToLittleEndian (&wavhdr, WaveHeaderFormat);
    WavpackNativeToLittleEndian (&datahdr, ChunkHeaderFormat);

    if (!DoWriteFile (outfile, &riffhdr, sizeof (riffhdr), &bcount) || bcount != sizeof (riffhdr) ||
        (do_rf64 && (!DoWriteFile (outfile, &ds64hdr, sizeof (ds64hdr), &bcount) || bcount != sizeof (ds64hdr))) ||
        (do_rf64 && (!DoWriteFile (outfile, &ds64_chunk, sizeof (ds64_chunk), &bcount) || bcount != sizeof (ds64_chunk)))) {
            error_line ("can't write .WAV data, disk probably full!");
            return FALSE;
    }


    while (table_length--)
        if (!DoWriteFile (outfile, &cs64_chunk, sizeof (cs64_chunk), &bcount) || bcount != sizeof (cs64_chunk)) {
            error_line ("can't write .WAV data, disk probably full!");
            return FALSE;
        }


    if ((write_junk && (!DoWriteFile (outfile, &junkchunk, sizeof (junkchunk), &bcount) || bcount != sizeof (junkchunk))) ||
        !DoWriteFile (outfile, &fmthdr, sizeof (fmthdr), &bcount) || bcount != sizeof (fmthdr) ||
        !DoWriteFile (outfile, &wavhdr, wavhdrsize, &bcount) || bcount != wavhdrsize ||
        !DoWriteFile (outfile, &datahdr, sizeof (datahdr), &bcount) || bcount != sizeof (datahdr)) {
            error_line ("can't write .WAV data, disk probably full!");
            return FALSE;
    }

    return TRUE;
}

Project: WavPack

int WriteWave64Header (FILE *outfile, WavpackContext *wpc, int64_t total_samples, int qmode)
{
    Wave64ChunkHeader datahdr, fmthdr;
    Wave64FileHeader filehdr;
    WaveHeader wavhdr;
    uint32_t bcount;

    int64_t total_data_bytes, total_file_bytes;
    int num_channels = WavpackGetNumChannels (wpc);
    int32_t channel_mask = WavpackGetChannelMask (wpc);
    int32_t sample_rate = WavpackGetSampleRate (wpc);
    int bytes_per_sample = WavpackGetBytesPerSample (wpc);
    int bits_per_sample = WavpackGetBitsPerSample (wpc);
    int format = WavpackGetFloatNormExp (wpc) ? 3 : 1;
    int wavhdrsize = 16;

    if (format == 3 && WavpackGetFloatNormExp (wpc) != 127) {
        error_line ("can't create valid Wave64 header for non-normalized floating data!");
        return FALSE;
    }

    if (total_samples == -1)
        total_samples = 0x7ffff000 / (bytes_per_sample * num_channels);

    total_data_bytes = total_samples * bytes_per_sample * num_channels;
    CLEAR (wavhdr);

    wavhdr.FormatTag = format;
    wavhdr.NumChannels = num_channels;
    wavhdr.SampleRate = sample_rate;
    wavhdr.BytesPerSecond = sample_rate * num_channels * bytes_per_sample;
    wavhdr.BlockAlign = bytes_per_sample * num_channels;
    wavhdr.BitsPerSample = bits_per_sample;

    if (num_channels > 2 || channel_mask != 0x5 - num_channels) {
        wavhdrsize = sizeof (wavhdr);
        wavhdr.cbSize = 22;
        wavhdr.ValidBitsPerSample = bits_per_sample;
        wavhdr.SubFormat = format;
        wavhdr.ChannelMask = channel_mask;
        wavhdr.FormatTag = 0xfffe;
        wavhdr.BitsPerSample = bytes_per_sample * 8;
        wavhdr.GUID [4] = 0x10;
        wavhdr.GUID [6] = 0x80;
        wavhdr.GUID [9] = 0xaa;
        wavhdr.GUID [11] = 0x38;
        wavhdr.GUID [12] = 0x9b;
        wavhdr.GUID [13] = 0x71;
    }

    total_file_bytes = sizeof (filehdr) + sizeof (fmthdr) + wavhdrsize + sizeof (datahdr) + ((total_data_bytes + 7) & ~(int64_t)7);

    memcpy (filehdr.ckID, riff_guid, sizeof (riff_guid));
    memcpy (filehdr.formType, wave_guid, sizeof (wave_guid));
    filehdr.ckSize = total_file_bytes;

    memcpy (fmthdr.ckID, fmt_guid, sizeof (fmt_guid));
    fmthdr.ckSize = sizeof (fmthdr) + wavhdrsize;

    memcpy (datahdr.ckID, data_guid, sizeof (data_guid));
    datahdr.ckSize = total_data_bytes + sizeof (datahdr);


    WavpackNativeToLittleEndian (&filehdr, Wave64ChunkHeaderFormat);
    WavpackNativeToLittleEndian (&fmthdr, Wave64ChunkHeaderFormat);
    WavpackNativeToLittleEndian (&wavhdr, WaveHeaderFormat);
    WavpackNativeToLittleEndian (&datahdr, Wave64ChunkHeaderFormat);

    if (!DoWriteFile (outfile, &filehdr, sizeof (filehdr), &bcount) || bcount != sizeof (filehdr) ||
        !DoWriteFile (outfile, &fmthdr, sizeof (fmthdr), &bcount) || bcount != sizeof (fmthdr) ||
        !DoWriteFile (outfile, &wavhdr, wavhdrsize, &bcount) || bcount != wavhdrsize ||
        !DoWriteFile (outfile, &datahdr, sizeof (datahdr), &bcount) || bcount != sizeof (datahdr)) {
            error_line ("can't write .W64 data, disk probably full!");
            return FALSE;
    }

    return TRUE;
}