More transcoder fixes found during fuzz testing (I disabled all header/data CRC checking by setting BASISU_NO_HEADER_OR_DATA_CRC16_CHECKS to 1).

This commit is contained in:
Rich Geldreich
2019-05-09 14:43:13 -07:00
parent d7b22fc404
commit 78e22234bd
5 changed files with 135 additions and 22 deletions

View File

@@ -60,7 +60,9 @@ namespace basist
cBASISTexType2DArray = 1, // An array of 2D RGB or RGBA images with optional mipmaps, array size = # images, each image has the same resolution and mipmap levels
cBASISTexTypeCubemapArray = 2, // an array of cubemap levels, total # of images must be divisable by 6, in X+, X-, Y+, Y-, Z+, Z- order, with optional mipmaps
cBASISTexTypeVideoFrames = 3, // An array of 2D video frames, with optional mipmaps, # frames = # images, each image has the same resolution and # of mipmap levels
cBASISTexTypeVolume = 4 // A 3D texture with optional mipmaps, Z dimension = # images, each image has the same resolution and # of mipmap levels
cBASISTexTypeVolume = 4, // A 3D texture with optional mipmaps, Z dimension = # images, each image has the same resolution and # of mipmap levels
cBASISTexTypeTotal
};
enum

View File

@@ -1,5 +1,4 @@
// basisu_transcoder.cpp
// basisu_transcoder.cpp
// Copyright (C) 2017-2019 Binomial LLC. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
@@ -21,6 +20,9 @@
// The supported .basis file header version. Keep in sync with BASIS_FILE_VERSION.
#define BASISD_SUPPORTED_BASIS_VERSION (0x13)
// Set to 1 for fuzz testing. This will disable all CRC16 checks on headers and compressed data.
#define BASISU_NO_HEADER_OR_DATA_CRC16_CHECKS 0
#define BASISD_SUPPORT_DXT1 1
#define BASISD_SUPPORT_DXT5A 1
#define BASISD_SUPPORT_BC7 1
@@ -3984,7 +3986,13 @@ namespace basist
} // block-y
assert(endpoint_pred_repeat_count == 0);
if (endpoint_pred_repeat_count != 0)
{
BASISU_DEVEL_ERROR("basisu_lowlevel_transcoder::transcode_slice: endpoint_pred_repeat_count != 0. The file is corrupted or this is a bug\n");
return false;
}
//assert(endpoint_pred_repeat_count == 0);
if (fmt == cPVRTC1_4_OPAQUE_ONLY)
{
@@ -4014,6 +4022,7 @@ namespace basist
const basis_file_header *pHeader = reinterpret_cast<const basis_file_header*>(pData);
#if !BASISU_NO_HEADER_OR_DATA_CRC16_CHECKS
if (crc16(&pHeader->m_data_size, sizeof(basis_file_header) - BASISU_OFFSETOF(basis_file_header, m_data_size), 0) != pHeader->m_header_crc16)
{
BASISU_DEVEL_ERROR("basisu_transcoder::get_total_images: header CRC check failed\n");
@@ -4028,7 +4037,8 @@ namespace basist
return false;
}
}
#endif
return true;
}
@@ -4051,7 +4061,21 @@ namespace basist
BASISU_DEVEL_ERROR("basisu_transcoder::get_total_images: source buffer is too small\n");
return false;
}
if ((!pHeader->m_total_slices) || (!pHeader->m_total_images))
{
BASISU_DEVEL_ERROR("basisu_transcoder::validate_header_quick: header is invalid\n");
return false;
}
if ( (pHeader->m_slice_desc_file_ofs >= data_size) ||
((data_size - pHeader->m_slice_desc_file_ofs) < (sizeof(basis_slice_desc) * pHeader->m_total_slices))
)
{
BASISU_DEVEL_ERROR("basisu_transcoder::validate_header_quick: passed in buffer is too small or data is corrupted\n");
return false;
}
return true;
}
@@ -4092,13 +4116,17 @@ namespace basist
if (pHeader->m_flags & cBASISHeaderFlagHasAlphaSlices)
{
// Must have an even # of slices if the .basis file has alpha
if (pHeader->m_total_slices & 1)
if (pHeader->m_total_slices != pHeader->m_total_images * 2)
{
BASISU_DEVEL_ERROR("basisu_transcoder::get_total_images: invalid basis file (odd number of slices)\n");
BASISU_DEVEL_ERROR("basisu_transcoder::get_total_images: invalid alpha basis file\n");
return false;
}
}
else if (pHeader->m_total_slices != pHeader->m_total_images)
{
BASISU_DEVEL_ERROR("basisu_transcoder::get_total_images: invalid total_images field in header\n");
return false;
}
if ((pHeader->m_flags & cBASISHeaderFlagETC1S) == 0)
{
@@ -4107,6 +4135,14 @@ namespace basist
return false;
}
if ( (pHeader->m_slice_desc_file_ofs >= data_size) ||
((data_size - pHeader->m_slice_desc_file_ofs) < (sizeof(basis_slice_desc) * pHeader->m_total_slices))
)
{
BASISU_DEVEL_ERROR("basisu_transcoder::validate_header_quick: passed in buffer is too small or data is corrupted\n");
return false;
}
return true;
}
@@ -4120,7 +4156,15 @@ namespace basist
const basis_file_header *pHeader = static_cast<const basis_file_header *>(pData);
return static_cast<basis_texture_type>(static_cast<uint8_t>(pHeader->m_tex_type));
basis_texture_type btt = static_cast<basis_texture_type>(static_cast<uint8_t>(pHeader->m_tex_type));
if (btt >= cBASISTexTypeTotal)
{
BASISU_DEVEL_ERROR("basisu_transcoder::validate_header_quick: header's texture type field is invalid\n");
return cBASISTexType2DArray;
}
return btt;
}
bool basisu_transcoder::get_userdata(const void *pData, uint32_t data_size, uint32_t &userdata0, uint32_t &userdata1) const
@@ -4318,10 +4362,10 @@ namespace basist
BASISU_DEVEL_ERROR("basisu_transcoder::get_file_info: validate_file_checksums failed\n");
return false;
}
const basis_file_header* pHeader = static_cast<const basis_file_header*>(pData);
const basis_slice_desc* pSlice_descs = reinterpret_cast<const basis_slice_desc*>(static_cast<const uint8_t*>(pData) + pHeader->m_slice_desc_file_ofs);
file_info.m_version = pHeader->m_ver;
file_info.m_total_header_size = sizeof(basis_file_header) + pHeader->m_total_slices * sizeof(basis_slice_desc);
@@ -4345,6 +4389,13 @@ namespace basist
file_info.m_slices_size = 0;
file_info.m_tex_type = static_cast<basis_texture_type>(static_cast<uint8_t>(pHeader->m_tex_type));
if (file_info.m_tex_type > cBASISTexTypeTotal)
{
BASISU_DEVEL_ERROR("basisu_transcoder::get_file_info: invalid texture type, file is corrupted\n");
return false;
}
file_info.m_us_per_frame = pHeader->m_us_per_frame;
file_info.m_userdata0 = pHeader->m_userdata0;
file_info.m_userdata1 = pHeader->m_userdata1;
@@ -4404,6 +4455,30 @@ namespace basist
const uint8_t *pDataU8 = static_cast<const uint8_t *>(pData);
if ((pHeader->m_endpoint_cb_file_ofs > data_size) || (pHeader->m_selector_cb_file_ofs > data_size) || (pHeader->m_tables_file_ofs > data_size))
{
BASISU_DEVEL_ERROR("basisu_transcoder::transcode_slice: file is corrupted or passed in buffer too small (1)\n");
return false;
}
if (pHeader->m_endpoint_cb_file_size > (data_size - pHeader->m_endpoint_cb_file_ofs))
{
BASISU_DEVEL_ERROR("basisu_transcoder::transcode_slice: file is corrupted or passed in buffer too small (2)\n");
return false;
}
if (pHeader->m_selector_cb_file_size > (data_size - pHeader->m_selector_cb_file_ofs))
{
BASISU_DEVEL_ERROR("basisu_transcoder::transcode_slice: file is corrupted or passed in buffer too small (3)\n");
return false;
}
if (pHeader->m_tables_file_size > (data_size - pHeader->m_tables_file_ofs))
{
BASISU_DEVEL_ERROR("basisu_transcoder::transcode_slice: file is corrupted or passed in buffer too small (3)\n");
return false;
}
if (!m_lowlevel_decoder.decode_palettes(
pHeader->m_total_endpoints, pDataU8 + pHeader->m_endpoint_cb_file_ofs, pHeader->m_endpoint_cb_file_size,
pHeader->m_total_selectors, pDataU8 + pHeader->m_selector_cb_file_ofs, pHeader->m_selector_cb_file_size))
@@ -4474,6 +4549,19 @@ namespace basist
}
}
}
if (slice_desc.m_file_ofs > data_size)
{
BASISU_DEVEL_ERROR("basisu_transcoder::transcode_slice: invalid slice_desc.m_file_ofs, or passed in buffer too small\n");
return false;
}
const uint32_t data_size_left = data_size - slice_desc.m_file_ofs;
if (data_size_left < slice_desc.m_file_size)
{
BASISU_DEVEL_ERROR("basisu_transcoder::transcode_slice: invalid slice_desc.m_file_size, or passed in buffer too small\n");
return false;
}
return m_lowlevel_decoder.transcode_slice(pOutput_blocks, slice_desc.m_num_blocks_x, slice_desc.m_num_blocks_y,
pDataU8 + slice_desc.m_file_ofs, slice_desc.m_file_size,
@@ -4577,8 +4665,6 @@ namespace basist
transcoder_texture_format fmt,
uint32_t decode_flags) const
{
if (!m_lowlevel_decoder.m_endpoints.size())
{
BASISU_DEVEL_ERROR("basisu_transcoder::transcode_image_level: must call start_transcoding() first\n");

View File

@@ -143,6 +143,9 @@ namespace basist
{
tree_cur -= ((rev_code >>= 1) & 1);
if ((-tree_cur - 1) >= (int)m_tree.size())
m_tree.resize((-tree_cur - 1) + 1);
if (!m_tree[-tree_cur - 1])
{
m_tree[-tree_cur - 1] = (int16_t)tree_next;
@@ -154,6 +157,10 @@ namespace basist
}
tree_cur -= ((rev_code >>= 1) & 1);
if ((-tree_cur - 1) >= (int)m_tree.size())
m_tree.resize((-tree_cur - 1) + 1);
m_tree[-tree_cur - 1] = (int16_t)sym_index;
}
@@ -175,7 +182,9 @@ namespace basist
public:
bitwise_decoder() :
m_buf_size(0),
m_pBuf(0),
m_pBuf(nullptr),
m_pBuf_start(nullptr),
m_pBuf_end(nullptr),
m_bit_buf(0),
m_bit_buf_size(0)
{
@@ -184,15 +193,22 @@ namespace basist
void clear()
{
m_buf_size = 0;
m_pBuf = 0;
m_pBuf = nullptr;
m_pBuf_start = nullptr;
m_pBuf_end = nullptr;
m_bit_buf = 0;
m_bit_buf_size = 0;
}
bool init(const uint8_t *pBuf, uint32_t buf_size)
{
if ((!pBuf) && (buf_size))
return false;
m_buf_size = buf_size;
m_pBuf = pBuf;
m_pBuf_start = pBuf;
m_pBuf_end = pBuf + buf_size;
m_bit_buf = 0;
m_bit_buf_size = 0;
return true;
@@ -211,7 +227,10 @@ namespace basist
while (m_bit_buf_size < num_bits)
{
const uint32_t c = m_buf_size ? *m_pBuf++ : 0;
uint32_t c = 0;
if (m_pBuf < m_pBuf_end)
c = *m_pBuf++;
m_bit_buf |= (c << m_bit_buf_size);
m_bit_buf_size += 8;
assert(m_bit_buf_size <= 32);
@@ -331,7 +350,10 @@ namespace basist
while (m_bit_buf_size < 16)
{
const uint32_t c = m_buf_size ? *m_pBuf++ : 0;
uint32_t c = 0;
if (m_pBuf < m_pBuf_end)
c = *m_pBuf++;
m_bit_buf |= (c << m_bit_buf_size);
m_bit_buf_size += 8;
assert(m_bit_buf_size <= 32);
@@ -410,7 +432,8 @@ namespace basist
l = get_bits(basisu::cHuffmanBigRepeatExtraBits) + basisu::cHuffmanBigRepeatSizeMin;
const uint8_t prev = code_sizes[cur - 1];
assert(prev != 0);
if (prev == 0)
return false;
do
{
if (cur >= total_used_syms)
@@ -429,6 +452,8 @@ namespace basist
private:
uint32_t m_buf_size;
const uint8_t *m_pBuf;
const uint8_t *m_pBuf_start;
const uint8_t *m_pBuf_end;
uint32_t m_bit_buf;
uint32_t m_bit_buf_size;