Skip to content

openexr: Add grammar base EXR fuzzer #13302

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions projects/openexr/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,17 @@

FROM gcr.io/oss-fuzz-base/base-builder
RUN apt-get update && apt-get install -y make autoconf automake libtool zlib1g-dev
RUN apt-get update -y && \
apt-get install -y make autoconf automake libtool zlib1g-dev \
binutils ninja-build liblzma-dev libz-dev pkg-config

RUN git clone --depth 1 https://github.com/AcademySoftwareFoundation/openexr openexr

RUN git clone --depth 1 https://github.com/google/libprotobuf-mutator.git
RUN (mkdir LPM && cd LPM && cmake ../libprotobuf-mutator -GNinja -DLIB_PROTO_MUTATOR_DOWNLOAD_PROTOBUF=ON -DLIB_PROTO_MUTATOR_TESTING=OFF -DCMAKE_BUILD_TYPE=Release && ninja)

WORKDIR openexr

COPY *.dict *.proto *.h *.cc $SRC/

COPY build.sh $SRC/
25 changes: 25 additions & 0 deletions projects/openexr/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,28 @@ for fuzzer in $SRC/openexr/src/test/OpenEXRFuzzTest/oss-fuzz/*_fuzzer.cc; do
$CXX $CXXFLAGS -std=c++11 -pthread ${INCLUDES[@]} $fuzzer $LIB_FUZZING_ENGINE ${LIBS[@]} -lz \
-o $OUT/$fuzzer_basename
done


# ProtobufMutator does not currently support MSan
if [[ $CFLAGS = *sanitize=memory* ]]; then
exit 0
fi

# For fuzz-introspector, exclude files we don't want
export FUZZ_INTROSPECTOR_CONFIG=$SRC/fuzz_introspector_exclusion.config
cat > $FUZZ_INTROSPECTOR_CONFIG <<EOF
FILES_TO_AVOID
LPM
genfiles
EOF

# Compile json proto.
rm -rf genfiles && mkdir genfiles && $SRC/LPM/external.protobuf/bin/protoc exr.proto --cpp_out=genfiles --proto_path=$SRC

# Compile LPM fuzzer.
$CXX $CXXFLAGS -std=c++14 -pthread ${INCLUDES[@]} -I genfiles -I $SRC/libprotobuf-mutator/ -I $SRC/LPM/external.protobuf/include $LIB_FUZZING_ENGINE \
$SRC/exr_proto_fuzzer.cc genfiles/exr.pb.cc $SRC/exr_proto_converter.cc \
$SRC/LPM/src/libfuzzer/libprotobuf-mutator-libfuzzer.a \
$SRC/LPM/src/libprotobuf-mutator.a \
-Wl,--start-group $SRC/LPM/external.protobuf/lib/lib*.a -Wl,--end-group \
${LIBS[@]} -lz -o $OUT/exr_proto_fuzzer
73 changes: 73 additions & 0 deletions projects/openexr/exr.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

syntax = "proto2";
// Very simple proto description of the EXR format,
// described at https://openexr.com/en/latest/OpenEXRFileLayout.html

// we will calculate xMax and yMax from with width & height
message Exr_box2i {
required int32 xMin = 1;
required int32 yMin = 2;
required int32 w = 3;
required int32 h = 4;
}

message Exr_channel {
required string chname = 1;
required int32 pixel_type = 2;
required uint32 pLinear = 3;
// here there should be three \x00 chars
required int32 xSampling = 4;
required int32 ySampling = 5;
}

message Exr_chlist {
// this should be null terminated
repeated Exr_channel channels = 1;
}

message Exr_compression {
required uint32 compression = 1;
}

message Exr_v2f {
required float f1 = 1;
required float f2 = 2;
}

message Exr_lineOrder {
required uint32 lineOrder = 1;
}

message ExrHeader {
required Exr_chlist channel_list = 1;
required Exr_compression compression = 2;
required Exr_box2i dataWindow = 3;
required Exr_box2i displayWindow = 4;
required Exr_lineOrder lineOrder = 5;
required float pixelAspectRatio = 6;
required Exr_v2f screenWindowCenter = 7;
required float screenWindowWidth = 8;
required bytes other_attr = 9;
}

message ExrProto {
required ExrHeader header = 1;
required bytes scanlines = 2;
}

// package fuzzer_examples;
192 changes: 192 additions & 0 deletions projects/openexr/exr_proto_converter.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#include "exr_proto_converter.h"

static void WriteInt(std::stringstream &out, uint32_t x) {
//x = __builtin_bswap32(x);
out.write((char *)&x, sizeof(x));
}

static void WriteLong(std::stringstream &out, uint64_t x) {
//x = __builtin_bswap64(x);
out.write((char *)&x, sizeof(x));
}

static void WriteFloat(std::stringstream &out, float x) {
out.write((char *)&x, sizeof(x));
}

static void WriteByte(std::stringstream &out, uint8_t x) {
out.write((char *)&x, sizeof(x));
}

static void WriteString(std::stringstream &out, std::string x) {
out.write(x.data(), x.size());
out.write("\x00", 1);
}

static void WriteNString(std::stringstream &out, std::string x) {
out.write(x.data(), x.size());
}

static void WriteTypeInfo(std::stringstream &out, std::string name, std::string type, int size) {
WriteString(out, name);
WriteString(out, type);
WriteInt(out, size);
}

static int CalculateChannelSize (int chan_type) {
int size = 0;
switch (chan_type % 3) {
case 1:
size = 2;
break;
default:
size = 4;
}
return size;
}

const float MIN_PIXEL_ASPECT_RATIO = 1e-6f;
const float MAX_PIXEL_ASPECT_RATIO = 1e+6f;

std::string ProtoToExr(const ExrProto &exr_proto) {
std::stringstream all;
const unsigned char magic[] = {0x76, 0x2f, 0x31, 0x01};
all.write((const char*)magic, sizeof(magic));

// we only support version2 in single flat scanlines for now
const unsigned char version[] = {0x02, 0x00, 0x00, 0x00};
all.write((const char*)version, sizeof(version));

auto &header = exr_proto.header();

std::stringstream channels;
std::unordered_set<std::string> used_channels;
int pixel_data_size = 0;
for (const auto& chan : header.channel_list().channels()) {
std::string chname = "G";
if (!chan.chname().empty() && std::isprint(static_cast<unsigned char>(chan.chname()[0]))) {
chname = chan.chname();
}
if (used_channels.find(chname) != used_channels.end()) {
continue; // we don't care about this one. bad but at least works
} else {
used_channels.insert(chname);
}
WriteString(channels, chname);

WriteInt(channels, chan.pixel_type() % 3); // UINT, HALF, FLOAT
WriteByte(channels, chan.plinear() % 2); // 0 or 1
WriteByte(channels, 0);
WriteByte(channels, 0);
WriteByte(channels, 0);
WriteInt(channels, std::max(chan.xsampling(), 1));
WriteInt(channels, std::max(chan.ysampling(), 1));

pixel_data_size += CalculateChannelSize(chan.pixel_type());
}
WriteTypeInfo(all, "channels", "chlist", channels.str().size() + 1);
WriteString(all, channels.str()); // value

WriteTypeInfo(all, "compression", "compression", 1);
WriteByte(all, header.compression().compression() % 10); //value

// this is the base point
auto xmin = header.datawindow().xmin();
auto ymin = header.datawindow().ymin();

// this are the lenght of the sides (h and w) so we compute the other coordinates
auto xmax = xmin + std::max(std::min(header.datawindow().w(), 0), 20);
auto ymax = ymin + std::max(std::min(header.datawindow().h(), 0), 20);

WriteTypeInfo(all, "dataWindow", "box2i", 16);
WriteInt(all, xmin);
WriteInt(all, ymin);
WriteInt(all, xmax);
WriteInt(all, ymax);

// we are copying datawindow values to decrease the number of operations
// but this also reduce the fuzzed testcases
WriteTypeInfo(all, "displayWindow", "box2i", 16);
WriteInt(all, xmin);
WriteInt(all, ymin);
WriteInt(all, xmax);
WriteInt(all, ymax);

WriteTypeInfo(all, "lineOrder", "lineOrder", 1);
WriteByte(all, header.lineorder().lineorder() % 3);

WriteTypeInfo(all, "pixelAspectRatio", "float", 4);
auto par = std::max(MIN_PIXEL_ASPECT_RATIO, std::min(header.pixelaspectratio(), MAX_PIXEL_ASPECT_RATIO));
if (par == 0.0) { par += 0.1337; }
WriteFloat(all, par);

WriteTypeInfo(all, "screenWindowCenter", "v2f", 8);
WriteFloat(all, header.screenwindowcenter().f1());
WriteFloat(all, header.screenwindowcenter().f2());

WriteTypeInfo(all, "screenWindowWidth", "float", 4);
WriteFloat(all, header.screenwindowwidth());

// end of header
WriteByte(all, 0);

auto n_channels = header.channel_list().channels_size();
auto lines = (xmax - xmin);
auto n_pixels = (ymax - ymin);

pixel_data_size = pixel_data_size * n_pixels;

// base_offset contains the header size and the offset table size
// the offset table contains 64bit integers for every scanline
auto base_offset = static_cast<int>(all.tellp()) + (lines * 8);

std::stringstream offset_table;
std::stringstream scanlines;

for (int i = 0; i<= lines; i++) {
// write the offset for the current line
// (4 + 4 + n_pixels * n_channels) for every scanline
WriteInt(offset_table, base_offset + (i * (8 + pixel_data_size)));

// write scanline number
WriteInt(scanlines, i);
// write pixel data size times pixel number
WriteInt(scanlines, pixel_data_size);

// write the pixel data
for (const auto& chan : header.channel_list().channels()) {
std::string pixels(n_pixels * CalculateChannelSize(chan.pixel_type()), '\xea');
pixels.replace(0, exr_proto.scanlines().size(), exr_proto.scanlines());
WriteNString(scanlines, pixels);
}
}

WriteNString(all, offset_table.str());
WriteByte(all, 255);
WriteNString(all, scanlines.str());

std::string res = all.str();
if (const char *dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) {
// With libFuzzer binary run this to generate a EXR file x.exr:
// PROTO_FUZZER_DUMP_PATH=x.exr ./a.out proto-input
std::ofstream of(dump_path, std::ios::binary);
of.write(res.data(), res.size());
}
return res;
}
31 changes: 31 additions & 0 deletions projects/openexr/exr_proto_converter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#ifndef EXR_PROTO_CONVERTER_H
#define EXR_PROTO_CONVERTER_H

#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
#include "exr.pb.h"

#include <string>
#include <sstream>
#include <fstream>
#include <cstdlib>
#include <algorithm>

std::string ProtoToExr(const ExrProto &exr_proto);

#endif // EXR_PROTO_CONVERTER_H
31 changes: 31 additions & 0 deletions projects/openexr/exr_proto_fuzzer.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////

#include "libprotobuf-mutator/src/libfuzzer/libfuzzer_macro.h"
#include "exr.pb.h"

#include <ImfNamespace.h>
#include <ImfCheckFile.h>
#include <string>

#include "exr_proto_converter.h"

DEFINE_PROTO_FUZZER(const ExrProto &exr) {
std::string data = ProtoToExr(exr);

Imf::checkOpenEXRFile (data.c_str() , data.size() , true , true , true);
return;
}
1 change: 1 addition & 0 deletions projects/openexr/project.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ auto_ccs:
- "[email protected]"
- "[email protected]"
- "[email protected]"
- "[email protected]"
main_repo: 'https://github.com/AcademySoftwareFoundation/openexr'

fuzzing_engines:
Expand Down
Loading