Commit 1e381841 authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

Data decoder: Limit JPEG decode size

This caps JPEG decode size in the Data Decoder service at 1 GB of
uncompressed image data in an attempt to stop excessively large image
decodes from inducing OOM crashes.

Bug: 1014022
Change-Id: Idb3dfed8cf383fbe58d9521093a1d11f7feee01e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1866208Reviewed-by: default avatarJorge Lucangeli Obes <jorgelo@chromium.org>
Commit-Queue: Ken Rockot <rockot@google.com>
Cr-Commit-Position: refs/heads/master@{#709170}
parent c3e9aea7
...@@ -25,6 +25,13 @@ namespace data_decoder { ...@@ -25,6 +25,13 @@ namespace data_decoder {
namespace { namespace {
#if defined(OS_CHROMEOS)
// NOTE: This 1 GB limit is arbitrary and may be subject to change. The purpose
// of limiting image decode size is to avoid OOM crashes caused by very large
// image data being thrown at the service.
constexpr size_t kJpegMaxDecodedNumBytes = 1024 * 1024 * 1024;
#endif
int64_t kPadding = 64; int64_t kPadding = 64;
void ResizeImage(SkBitmap* decoded_image, void ResizeImage(SkBitmap* decoded_image,
...@@ -81,7 +88,7 @@ void ImageDecoderImpl::DecodeImage(const std::vector<uint8_t>& encoded_data, ...@@ -81,7 +88,7 @@ void ImageDecoderImpl::DecodeImage(const std::vector<uint8_t>& encoded_data,
// Our robust jpeg decoding is using IJG libjpeg. // Our robust jpeg decoding is using IJG libjpeg.
if (encoded_data.size()) { if (encoded_data.size()) {
std::unique_ptr<SkBitmap> decoded_jpeg(gfx::JPEGCodecRobustSlow::Decode( std::unique_ptr<SkBitmap> decoded_jpeg(gfx::JPEGCodecRobustSlow::Decode(
encoded_data.data(), encoded_data.size())); encoded_data, kJpegMaxDecodedNumBytes));
if (decoded_jpeg.get() && !decoded_jpeg->empty()) if (decoded_jpeg.get() && !decoded_jpeg->empty())
decoded_image = *decoded_jpeg; decoded_image = *decoded_jpeg;
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include <memory> #include <memory>
#include "base/logging.h" #include "base/logging.h"
#include "base/numerics/safe_conversions.h"
extern "C" { extern "C" {
// IJG provides robust JPEG decode // IJG provides robust JPEG decode
...@@ -43,14 +44,6 @@ void IjgErrorExit(jpeg_common_struct* cinfo) { ...@@ -43,14 +44,6 @@ void IjgErrorExit(jpeg_common_struct* cinfo) {
namespace { namespace {
struct IjgJpegDecoderState {
IjgJpegDecoderState(const unsigned char* in, size_t len)
: input_buffer(in), input_buffer_length(len) {}
const unsigned char* input_buffer;
size_t input_buffer_length;
};
// Callback to initialize the source. // Callback to initialize the source.
// //
// From the JPEG library: // From the JPEG library:
...@@ -58,10 +51,10 @@ struct IjgJpegDecoderState { ...@@ -58,10 +51,10 @@ struct IjgJpegDecoderState {
// actually read. May leave bytes_in_buffer set to 0 (in which case a // actually read. May leave bytes_in_buffer set to 0 (in which case a
// fill_input_buffer() call will occur immediately)." // fill_input_buffer() call will occur immediately)."
void IjgInitSource(j_decompress_ptr cinfo) { void IjgInitSource(j_decompress_ptr cinfo) {
IjgJpegDecoderState* state = auto* compressed_data =
static_cast<IjgJpegDecoderState*>(cinfo->client_data); static_cast<base::span<const uint8_t>*>(cinfo->client_data);
cinfo->src->next_input_byte = state->input_buffer; cinfo->src->next_input_byte = compressed_data->data();
cinfo->src->bytes_in_buffer = state->input_buffer_length; cinfo->src->bytes_in_buffer = compressed_data->size();
} }
// Callback to fill the buffer. Since our buffer already contains all the data, // Callback to fill the buffer. Since our buffer already contains all the data,
...@@ -118,7 +111,7 @@ void IjgTermSource(j_decompress_ptr cinfo) {} ...@@ -118,7 +111,7 @@ void IjgTermSource(j_decompress_ptr cinfo) {}
#if !defined(JCS_EXTENSIONS) #if !defined(JCS_EXTENSIONS)
// Converts one row of rgb data to rgba data by adding a fully-opaque alpha // Converts one row of rgb data to rgba data by adding a fully-opaque alpha
// value. // value.
void AddAlpha(const unsigned char* rgb, int pixel_width, unsigned char* rgba) { void AddAlpha(const uint8_t* rgb, int pixel_width, uint8_t* rgba) {
for (int x = 0; x < pixel_width; x++) { for (int x = 0; x < pixel_width; x++) {
memcpy(&rgba[x * 4], &rgb[x * 3], 3); memcpy(&rgba[x * 4], &rgb[x * 3], 3);
rgba[x * 4 + 3] = 0xff; rgba[x * 4 + 3] = 0xff;
...@@ -127,10 +120,10 @@ void AddAlpha(const unsigned char* rgb, int pixel_width, unsigned char* rgba) { ...@@ -127,10 +120,10 @@ void AddAlpha(const unsigned char* rgb, int pixel_width, unsigned char* rgba) {
// Converts one row of RGB data to BGRA by reordering the color components and // Converts one row of RGB data to BGRA by reordering the color components and
// adding alpha values of 0xff. // adding alpha values of 0xff.
void RGBtoBGRA(const unsigned char* bgra, int pixel_width, unsigned char* rgb) { void RGBtoBGRA(const uint8_t* bgra, int pixel_width, uint8_t* rgb) {
for (int x = 0; x < pixel_width; x++) { for (int x = 0; x < pixel_width; x++) {
const unsigned char* pixel_in = &bgra[x * 3]; const uint8_t* pixel_in = &bgra[x * 3];
unsigned char* pixel_out = &rgb[x * 4]; uint8_t* pixel_out = &rgb[x * 4];
pixel_out[0] = pixel_in[2]; pixel_out[0] = pixel_in[2];
pixel_out[1] = pixel_in[1]; pixel_out[1] = pixel_in[1];
pixel_out[2] = pixel_in[0]; pixel_out[2] = pixel_in[0];
...@@ -149,12 +142,12 @@ struct JpegRobustDecompressStructDeleter { ...@@ -149,12 +142,12 @@ struct JpegRobustDecompressStructDeleter {
} // namespace } // namespace
bool JPEGCodecRobustSlow::Decode(const unsigned char* input, bool JPEGCodecRobustSlow::Decode(base::span<const uint8_t> compressed_data,
size_t input_size,
ColorFormat format, ColorFormat format,
std::vector<unsigned char>* output, std::vector<uint8_t>* output,
int* w, int* w,
int* h) { int* h,
base::Optional<size_t> max_decoded_num_bytes) {
std::unique_ptr<jpeg_decompress_struct, JpegRobustDecompressStructDeleter> std::unique_ptr<jpeg_decompress_struct, JpegRobustDecompressStructDeleter>
cinfo(new jpeg_decompress_struct); cinfo(new jpeg_decompress_struct);
output->clear(); output->clear();
...@@ -185,8 +178,7 @@ bool JPEGCodecRobustSlow::Decode(const unsigned char* input, ...@@ -185,8 +178,7 @@ bool JPEGCodecRobustSlow::Decode(const unsigned char* input,
srcmgr.term_source = IjgTermSource; srcmgr.term_source = IjgTermSource;
cinfo->src = &srcmgr; cinfo->src = &srcmgr;
IjgJpegDecoderState state(input, input_size); cinfo->client_data = &compressed_data;
cinfo->client_data = &state;
// fill the file metadata into our buffer // fill the file metadata into our buffer
if (jpeg_read_header(cinfo.get(), true) != JPEG_HEADER_OK) if (jpeg_read_header(cinfo.get(), true) != JPEG_HEADER_OK)
...@@ -237,83 +229,83 @@ bool JPEGCodecRobustSlow::Decode(const unsigned char* input, ...@@ -237,83 +229,83 @@ bool JPEGCodecRobustSlow::Decode(const unsigned char* input,
*w = cinfo->output_width; *w = cinfo->output_width;
*h = cinfo->output_height; *h = cinfo->output_height;
jpeg_start_decompress(cinfo.get());
// FIXME(brettw) we may want to allow the capability for callers to request // FIXME(brettw) we may want to allow the capability for callers to request
// how to align row lengths as we do for the compressor. // how to align row lengths as we do for the compressor.
int row_read_stride = cinfo->output_width * cinfo->output_components; const size_t decoded_row_stride =
cinfo->output_width *
#ifdef JCS_EXTENSIONS base::saturated_cast<size_t>(cinfo->output_components);
// Create memory for a decoded image and write decoded lines to the memory void (*converter)(const uint8_t* rgb, int w, uint8_t* out) = nullptr;
// without conversions same as JPEGCodec::Encode(). size_t output_row_stride;
int row_write_stride = row_read_stride;
output->resize(row_write_stride * cinfo->output_height); #if defined(JCS_EXTENSIONS)
// Easy case: rows need no pixel format conversion.
for (int row = 0; row < static_cast<int>(cinfo->output_height); row++) { output_row_stride = decoded_row_stride;
unsigned char* rowptr = &(*output)[row * row_write_stride];
if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
return false;
}
#else #else
if (format == FORMAT_RGB) { if (format == FORMAT_RGB) {
// easy case, row needs no conversion // Easy case: rows need no pixel format conversion.
int row_write_stride = row_read_stride; output_row_stride = decoded_row_stride;
output->resize(row_write_stride * cinfo->output_height);
for (int row = 0; row < static_cast<int>(cinfo->output_height); row++) {
unsigned char* rowptr = &(*output)[row * row_write_stride];
if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
return false;
}
} else { } else {
// Rows need conversion to output format: read into a temporary buffer and // Rows will need a pixel format conversion to output format.
// expand to the final one. Performance: we could avoid the extra
// allocation by doing the expansion in-place.
int row_write_stride;
void (*converter)(const unsigned char* rgb, int w, unsigned char* out);
if (format == FORMAT_RGBA || if (format == FORMAT_RGBA ||
(format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) { (format == FORMAT_SkBitmap && SK_R32_SHIFT == 0)) {
row_write_stride = cinfo->output_width * 4; output_row_stride = cinfo->output_width * 4;
converter = AddAlpha; converter = AddAlpha;
} else if (format == FORMAT_BGRA || } else if (format == FORMAT_BGRA ||
(format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) { (format == FORMAT_SkBitmap && SK_B32_SHIFT == 0)) {
row_write_stride = cinfo->output_width * 4; output_row_stride = cinfo->output_width * 4;
converter = RGBtoBGRA; converter = RGBtoBGRA;
} else { } else {
NOTREACHED() << "Invalid pixel format"; NOTREACHED() << "Invalid pixel format";
jpeg_destroy_decompress(cinfo.get());
return false; return false;
} }
}
#endif
output->resize(row_write_stride * cinfo->output_height); const size_t output_num_bytes = output_row_stride * cinfo->output_height;
if (max_decoded_num_bytes && output_num_bytes > *max_decoded_num_bytes)
return false;
jpeg_start_decompress(cinfo.get());
std::unique_ptr<unsigned char[]> row_data( output->resize(output_num_bytes);
new unsigned char[row_read_stride]); if (converter) {
unsigned char* rowptr = row_data.get(); // The decoded pixels need to be converted to the output format. We reuse
for (int row = 0; row < static_cast<int>(cinfo->output_height); row++) { // this temporary buffer to decode each row and then write their converted
// form to the real output buffer.
auto row_data = std::make_unique<uint8_t[]>(decoded_row_stride);
uint8_t* rowptr = row_data.get();
for (size_t row = 0; row < cinfo->output_height; ++row) {
if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
return false;
converter(rowptr, *w, &(*output)[row * output_row_stride]);
}
} else {
for (size_t row = 0; row < cinfo->output_height; ++row) {
uint8_t* rowptr = &(*output)[row * output_row_stride];
if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1)) if (!jpeg_read_scanlines(cinfo.get(), &rowptr, 1))
return false; return false;
converter(rowptr, *w, &(*output)[row * row_write_stride]);
} }
} }
#endif
jpeg_finish_decompress(cinfo.get()); jpeg_finish_decompress(cinfo.get());
return true; return true;
} }
// static // static
SkBitmap* JPEGCodecRobustSlow::Decode(const unsigned char* input, std::unique_ptr<SkBitmap> JPEGCodecRobustSlow::Decode(
size_t input_size) { base::span<const uint8_t> compressed_data,
base::Optional<size_t> max_decoded_num_bytes) {
int w, h; int w, h;
std::vector<unsigned char> data_vector; std::vector<uint8_t> data_vector;
if (!Decode(input, input_size, FORMAT_SkBitmap, &data_vector, &w, &h)) if (!Decode(compressed_data, FORMAT_SkBitmap, &data_vector, &w, &h,
return NULL; max_decoded_num_bytes)) {
return nullptr;
}
// Skia only handles 32 bit images. // Skia only handles 32 bit images.
int data_length = w * h * 4; int data_length = w * h * 4;
SkBitmap* bitmap = new SkBitmap(); auto bitmap = std::make_unique<SkBitmap>();
bitmap->allocN32Pixels(w, h); bitmap->allocN32Pixels(w, h);
memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length); memcpy(bitmap->getAddr32(0, 0), &data_vector[0], data_length);
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include <stddef.h> #include <stddef.h>
#include <vector> #include <vector>
#include "base/containers/span.h"
#include "base/optional.h"
#include "ui/gfx/codec/codec_export.h" #include "ui/gfx/codec/codec_export.h"
class SkBitmap; class SkBitmap;
...@@ -37,21 +39,26 @@ class CODEC_EXPORT JPEGCodecRobustSlow { ...@@ -37,21 +39,26 @@ class CODEC_EXPORT JPEGCodecRobustSlow {
FORMAT_SkBitmap FORMAT_SkBitmap
}; };
// Decodes the JPEG data contained in input of length input_size. The // Decodes the JPEG data contained in |compressed_data|. The decoded data will
// decoded data will be placed in *output with the dimensions in *w and *h // be placed in |*output| with the dimensions in |*w| and |*h| on success
// on success (returns true). This data will be written in the'format' // (returns |true|). This data will be written in the |format| format.
// format. On failure, the values of these output variables is undefined. //
static bool Decode(const unsigned char* input, // On failure, the values of these output variables is undefined.
size_t input_size, //
// This will fail immediately without attempting to decode aything if the
// decompressed image data size would exceed |max_decoded_num_bytes| when
// given.
static bool Decode(base::span<const uint8_t> compressed_data,
ColorFormat format, ColorFormat format,
std::vector<unsigned char>* output, std::vector<uint8_t>* output,
int* w, int* w,
int* h); int* h,
base::Optional<size_t> max_decoded_num_bytes);
// Decodes the JPEG data contained in input of length input_size. If // Same as above, but the image is returned as an SkBitmap.
// successful, a SkBitmap is created and returned. It is up to the caller static std::unique_ptr<SkBitmap> Decode(
// to delete the returned bitmap. base::span<const uint8_t> compressed_data,
static SkBitmap* Decode(const unsigned char* input, size_t input_size); base::Optional<size_t> max_decoded_num_bytes);
}; };
} // namespace gfx } // namespace gfx
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
#include <math.h> #include <math.h>
#include <stdint.h> #include <stdint.h>
#include "base/containers/span.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/codec/chromeos/jpeg_codec_robust_slow.h" #include "ui/gfx/codec/chromeos/jpeg_codec_robust_slow.h"
...@@ -59,7 +60,7 @@ const uint8_t kTopSitesMigrationTestImage[] = ...@@ -59,7 +60,7 @@ const uint8_t kTopSitesMigrationTestImage[] =
namespace gfx { namespace gfx {
static void MakeRGBImage(int w, int h, std::vector<unsigned char>* dat) { static void MakeRGBImage(int w, int h, std::vector<uint8_t>* dat) {
dat->resize(w * h * 3); dat->resize(w * h * 3);
for (int y = 0; y < h; y++) { for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) { for (int x = 0; x < w; x++) {
...@@ -76,15 +77,15 @@ TEST(JPEGCodecRobustSlow, DecodeCorrupted) { ...@@ -76,15 +77,15 @@ TEST(JPEGCodecRobustSlow, DecodeCorrupted) {
int w = 20, h = 20; int w = 20, h = 20;
// some random data (an uncompressed image) // some random data (an uncompressed image)
std::vector<unsigned char> original; std::vector<uint8_t> original;
MakeRGBImage(w, h, &original); MakeRGBImage(w, h, &original);
// it should fail when given non-JPEG compressed data // it should fail when given non-JPEG compressed data
std::vector<unsigned char> output; std::vector<uint8_t> output;
int outw, outh; int outw, outh;
ASSERT_FALSE(JPEGCodecRobustSlow::Decode(&original[0], original.size(), ASSERT_FALSE(JPEGCodecRobustSlow::Decode(
JPEGCodecRobustSlow::FORMAT_RGB, original, JPEGCodecRobustSlow::FORMAT_RGB, &output, &outw, &outh,
&output, &outw, &outh)); base::nullopt /* max_decoded_num_bytes */));
} }
// Test that we can decode JPEG images without invalid-read errors on valgrind. // Test that we can decode JPEG images without invalid-read errors on valgrind.
...@@ -94,11 +95,20 @@ TEST(JPEGCodecRobustSlow, InvalidRead) { ...@@ -94,11 +95,20 @@ TEST(JPEGCodecRobustSlow, InvalidRead) {
std::vector<unsigned char> output; std::vector<unsigned char> output;
int outw, outh; int outw, outh;
ASSERT_TRUE(JPEGCodecRobustSlow::Decode( ASSERT_TRUE(JPEGCodecRobustSlow::Decode(
kTopSitesMigrationTestImage, base::size(kTopSitesMigrationTestImage), kTopSitesMigrationTestImage, JPEGCodecRobustSlow::FORMAT_RGB, &output,
JPEGCodecRobustSlow::FORMAT_RGB, &output, &outw, &outh)); &outw, &outh, base::nullopt /* max_decoded_num_bytes */));
ASSERT_TRUE(JPEGCodecRobustSlow::Decode( ASSERT_TRUE(JPEGCodecRobustSlow::Decode(
kTopSitesMigrationTestImage, base::size(kTopSitesMigrationTestImage), kTopSitesMigrationTestImage, JPEGCodecRobustSlow::FORMAT_RGBA, &output,
JPEGCodecRobustSlow::FORMAT_RGBA, &output, &outw, &outh)); &outw, &outh, base::nullopt /* max_decoded_num_bytes */));
}
// Test that we can limit the size of a decode operation.
TEST(JPEGCodecRobustSlow, MaxDecodedSize) {
std::vector<uint8_t> output;
int outw, outh;
EXPECT_FALSE(JPEGCodecRobustSlow::Decode(
kTopSitesMigrationTestImage, JPEGCodecRobustSlow::FORMAT_RGB, &output,
&outw, &outh, 1 /* max_decoded_num_bytes */));
} }
} // namespace gfx } // namespace gfx
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment