Skip to content

Commit d43311d

Browse files
issakomihjmjohnson
authored andcommitted
BUG: fixed undefined behaviour with corrupted JPEG file
S. #2928
1 parent 1a3c66e commit d43311d

File tree

5 files changed

+95
-44
lines changed

5 files changed

+95
-44
lines changed

Modules/IO/JPEG/include/itkJPEGImageIO.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class ITKIOJPEG_EXPORT JPEGImageIO : public ImageIOBase
111111
PrintSelf(std::ostream & os, Indent indent) const override;
112112

113113
void
114-
WriteSlice(std::string & fileName, const void * buffer);
114+
WriteSlice(std::string & fileName, const void * const buffer);
115115

116116
/** Default = true*/
117117
bool m_Progressive;

Modules/IO/JPEG/src/itkJPEGImageIO.cxx

Lines changed: 37 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222
#include "itk_jpeg.h"
2323
#include <csetjmp>
2424

25+
#define JPEGIO_JPEG_MESSAGES 1
26+
27+
#if (defined JPEGIO_JPEG_MESSAGES && JPEGIO_JPEG_MESSAGES == 1)
28+
# include <cstdio>
29+
#endif
30+
2531
// create an error handler for jpeg that
2632
// can longjmp out of the jpeg library
2733
struct itk_jpeg_error_mgr
@@ -36,37 +42,31 @@ extern "C"
3642
{
3743
/* cinfo->err really points to a itk_jpeg_error_mgr struct, so coerce pointer
3844
*/
39-
auto * myerr = reinterpret_cast<itk_jpeg_error_mgr *>(cinfo->err);
45+
itk_jpeg_error_mgr * myerr = (itk_jpeg_error_mgr *)cinfo->err;
4046

4147
/* Always display the message. */
4248
/* We could postpone this until after returning, if we chose. */
4349
(*cinfo->err->output_message)(cinfo);
4450

45-
jpeg_abort(cinfo); /* clean up libjpeg state */
4651
/* Return control to the setjmp point */
4752
longjmp(myerr->setjmp_buffer, 1);
4853
}
4954

50-
METHODDEF(void) itk_jpeg_output_message(j_common_ptr) {}
55+
METHODDEF(void) itk_jpeg_output_message(j_common_ptr cinfo)
56+
{
57+
#if (defined JPEGIO_JPEG_MESSAGES && JPEGIO_JPEG_MESSAGES == 1)
58+
char buffer[JMSG_LENGTH_MAX + 1];
59+
(*cinfo->err->format_message)(cinfo, buffer);
60+
printf("%s\n", buffer);
61+
#else
62+
(void)cinfo;
63+
#endif
64+
}
5165
}
5266

5367
namespace itk
5468
{
5569

56-
namespace
57-
{
58-
// Wrap setjmp call to avoid warnings about variable clobbering.
59-
bool
60-
wrapSetjmp(itk_jpeg_error_mgr & jerr)
61-
{
62-
if (setjmp(jerr.setjmp_buffer))
63-
{
64-
return true;
65-
}
66-
return false;
67-
}
68-
} // namespace
69-
7070
// simple class to call fopen on construct and
7171
// fclose on destruct
7272
class JPEGFileWrapper
@@ -86,7 +86,7 @@ class JPEGFileWrapper
8686
}
8787
}
8888

89-
FILE * m_FilePointer;
89+
FILE * volatile m_FilePointer;
9090
};
9191

