blob: 66c43dd5e375afe407573702630ca3c6cd806302 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <va/va.h>
#include <algorithm>
#include "base/callback_helpers.h"
#include "base/cxx17_backports.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/numerics/safe_conversions.h"
#include "media/gpu/vaapi/fuzzers/jpeg_decoder/jpeg_decoder_fuzzer_input.pb.h"
#include "media/gpu/vaapi/vaapi_jpeg_decoder.h"
#include "media/gpu/vaapi/vaapi_utils.h"
#include "media/gpu/vaapi/vaapi_wrapper.h"
#include "media/parsers/jpeg_parser.h"
#include "testing/libfuzzer/proto/lpm_interface.h"
#include "ui/gfx/geometry/size.h"
// This file defines a fuzzer test for the JPEG decoding functionality of the
// VA-API. To do the fuzzing, we re-use code from media::VaapiJpegDecoder: we
// poke at its internals to get past the parsing and image support validation.
// That way, we can inject a large variety of data into the VA-API driver and
// exercise more paths than if we just pass raw data to the
// media::VaapiJpegDecoder using its public API.
//
// The VA-API does not take raw JPEG data. Instead, it expects that some
// pre-parsing was done. Therefore, we feed the data as a
// media::JpegParseResult. To generate the fuzzing inputs, we use
// libprotobuf-mutator, so we define a protobuf that mirrors the structure of
// media::JpegParseResult.
//
// This fuzzer should be run on a real device that supports the VA-API.
// Additionally, libva and the backend user-space VA-API driver (plus its
// dependencies) should be instrumented for fuzzing.
namespace {
// Converts |proto_huffman_table| to a media::JpegHuffmanTable which can be used
// to populate the Huffman table fields of a media::JpegParseResult.
media::JpegHuffmanTable ConvertToJpegHuffmanTable(
const media::fuzzing::JpegHuffmanTable& proto_huffman_table) {
media::JpegHuffmanTable huffman_table{};
huffman_table.valid = proto_huffman_table.valid();
memcpy(huffman_table.code_length, proto_huffman_table.code_length().data(),
std::min(base::size(huffman_table.code_length),
proto_huffman_table.code_length().size()));
memcpy(huffman_table.code_value, proto_huffman_table.code_value().data(),
std::min(base::size(huffman_table.code_value),
proto_huffman_table.code_value().size()));
return huffman_table;
}
// Converts |proto_parse_result| to a media::JpegParseResult that can be used as
// the input for VaapiJpegDecoder::SubmitBuffers(). The data field of the return
// value will point to the same memory as |proto_parse_result|.data().data(), so
// we assume |proto_parse_result| will live for as long as that data needs to be
// used.
media::JpegParseResult ConvertToJpegParseResult(
const media::fuzzing::JpegParseResult& proto_parse_result) {
media::JpegParseResult parse_result{};
// Convert the frame header.
parse_result.frame_header.visible_width =
proto_parse_result.frame_header().visible_width() & 0xFFFF;
parse_result.frame_header.visible_height =
proto_parse_result.frame_header().visible_height() & 0xFFFF;
parse_result.frame_header.coded_width =
proto_parse_result.frame_header().coded_width() & 0xFFFF;
parse_result.frame_header.coded_height =
proto_parse_result.frame_header().coded_height() & 0xFFFF;
const size_t frame_header_num_components =
std::min(base::size(parse_result.frame_header.components),
base::checked_cast<size_t>(
proto_parse_result.frame_header().components_size()));
for (size_t i = 0; i < frame_header_num_components; i++) {
const auto& input_jpeg_component =
proto_parse_result.frame_header().components()[i];
parse_result.frame_header.components[i].id =
input_jpeg_component.id() & 0xFF;
parse_result.frame_header.components[i].horizontal_sampling_factor =
input_jpeg_component.horizontal_sampling_factor() & 0xFF;
parse_result.frame_header.components[i].vertical_sampling_factor =
input_jpeg_component.vertical_sampling_factor() & 0xFF;
parse_result.frame_header.components[i].quantization_table_selector =
input_jpeg_component.quantization_table_selector() & 0xFF;
}
parse_result.frame_header.num_components =
static_cast<uint8_t>(frame_header_num_components);
// Convert the DC/AC Huffman tables.
const size_t num_dc_tables =
std::min(base::size(parse_result.dc_table),
base::checked_cast<size_t>(proto_parse_result.dc_table_size()));
for (size_t i = 0; i < num_dc_tables; i++) {
parse_result.dc_table[i] =
ConvertToJpegHuffmanTable(proto_parse_result.dc_table()[i]);
}
const size_t num_ac_tables =
std::min(base::size(parse_result.ac_table),
base::checked_cast<size_t>(proto_parse_result.ac_table_size()));
for (size_t i = 0; i < num_ac_tables; i++) {
parse_result.ac_table[i] =
ConvertToJpegHuffmanTable(proto_parse_result.ac_table()[i]);
}
// Convert the quantization tables.
const size_t num_q_tables =
std::min(base::size(parse_result.q_table),
base::checked_cast<size_t>(proto_parse_result.q_table_size()));
for (size_t i = 0; i < num_q_tables; i++) {
const media::fuzzing::JpegQuantizationTable& input_q_table =
proto_parse_result.q_table()[i];
parse_result.q_table[i].valid = input_q_table.valid();
memcpy(parse_result.q_table[i].value, input_q_table.value().data(),
std::min(base::size(parse_result.q_table[i].value),
input_q_table.value().size()));
}
// Convert the scan header.
const size_t scan_num_components = std::min(
base::size(parse_result.scan.components),
base::checked_cast<size_t>(proto_parse_result.scan().components_size()));
for (size_t i = 0; i < scan_num_components; i++) {
const media::fuzzing::JpegScanHeader::Component& input_component =
proto_parse_result.scan().components()[i];
parse_result.scan.components[i].component_selector =
input_component.component_selector() & 0xFF;
parse_result.scan.components[i].dc_selector =
input_component.dc_selector() & 0xFF;
parse_result.scan.components[i].ac_selector =
input_component.ac_selector() & 0xFF;
}
parse_result.scan.num_components = static_cast<uint8_t>(scan_num_components);
// Convert the coded data. Note that we don't do a deep copy, so we assume
// that |proto_parse_result| will live for as long as |parse_result|.data is
// needed.
parse_result.data = proto_parse_result.data().data();
parse_result.data_size = proto_parse_result.data().size();
// Convert the rest of the fields.
parse_result.restart_interval =
proto_parse_result.restart_interval() & 0xFFFF;
parse_result.image_size =
base::strict_cast<size_t>(proto_parse_result.image_size());
return parse_result;
}
unsigned int ConvertToVARTFormat(
media::fuzzing::VARTFormat proto_picture_va_rt_format) {
switch (proto_picture_va_rt_format) {
case media::fuzzing::VARTFormat::INVALID:
return media::kInvalidVaRtFormat;
case media::fuzzing::VARTFormat::YUV420:
return VA_RT_FORMAT_YUV420;
case media::fuzzing::VARTFormat::YUV422:
return VA_RT_FORMAT_YUV422;
case media::fuzzing::VARTFormat::YUV444:
return VA_RT_FORMAT_YUV444;
case media::fuzzing::VARTFormat::YUV411:
return VA_RT_FORMAT_YUV411;
case media::fuzzing::VARTFormat::YUV400:
return VA_RT_FORMAT_YUV400;
case media::fuzzing::VARTFormat::YUV420_10:
return VA_RT_FORMAT_YUV420_10;
case media::fuzzing::VARTFormat::YUV422_10:
return VA_RT_FORMAT_YUV422_10;
case media::fuzzing::VARTFormat::YUV444_10:
return VA_RT_FORMAT_YUV444_10;
case media::fuzzing::VARTFormat::YUV420_12:
return VA_RT_FORMAT_YUV420_12;
case media::fuzzing::VARTFormat::YUV422_12:
return VA_RT_FORMAT_YUV422_12;
case media::fuzzing::VARTFormat::YUV444_12:
return VA_RT_FORMAT_YUV444_12;
case media::fuzzing::VARTFormat::RGB16:
return VA_RT_FORMAT_RGB16;
case media::fuzzing::VARTFormat::RGB32:
return VA_RT_FORMAT_RGB32;
case media::fuzzing::VARTFormat::RGBP:
return VA_RT_FORMAT_RGBP;
case media::fuzzing::VARTFormat::RGB32_10:
return VA_RT_FORMAT_RGB32_10;
case media::fuzzing::VARTFormat::PROTECTED:
return VA_RT_FORMAT_PROTECTED;
}
}
} // namespace
namespace media {
namespace fuzzing {
// This wrapper lets us poke at the internals of a media::VaapiJpegDecoder. It
// somewhat mirrors the way media::VaapiImageDecoder drives a VaapiJpegDecoder.
class VaapiJpegDecoderWrapper {
public:
VaapiJpegDecoderWrapper() = default;
~VaapiJpegDecoderWrapper() = default;
bool Initialize() { return decoder_.Initialize(base::DoNothing()); }
bool MaybeCreateSurface(unsigned int picture_va_rt_format,
const gfx::Size& new_coded_size,
const gfx::Size& new_visible_size) {
if (!decoder_.MaybeCreateSurface(picture_va_rt_format, new_coded_size,
new_visible_size)) {
decoder_.scoped_va_context_and_surface_.reset();
return false;
}
return true;
}
bool SubmitBuffers(const media::JpegParseResult& parse_result) {
if (!decoder_.SubmitBuffers(parse_result)) {
decoder_.scoped_va_context_and_surface_.reset();
return false;
}
return true;
}
bool ExecuteAndDestroyPendingBuffers() {
if (!decoder_.vaapi_wrapper_->ExecuteAndDestroyPendingBuffers(
decoder_.scoped_va_context_and_surface_->id())) {
decoder_.scoped_va_context_and_surface_.reset();
return false;
}
return true;
}
private:
VaapiJpegDecoder decoder_;
};
struct Environment {
Environment() { VaapiWrapper::PreSandboxInitialization(); }
};
DEFINE_PROTO_FUZZER(const JpegImageList& image_list) {
static const Environment env;
VaapiJpegDecoderWrapper decoder_wrapper;
if (!decoder_wrapper.Initialize()) {
LOG(ERROR) << "Cannot initialize the VaapiJpegDecoder";
abort();
}
for (const auto& jpeg_image : image_list.images()) {
if (!decoder_wrapper.MaybeCreateSurface(
ConvertToVARTFormat(jpeg_image.picture_va_rt_format()),
gfx::Size(
base::strict_cast<int>(jpeg_image.surface_coded_width()),
base::strict_cast<int>(jpeg_image.surface_coded_height())),
gfx::Size(
base::strict_cast<int>(jpeg_image.surface_visible_width()),
base::strict_cast<int>(jpeg_image.surface_visible_height())))) {
continue;
}
if (!decoder_wrapper.SubmitBuffers(
ConvertToJpegParseResult(jpeg_image.parse_result()))) {
continue;
}
if (!decoder_wrapper.ExecuteAndDestroyPendingBuffers())
continue;
// TODO(crbug.com/1034357): for decodes that succeeded, it would be good to
// get the result as a dma-buf, map it, and read it to make sure that doing
// so does not lead us into an invalid state.
}
}
} // namespace fuzzing
} // namespace media