1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-21 19:33:03 +04:00

Merge pull request #26782 from vrabaud:png_leak

Fix potential READ memory access #26782

This fixes https://oss-fuzz.com/testcase-detail/4923671881252864 and https://oss-fuzz.com/testcase-detail/5048650127966208

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [x] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Vincent Rabaud
2025-01-22 12:47:28 +01:00
committed by GitHub
parent f6aa472acc
commit 7728dd3387
2 changed files with 221 additions and 256 deletions
+212 -212
View File
@@ -198,6 +198,7 @@ PngDecoder::PngDecoder()
PngDecoder::~PngDecoder()
{
ClearPngPtr();
if( m_f )
{
fclose( m_f );
@@ -205,6 +206,26 @@ PngDecoder::~PngDecoder()
}
}
bool PngDecoder::InitPngPtr() {
ClearPngPtr();
m_png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
if (!m_png_ptr)
return false;
m_info_ptr = png_create_info_struct(m_png_ptr);
m_end_info = png_create_info_struct(m_png_ptr);
return (m_info_ptr && m_end_info);
}
void PngDecoder::ClearPngPtr() {
if (m_png_ptr)
png_destroy_read_struct(&m_png_ptr, &m_info_ptr, &m_end_info);
m_png_ptr = nullptr;
m_info_ptr = nullptr;
m_end_info = nullptr;
}
ImageDecoder PngDecoder::newDecoder() const
{
return makePtr<PngDecoder>();
@@ -227,167 +248,164 @@ void PngDecoder::readDataFromBuf( void* _png_ptr, unsigned char* dst, size_t si
bool PngDecoder::readHeader()
{
volatile bool result = false;
// Declare dynamic variables before a potential longjmp.
Chunk chunk;
PngPtrs png_ptrs;
png_structp png_ptr = png_ptrs.getPng();
png_infop info_ptr = png_ptrs.getInfo();
png_infop end_info = png_ptrs.getEndInfo();
if (!InitPngPtr())
return false;
if( png_ptr && info_ptr && end_info )
if (setjmp(png_jmpbuf(m_png_ptr)))
return false;
m_buf_pos = 0;
unsigned char sig[8];
uint32_t id = 0;
if( !m_buf.empty() )
png_set_read_fn(m_png_ptr, this, (png_rw_ptr)readDataFromBuf );
else
{
m_buf_pos = 0;
if( setjmp( png_jmpbuf( png_ptr ) ) == 0 )
m_f = fopen(m_filename.c_str(), "rb");
if (!m_f)
{
unsigned char sig[8];
uint32_t id = 0;
Chunk chunk;
if( !m_buf.empty() )
png_set_read_fn(png_ptr, this, (png_rw_ptr)readDataFromBuf );
else
{
m_f = fopen(m_filename.c_str(), "rb");
if (!m_f)
{
return false;
}
png_init_io(png_ptr, m_f);
}
// Read PNG header: 137 80 78 71 13 10 26 10
if (!read_from_io(&sig, 8))
return false;
id = read_chunk(m_chunkIHDR);
// 8=HDR+size, 13=size of IHDR chunk, 4=CRC
// http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR
if (!(id == id_IHDR && m_chunkIHDR.p.size() == 8 + 13 + 4))
{
return false;
}
while (true)
{
m_is_fcTL_loaded = false;
id = read_chunk(chunk);
if (!id || (m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total()))
{
return false;
}
if (id == id_IDAT)
{
if (m_f)
fseek(m_f, 0, SEEK_SET);
else
m_buf_pos = 0;
break;
}
if (id == id_acTL)
{
// 8=HDR+size, 8=size of acTL chunk, 4=CRC
// https://wiki.mozilla.org/APNG_Specification#%60acTL%60:_The_Animation_Control_Chunk
if (chunk.p.size() != 8 + 8 + 4)
return false;
m_animation.loop_count = png_get_uint_32(&chunk.p[12]);
m_frame_count = png_get_uint_32(&chunk.p[8]);
if (m_frame_count == 0)
return false;
}
if (id == id_fcTL)
{
// 8=HDR+size, 26=size of fcTL chunk, 4=CRC
// https://wiki.mozilla.org/APNG_Specification#%60fcTL%60:_The_Frame_Control_Chunk
if (chunk.p.size() != 8 + 26 + 4)
return false;
m_is_fcTL_loaded = true;
w0 = png_get_uint_32(&chunk.p[12]);
h0 = png_get_uint_32(&chunk.p[16]);
x0 = png_get_uint_32(&chunk.p[20]);
y0 = png_get_uint_32(&chunk.p[24]);
delay_num = png_get_uint_16(&chunk.p[28]);
delay_den = png_get_uint_16(&chunk.p[30]);
dop = chunk.p[32];
bop = chunk.p[33];
}
if (id == id_bKGD)
{
// 8=HDR+size, ??=size of bKGD chunk, 4=CRC
// The spec is actually more complex: http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.bKGD
// TODO: we only check that 4 bytes can be read from &chunk.p[8]. Fix.
if (chunk.p.size() < 8 + 4)
return false;
int bgcolor = png_get_uint_32(&chunk.p[8]);
m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF;
m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF;
m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF;
m_animation.bgcolor[0] = bgcolor & 0xFF;
}
if (id == id_PLTE || id == id_tRNS)
m_chunksInfo.push_back(chunk);
}
png_uint_32 wdth, hght;
int bit_depth, color_type, num_trans=0;
png_bytep trans;
png_color_16p trans_values;
// Free chunk in case png_read_info uses longjmp.
chunk.p.clear();
chunk.p.shrink_to_fit();
png_read_info( png_ptr, info_ptr );
png_get_IHDR(png_ptr, info_ptr, &wdth, &hght,
&bit_depth, &color_type, 0, 0, 0);
m_width = (int)wdth;
m_height = (int)hght;
m_color_type = color_type;
m_bit_depth = bit_depth;
if (bit_depth <= 8 || bit_depth == 16)
{
switch (color_type)
{
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_PALETTE:
png_get_tRNS(png_ptr, info_ptr, &trans, &num_trans, &trans_values);
if (num_trans > 0)
m_type = CV_8UC4;
else
m_type = CV_8UC3;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
case PNG_COLOR_TYPE_RGB_ALPHA:
m_type = CV_8UC4;
break;
default:
m_type = CV_8UC1;
}
if (bit_depth == 16)
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
result = true;
}
return false;
}
png_init_io(m_png_ptr, m_f);
}
if(result)
// Read PNG header: 137 80 78 71 13 10 26 10
if (!read_from_io(&sig, 8))
return false;
id = read_chunk(m_chunkIHDR);
// 8=HDR+size, 13=size of IHDR chunk, 4=CRC
// http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.IHDR
if (!(id == id_IHDR && m_chunkIHDR.p.size() == 8 + 13 + 4))
{
m_png_ptrs = std::move(png_ptrs);
return false;
}
return result;
m_is_fcTL_loaded = false;
while (true)
{
id = read_chunk(chunk);
if (!id || (m_f && feof(m_f)) || (!m_buf.empty() && m_buf_pos > m_buf.total()))
{
return false;
}
if (id == id_IDAT)
{
if (m_f)
fseek(m_f, 0, SEEK_SET);
else
m_buf_pos = 0;
break;
}
if (id == id_acTL)
{
// 8=HDR+size, 8=size of acTL chunk, 4=CRC
// https://wiki.mozilla.org/APNG_Specification#%60acTL%60:_The_Animation_Control_Chunk
if (chunk.p.size() != 8 + 8 + 4)
return false;
m_animation.loop_count = png_get_uint_32(&chunk.p[12]);
m_frame_count = png_get_uint_32(&chunk.p[8]);
if (m_frame_count == 0)
return false;
}
if (id == id_fcTL)
{
// 8=HDR+size, 26=size of fcTL chunk, 4=CRC
// https://wiki.mozilla.org/APNG_Specification#%60fcTL%60:_The_Frame_Control_Chunk
if (chunk.p.size() != 8 + 26 + 4)
return false;
m_is_fcTL_loaded = true;
w0 = png_get_uint_32(&chunk.p[12]);
h0 = png_get_uint_32(&chunk.p[16]);
x0 = png_get_uint_32(&chunk.p[20]);
y0 = png_get_uint_32(&chunk.p[24]);
delay_num = png_get_uint_16(&chunk.p[28]);
delay_den = png_get_uint_16(&chunk.p[30]);
dop = chunk.p[32];
bop = chunk.p[33];
}
if (id == id_bKGD)
{
// 8=HDR+size, ??=size of bKGD chunk, 4=CRC
// The spec is actually more complex: http://www.libpng.org/pub/png/spec/1.2/PNG-Chunks.html#C.bKGD
// TODO: we only check that 4 bytes can be read from &chunk.p[8]. Fix.
if (chunk.p.size() < 8 + 4)
return false;
int bgcolor = png_get_uint_32(&chunk.p[8]);
m_animation.bgcolor[3] = (bgcolor >> 24) & 0xFF;
m_animation.bgcolor[2] = (bgcolor >> 16) & 0xFF;
m_animation.bgcolor[1] = (bgcolor >> 8) & 0xFF;
m_animation.bgcolor[0] = bgcolor & 0xFF;
}
if (id == id_PLTE || id == id_tRNS)
m_chunksInfo.push_back(chunk);
}
png_uint_32 wdth, hght;
int bit_depth, color_type, num_trans=0;
png_bytep trans;
png_color_16p trans_values;
// Free chunk in case png_read_info uses longjmp.
chunk.p.clear();
chunk.p.shrink_to_fit();
png_read_info( m_png_ptr, m_info_ptr );
png_get_IHDR(m_png_ptr, m_info_ptr, &wdth, &hght,
&bit_depth, &color_type, 0, 0, 0);
m_width = (int)wdth;
m_height = (int)hght;
m_color_type = color_type;
m_bit_depth = bit_depth;
if (m_is_fcTL_loaded && (int(x0 + w0) > m_width || int(y0 + h0) > m_height || dop > 2 || bop > 1))
return false;
if (bit_depth <= 8 || bit_depth == 16)
{
switch (color_type)
{
case PNG_COLOR_TYPE_RGB:
case PNG_COLOR_TYPE_PALETTE:
png_get_tRNS(m_png_ptr, m_info_ptr, &trans, &num_trans, &trans_values);
if (num_trans > 0)
m_type = CV_8UC4;
else
m_type = CV_8UC3;
break;
case PNG_COLOR_TYPE_GRAY_ALPHA:
case PNG_COLOR_TYPE_RGB_ALPHA:
m_type = CV_8UC4;
break;
default:
m_type = CV_8UC1;
}
if (bit_depth == 16)
m_type = CV_MAKETYPE(CV_16U, CV_MAT_CN(m_type));
}
return true;
}
bool PngDecoder::readData( Mat& img )
{
// Declare dynamic variables before a potential longjmp.
AutoBuffer<unsigned char*> _buffer(m_height);
unsigned char** buffer = _buffer.data();
Chunk chunk;
if (m_frame_count > 1)
{
Mat mat_cur = Mat::zeros(img.rows, img.cols, m_type);
@@ -412,13 +430,14 @@ bool PngDecoder::readData( Mat& img )
frameCur.setMat(mat_cur);
processing_start((void*)&frameRaw, mat_cur);
png_structp png_ptr = m_png_ptrs.getPng();
png_infop info_ptr = m_png_ptrs.getInfo();
if (!processing_start((void*)&frameRaw, mat_cur))
return false;
if(setjmp(png_jmpbuf(m_png_ptr)))
return false;
while (true)
{
Chunk chunk;
id = read_chunk(chunk);
if (!id)
return false;
@@ -482,14 +501,14 @@ bool PngDecoder::readData( Mat& img )
else if (id == id_IDAT)
{
m_is_IDAT_loaded = true;
png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size());
png_process_data(m_png_ptr, m_info_ptr, chunk.p.data(), chunk.p.size());
}
else if (id == id_fdAT && m_is_fcTL_loaded)
{
m_is_IDAT_loaded = true;
png_save_uint_32(&chunk.p[4], static_cast<uint32_t>(chunk.p.size() - 16));
memcpy(&chunk.p[8], "IDAT", 4);
png_process_data(png_ptr, info_ptr, &chunk.p[4], chunk.p.size() - 4);
png_process_data(m_png_ptr, m_info_ptr, &chunk.p[4], chunk.p.size() - 4);
}
else if (id == id_IEND)
{
@@ -513,30 +532,24 @@ bool PngDecoder::readData( Mat& img )
return true;
}
else
png_process_data(png_ptr, info_ptr, chunk.p.data(), chunk.p.size());
png_process_data(m_png_ptr, m_info_ptr, chunk.p.data(), chunk.p.size());
}
return false;
}
volatile bool result = false;
AutoBuffer<unsigned char*> _buffer(m_height);
unsigned char** buffer = _buffer.data();
bool color = img.channels() > 1;
png_structp png_ptr = m_png_ptrs.getPng();
png_infop info_ptr = m_png_ptrs.getInfo();
png_infop end_info = m_png_ptrs.getEndInfo();
if( png_ptr && info_ptr && end_info && m_width && m_height )
if( m_png_ptr && m_info_ptr && m_end_info && m_width && m_height )
{
if( setjmp( png_jmpbuf ( png_ptr ) ) == 0 )
if( setjmp( png_jmpbuf ( m_png_ptr ) ) == 0 )
{
int y;
if( img.depth() == CV_8U && m_bit_depth == 16 )
png_set_strip_16( png_ptr );
png_set_strip_16( m_png_ptr );
else if( !isBigEndian() )
png_set_swap( png_ptr );
png_set_swap( m_png_ptr );
if(img.channels() < 4)
{
@@ -548,46 +561,46 @@ bool PngDecoder::readData( Mat& img )
* indicate that it is a good idea to always ask for
* stripping alpha.. 18.11.2004 Axel Walthelm
*/
png_set_strip_alpha( png_ptr );
png_set_strip_alpha( m_png_ptr );
} else
png_set_tRNS_to_alpha( png_ptr );
png_set_tRNS_to_alpha( m_png_ptr );
if( m_color_type == PNG_COLOR_TYPE_PALETTE )
png_set_palette_to_rgb( png_ptr );
png_set_palette_to_rgb( m_png_ptr );
if( (m_color_type & PNG_COLOR_MASK_COLOR) == 0 && m_bit_depth < 8 )
#if (PNG_LIBPNG_VER_MAJOR*10000 + PNG_LIBPNG_VER_MINOR*100 + PNG_LIBPNG_VER_RELEASE >= 10209) || \
(PNG_LIBPNG_VER_MAJOR == 1 && PNG_LIBPNG_VER_MINOR == 0 && PNG_LIBPNG_VER_RELEASE >= 18)
png_set_expand_gray_1_2_4_to_8( png_ptr );
png_set_expand_gray_1_2_4_to_8( m_png_ptr );
#else
png_set_gray_1_2_4_to_8( png_ptr );
#endif
if( (m_color_type & PNG_COLOR_MASK_COLOR) && color && !m_use_rgb)
png_set_bgr( png_ptr ); // convert RGB to BGR
png_set_bgr( m_png_ptr ); // convert RGB to BGR
else if( color )
png_set_gray_to_rgb( png_ptr ); // Gray->RGB
png_set_gray_to_rgb( m_png_ptr ); // Gray->RGB
else
png_set_rgb_to_gray( png_ptr, 1, 0.299, 0.587 ); // RGB->Gray
png_set_rgb_to_gray( m_png_ptr, 1, 0.299, 0.587 ); // RGB->Gray
png_set_interlace_handling( png_ptr );
png_read_update_info( png_ptr, info_ptr );
png_set_interlace_handling( m_png_ptr );
png_read_update_info( m_png_ptr, m_info_ptr );
for( y = 0; y < m_height; y++ )
buffer[y] = img.data + y*img.step;
png_read_image( png_ptr, buffer );
png_read_end( png_ptr, end_info );
png_read_image( m_png_ptr, buffer );
png_read_end( m_png_ptr, m_end_info );
#ifdef PNG_eXIf_SUPPORTED
png_uint_32 num_exif = 0;
png_bytep exif = 0;
// Exif info could be in info_ptr (intro_info) or end_info per specification
if( png_get_valid(png_ptr, info_ptr, PNG_INFO_eXIf) )
png_get_eXIf_1(png_ptr, info_ptr, &num_exif, &exif);
else if( png_get_valid(png_ptr, end_info, PNG_INFO_eXIf) )
png_get_eXIf_1(png_ptr, end_info, &num_exif, &exif);
if( png_get_valid(m_png_ptr, m_info_ptr, PNG_INFO_eXIf) )
png_get_eXIf_1(m_png_ptr, m_info_ptr, &num_exif, &exif);
else if( png_get_valid(m_png_ptr, m_end_info, PNG_INFO_eXIf) )
png_get_eXIf_1(m_png_ptr, m_end_info, &num_exif, &exif);
if( exif && num_exif > 0 )
{
@@ -719,42 +732,34 @@ uint32_t PngDecoder::read_chunk(Chunk& chunk)
bool PngDecoder::processing_start(void* frame_ptr, const Mat& img)
{
if (!InitPngPtr())
return false;
if (setjmp(png_jmpbuf(m_png_ptr)))
return false;
static uint8_t header[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
PngPtrs png_ptrs;
png_structp png_ptr = png_ptrs.getPng();
png_infop info_ptr = png_ptrs.getInfo();
if (!png_ptr || !info_ptr) {
return false;
}
if (setjmp(png_jmpbuf(png_ptr)))
{
return false;
}
m_png_ptrs = std::move(png_ptrs);
png_set_crc_action(png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
png_set_progressive_read_fn(png_ptr, frame_ptr, (png_progressive_info_ptr)info_fn, row_fn, NULL);
png_set_crc_action(m_png_ptr, PNG_CRC_QUIET_USE, PNG_CRC_QUIET_USE);
png_set_progressive_read_fn(m_png_ptr, frame_ptr, (png_progressive_info_ptr)info_fn, row_fn, NULL);
if (img.channels() < 4)
png_set_strip_alpha(png_ptr);
png_set_strip_alpha(m_png_ptr);
else
png_set_tRNS_to_alpha(png_ptr);
png_set_tRNS_to_alpha(m_png_ptr);
png_process_data(png_ptr, info_ptr, header, 8);
png_process_data(png_ptr, info_ptr, m_chunkIHDR.p.data(), m_chunkIHDR.p.size());
png_process_data(m_png_ptr, m_info_ptr, header, 8);
png_process_data(m_png_ptr, m_info_ptr, m_chunkIHDR.p.data(), m_chunkIHDR.p.size());
if ((m_color_type & PNG_COLOR_MASK_COLOR) && img.channels() > 1 && !m_use_rgb)
png_set_bgr(png_ptr); // convert RGB to BGR
png_set_bgr(m_png_ptr); // convert RGB to BGR
else if (img.channels() > 1)
png_set_gray_to_rgb(png_ptr); // Gray->RGB
png_set_gray_to_rgb(m_png_ptr); // Gray->RGB
else
png_set_rgb_to_gray(png_ptr, 1, 0.299, 0.587); // RGB->Gray
png_set_rgb_to_gray(m_png_ptr, 1, 0.299, 0.587); // RGB->Gray
for (size_t i = 0; i < m_chunksInfo.size(); i++)
png_process_data(png_ptr, info_ptr, m_chunksInfo[i].p.data(), m_chunksInfo[i].p.size());
png_process_data(m_png_ptr, m_info_ptr, m_chunksInfo[i].p.data(), m_chunksInfo[i].p.size());
return true;
}
@@ -763,22 +768,17 @@ bool PngDecoder::processing_finish()
{
static uint8_t footer[12] = { 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130 };
png_structp png_ptr = m_png_ptrs.getPng();
png_infop info_ptr = m_png_ptrs.getInfo();
if (!png_ptr) {
m_png_ptrs.clear();
if (!m_png_ptr) {
return false;
}
if (setjmp(png_jmpbuf(png_ptr)))
if (setjmp(png_jmpbuf(m_png_ptr)))
{
m_png_ptrs.clear();
return false;
}
png_process_data(png_ptr, info_ptr, footer, 12);
m_png_ptrs.clear();
png_process_data(m_png_ptr, m_info_ptr, footer, 12);
ClearPngPtr();
return true;
}
+9 -44
View File
@@ -130,56 +130,21 @@ public:
ImageDecoder newDecoder() const CV_OVERRIDE;
protected:
private:
static void readDataFromBuf(void* png_ptr, uchar* dst, size_t size);
static void info_fn(png_structp png_ptr, png_infop info_ptr);
static void row_fn(png_structp png_ptr, png_bytep new_row, png_uint_32 row_num, int pass);
bool processing_start(void* frame_ptr, const Mat& img);
bool processing_finish();
CV_NODISCARD_STD bool processing_start(void* frame_ptr, const Mat& img);
CV_NODISCARD_STD bool processing_finish();
void compose_frame(std::vector<png_bytep>& rows_dst, const std::vector<png_bytep>& rows_src, unsigned char bop, uint32_t x, uint32_t y, uint32_t w, uint32_t h, Mat& img);
bool read_from_io(void* buffer, size_t num_bytes);
CV_NODISCARD_STD bool read_from_io(void* buffer, size_t num_bytes);
uint32_t read_chunk(Chunk& chunk);
CV_NODISCARD_STD bool InitPngPtr();
void ClearPngPtr();
struct PngPtrs {
public:
PngPtrs() {
png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
if (png_ptr) {
info_ptr = png_create_info_struct( png_ptr );
end_info = png_create_info_struct( png_ptr );
} else {
info_ptr = end_info = nullptr;
}
}
~PngPtrs() {
clear();
}
PngPtrs& operator=(PngPtrs&& other) {
clear();
png_ptr = other.png_ptr;
info_ptr = other.info_ptr;
end_info = other.end_info;
other.png_ptr = nullptr;
other.info_ptr = other.end_info = nullptr;
return *this;
}
void clear() {
if (png_ptr) {
png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
png_ptr = nullptr;
info_ptr = end_info = nullptr;
}
}
png_structp getPng() const { return png_ptr; }
png_infop getInfo() const { return info_ptr; }
png_infop getEndInfo() const { return end_info; }
private:
png_structp png_ptr; // pointer to decompression structure
png_infop info_ptr; // pointer to image information structure
png_infop end_info; // pointer to one more image information structure
};
PngPtrs m_png_ptrs;
png_structp m_png_ptr = nullptr; // pointer to decompression structure
png_infop m_info_ptr = nullptr; // pointer to image information structure
png_infop m_end_info = nullptr; // pointer to one more image information structure
int m_bit_depth;
FILE* m_f;
int m_color_type;