fuzzing/color_conversion_fuzzer.cc (229 lines of code) (raw):
/*
* HEIF codec.
* Copyright (c) 2019 struktur AG, Joachim Bauch <bauch@struktur.de>
*
* This file is part of libheif.
*
* libheif is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* libheif is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with libheif. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <sstream>
#include "bitstream.h"
#include "color-conversion/colorconversion.h"
#include "pixelimage.h"
static bool is_valid_chroma(uint8_t chroma)
{
switch (chroma) {
case heif_chroma_monochrome:
case heif_chroma_420:
case heif_chroma_422:
case heif_chroma_444:
case heif_chroma_interleaved_RGB:
case heif_chroma_interleaved_RGBA:
case heif_chroma_interleaved_RRGGBB_BE:
case heif_chroma_interleaved_RRGGBBAA_BE:
case heif_chroma_interleaved_RRGGBB_LE:
case heif_chroma_interleaved_RRGGBBAA_LE:
return true;
default:
return false;
}
}
static bool is_valid_colorspace(uint8_t colorspace)
{
switch (colorspace) {
case heif_colorspace_YCbCr:
case heif_colorspace_RGB:
case heif_colorspace_monochrome:
return true;
default:
return false;
}
}
static bool read_plane(BitstreamRange* range,
std::shared_ptr<HeifPixelImage> image, heif_channel channel,
int width, int height, int bit_depth)
{
if (width <= 0 || height <= 0) {
return false;
}
if (std::numeric_limits<size_t>::max()/width/height == 0) {
return false;
}
if (!range->prepare_read(static_cast<size_t>(width) * height)) {
return false;
}
if (!image->add_plane(channel, width, height, bit_depth)) {
return false;
}
int stride;
uint8_t* plane = image->get_plane(channel, &stride);
assert(stride >= width);
auto stream = range->get_istream();
for (int y = 0; y < height; y++, plane += stride) {
assert(stream->read(plane, width));
}
return true;
}
static bool read_plane_interleaved(BitstreamRange* range,
std::shared_ptr<HeifPixelImage> image, heif_channel channel,
int width, int height, int bit_depth, int comps)
{
if (width <= 0 || height <= 0) {
return false;
}
if (std::numeric_limits<size_t>::max()/width/height/comps == 0) {
return false;
}
if (!range->prepare_read(static_cast<size_t>(width) * height * comps)) {
return false;
}
if (!image->add_plane(channel, width, height, bit_depth)) {
return false;
}
int stride;
uint8_t* plane = image->get_plane(channel, &stride);
assert(stride >= width * comps);
auto stream = range->get_istream();
for (int y = 0; y < height; y++, plane += stride) {
assert(stream->read(plane, width * comps));
}
return true;
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
{
auto reader = std::make_shared<StreamReader_memory>(data, size, false);
BitstreamRange range(reader, size);
int width;
int height;
int bit_depth;
bool alpha;
uint8_t in_chroma;
uint8_t in_colorspace;
uint8_t out_chroma;
uint8_t out_colorspace;
if (!range.prepare_read(10)) {
return 0;
}
width = range.read16();
height = range.read16();
bit_depth = range.read8();
alpha = range.read8() == 1;
in_chroma = range.read8();
in_colorspace = range.read8();
out_chroma = range.read8();
out_colorspace = range.read8();
// Width / height must be a multiple of 2.
if (width == 0 || height == 0 || (width & 1) != 0 || (height & 1) != 0) {
return 0;
}
switch (bit_depth) {
case 8:
break;
default:
// TODO: Add support for more color depths.
return 0;
}
if (!is_valid_chroma(in_chroma) || !is_valid_colorspace(in_colorspace) ||
!is_valid_chroma(out_chroma) || !is_valid_colorspace(out_colorspace)) {
return 0;
}
auto in_image = std::make_shared<HeifPixelImage>();
in_image->create(width, height, static_cast<heif_colorspace>(in_colorspace),
static_cast<heif_chroma>(in_chroma));
switch (in_colorspace) {
case heif_colorspace_YCbCr:
switch (in_chroma) {
case heif_chroma_420:
if (!read_plane(&range, in_image, heif_channel_Y,
width, height, bit_depth)) {
return 0;
}
if (!read_plane(&range, in_image, heif_channel_Cb,
width / 2, height / 2, bit_depth)) {
return 0;
}
if (!read_plane(&range, in_image, heif_channel_Cr,
width / 2, height / 2, bit_depth)) {
return 0;
}
break;
case heif_chroma_422:
if (!read_plane(&range, in_image, heif_channel_Y,
width, height, bit_depth)) {
return 0;
}
if (!read_plane(&range, in_image, heif_channel_Cb,
width / 2, height, bit_depth)) {
return 0;
}
if (!read_plane(&range, in_image, heif_channel_Cr,
width / 2, height, bit_depth)) {
return 0;
}
break;
case heif_chroma_444:
if (!read_plane(&range, in_image, heif_channel_Y,
width, height, bit_depth)) {
return 0;
}
if (!read_plane(&range, in_image, heif_channel_Cb,
width, height, bit_depth)) {
return 0;
}
if (!read_plane(&range, in_image, heif_channel_Cr,
width, height, bit_depth)) {
return 0;
}
break;
default:
return 0;
}
break;
case heif_colorspace_RGB:
switch (in_chroma) {
case heif_chroma_interleaved_RGB:
if (!read_plane_interleaved(&range, in_image,
heif_channel_interleaved, width, height, bit_depth, 3)) {
return 0;
}
break;
case heif_chroma_interleaved_RGBA:
if (!read_plane_interleaved(&range, in_image,
heif_channel_interleaved, width, height, bit_depth, 4)) {
return 0;
}
alpha = false; // Already part of interleaved data.
break;
default:
// TODO: Support other RGB chromas.
return 0;
}
break;
case heif_colorspace_monochrome:
if (in_chroma != heif_chroma_monochrome) {
return 0;
}
if (!read_plane(&range, in_image, heif_channel_Y,
width, height, bit_depth)) {
return 0;
}
break;
default:
assert(false);
}
if (alpha) {
if (!read_plane(&range, in_image, heif_channel_Alpha,
width, height, bit_depth)) {
return 0;
}
}
// TODO: also fuzz these parameters.
int output_bpp = 0; // Same as input.
heif_encoding_options* options = heif_encoding_options_alloc();
auto out_image = convert_colorspace(in_image,
static_cast<heif_colorspace>(out_colorspace),
static_cast<heif_chroma>(out_chroma),
nullptr,
output_bpp,
options->color_conversion_options);
heif_encoding_options_free(options);
if (!out_image) {
// Conversion is not supported.
return 0;
}
assert(out_image->get_width() == width);
assert(out_image->get_height() == height);
assert(out_image->get_chroma_format() ==
static_cast<heif_chroma>(out_chroma));
assert(out_image->get_colorspace() ==
static_cast<heif_colorspace>(out_colorspace));
return 0;
}