9292
bool
@@ -142,7 +142,7 @@ JPEGImageIO::CanReadFile(const char * file)
142142
jerr.pub.output_message = itk_jpeg_output_message;
143143
// set the jump point, if there is a jpeg error or warning
144144
// this will evaluate to true
145-
if (wrapSetjmp(jerr))
145+
if (setjmp(jerr.setjmp_buffer))
146146
{
147147
// clean up
148148
jpeg_destroy_decompress(&cinfo);
@@ -166,13 +166,9 @@ void
166166
JPEGImageIO::ReadVolume(void *)
167167
{}
168168

169-
//-----------------------------------------------------------------------------
170-
171169
void
172170
JPEGImageIO::Read(void * buffer)
173171
{
174-
unsigned int ui;
175-
176172
// use this class so return will call close
177173
JPEGFileWrapper JPEGfp(this->GetFileName(), "rb");
178174
FILE * fp = JPEGfp.m_FilePointer;
@@ -193,7 +189,7 @@ JPEGImageIO::Read(void * buffer)
193189
jerr.pub.error_exit = itk_jpeg_error_exit;
194190
// for any output message call itk_jpeg_output_message
195191
jerr.pub.output_message = itk_jpeg_output_message;
196-
if (wrapSetjmp(jerr))
192+
if (setjmp(jerr.setjmp_buffer))
197193
{
198194
// clean up
199195
jpeg_destroy_decompress(&cinfo);
@@ -212,21 +208,26 @@ JPEGImageIO::Read(void * buffer)
212208
// prepare to read the bulk data
213209
jpeg_start_decompress(&cinfo);
214210

215-
SizeValueType rowbytes = cinfo.output_components * cinfo.output_width;
216-
auto * tempImage = static_cast<JSAMPLE *>(buffer);
211+
const auto rowbytes = cinfo.output_width * cinfo.output_components;
212+
auto * tempImage = static_cast<JSAMPLE *>(buffer);
217213

218-
auto * row_pointers = new JSAMPROW[cinfo.output_height];
219-
for (ui = 0; ui < cinfo.output_height; ++ui)
214+
auto * volatile row_pointers = new JSAMPROW[cinfo.output_height];
215+
for (size_t ui = 0; ui < cinfo.output_height; ++ui)
220216
{
221217
row_pointers[ui] = tempImage + rowbytes * ui;
222218
}
223219

224220
// read the bulk data
225-
unsigned int remainingRows;
226221
while (cinfo.output_scanline < cinfo.output_height)
227222
{
228-
remainingRows = cinfo.output_height - cinfo.output_scanline;
229-
jpeg_read_scanlines(&cinfo, &row_pointers[cinfo.output_scanline], remainingRows);
223+
if (setjmp(jerr.setjmp_buffer))
224+
{
225+
itkWarningMacro("JPEG error in the file " << this->GetFileName());
226+
jpeg_destroy_decompress(&cinfo);
227+
delete[] row_pointers;
228+
return;
229+
}
230+
jpeg_read_scanlines(&cinfo, &row_pointers[cinfo.output_scanline], cinfo.output_height - cinfo.output_scanline);
230231
}
231232

232233
// finish the decompression step
@@ -398,7 +399,7 @@ JPEGImageIO::Write(const void * buffer)
398399
}
399400

400401
void
401-
JPEGImageIO::WriteSlice(std::string & fileName, const void * buffer)
402+
JPEGImageIO::WriteSlice(std::string & fileName, const void * const buffer)
402403
{
403404
// use this class so return will call close
404405
JPEGFileWrapper JPEGfp(fileName.c_str(), "wb");
@@ -410,22 +411,14 @@ JPEGImageIO::WriteSlice(std::string & fileName, const void * buffer)
410411
<< "Reason: " << itksys::SystemTools::GetLastSystemError());
411412
}
412413

413-
// Call the correct templated function for the output
414-
415-
// overriding jpeg_error_mgr so we don't exit when an error happens
416-
// Create the jpeg compression object and error handler
417-
// struct jpeg_compress_struct cinfo;
418-
// struct itk_jpeg_error_mgr jerr;
419-
420414
struct itk_jpeg_error_mgr jerr;
421415
struct jpeg_compress_struct cinfo;
422416
cinfo.err = jpeg_std_error(&jerr.pub);
423-
// set the jump point, if there is a jpeg error or warning
424-
// this will evaluate to true
425-
if (wrapSetjmp(jerr))
417+
// set the jump point
418+
if (setjmp(jerr.setjmp_buffer))
426419
{
427420
jpeg_destroy_compress(&cinfo);
428-
itkExceptionMacro(<< "JPEG : Out of disk space");
421+
itkExceptionMacro(<< "JPEG error, failed to write " << fileName);
429422
}
430423

431424
jpeg_create_compress(&cinfo);
@@ -527,6 +520,7 @@ JPEGImageIO::WriteSlice(std::string & fileName, const void * buffer)
527520

528521
if (fflush(fp) == EOF)
529522
{
523+
delete[] row_pointers;
530524
itkExceptionMacro(<< "JPEG : Out of disk space");
531525
}
532526

Modules/IO/JPEG/test/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ itk_module_test()
22
set(ITKIOJPEGTests
33
itkJPEGImageIOTest.cxx
44
itkJPEGImageIOTest2.cxx
5+
itkJPEGImageIODegenerateCasesTest.cxx
56
)
67

78
CreateTestDriver(ITKIOJPEG "${ITKIOJPEG-Test_LIBRARIES}" "${ITKIOJPEGTests}")
@@ -19,3 +20,6 @@ itk_add_test(NAME itkJPEGImageIOTest2
1920
itk_add_test(NAME itkJPEGImageIOSpacing
2021
COMMAND ITKIOJPEGTestDriver
2122
itkJPEGImageIOTest2 ${ITK_TEST_OUTPUT_DIR}/itkJPEGImageIOSpacing.jpg)
23+
itk_add_test(NAME itkJPEGImageIOTestCorruptedImage
24+
COMMAND ITKIOJPEGTestDriver
25+
itkJPEGImageIODegenerateCasesTest DATA{Input/corrupted_image.jpg})
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
c97188b2a9c38dae4e8a40ed6e533cb3380775d87431c97e3cc92a3dd80215c6b37fffc9db855ef22e940497027a873cdff4f191fa40236eafc217fb32e994cd
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*=========================================================================
2+
*
3+
* Copyright NumFOCUS
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0.txt
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*=========================================================================*/
18+
19+
#include "itkJPEGImageIO.h"
20+
#include "itkImageFileReader.h"
21+
#include "itkTestingMacros.h"
22+
23+
24+
int
25+
itkJPEGImageIODegenerateCasesTest(int argc, char * argv[])
26+
{
27+
if (argc != 2)
28+
{
29+
std::cerr << "Missing parameters." << std::endl;
30+
std::cerr << "Usage: " << itkNameOfTestExecutableMacro(argv);
31+
std::cerr << " inputFilename" << std::endl;
32+
return EXIT_FAILURE;
33+
}
34+
35+
constexpr unsigned int Dimension = 2;
36+
using PixelType = unsigned char;
37+
38+
using ImageType = itk::Image<PixelType, Dimension>;
39+
40+
itk::JPEGImageIO::Pointer io = itk::JPEGImageIO::New();
41+
42+
itk::ImageFileReader<ImageType>::Pointer reader = itk::ImageFileReader<ImageType>::New();
43+
44+
reader->SetFileName(argv[1]);
45+
reader->SetImageIO(io);
46+
47+
ITK_TRY_EXPECT_NO_EXCEPTION(reader->Update());
48+
49+
50+
std::cout << "Test finished." << std::endl;
51+
return EXIT_SUCCESS;
52+
}

0 commit comments

Comments
 (0)