Skip to content
Merged
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
3 changes: 3 additions & 0 deletions fuzzing/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ endif()

add_executable(ojph_expand_fuzz_target fuzz_targets/ojph_expand_fuzz_target.cpp)
target_link_libraries(ojph_expand_fuzz_target PRIVATE openjph)

add_executable(ojph_compress_fuzz_target fuzz_targets/ojph_compress_fuzz_target.cpp)
target_link_libraries(ojph_compress_fuzz_target PRIVATE openjph)
131 changes: 131 additions & 0 deletions fuzzing/fuzz_targets/ojph_compress_fuzz_target.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
//***************************************************************************/
// This software is released under the 2-Clause BSD license, included
// below.
//
// Copyright (c) 2019, Aous Naman
// Copyright (c) 2019, Kakadu Software Pty Ltd, Australia
// Copyright (c) 2019, The University of New South Wales, Australia
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//***************************************************************************/
// This file is part of the OpenJPH software implementation.
// File: ojph_compress_fuzz_target.cpp
// Fuzz target for the HTJ2K encoding (compression) path.
//***************************************************************************/

#include <cstdint>
#include <cstdlib>
#include <iostream>

#include "ojph_mem.h"
#include "ojph_file.h"
#include "ojph_codestream.h"
#include "ojph_params.h"

// Input layout (4 control bytes + pixel data):
// byte 0: [6:0] width-1 (1..128)
// byte 1: [6:0] height-1 (1..128)
// byte 2: [1:0] num_components-1 (1..4)
// [3:2] bit_depth selector (8,10,12,16)
// [4] is_signed
// [5] reversible
// [6] color_transform
// byte 3: [2:0] num_decompositions (0..5, clamped)
// [3] planar
// bytes 4+: pixel data (each byte becomes one sample)

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
{
if (Size < 5)
return 0;

ojph::ui32 width = (Data[0] & 0x7F) + 1;
ojph::ui32 height = (Data[1] & 0x7F) + 1;
ojph::ui32 num_comps = (Data[2] & 0x03) + 1;
ojph::ui32 bit_depth = (ojph::ui32[]){8, 10, 12, 16}[(Data[2] >> 2) & 0x03];
bool is_signed = (Data[2] >> 4) & 1;
bool reversible = (Data[2] >> 5) & 1;
bool color_transform = (Data[2] >> 6) & 1;
ojph::ui32 num_decomps = Data[3] & 0x07;
bool planar = (Data[3] >> 3) & 1;

if (num_decomps > 5) num_decomps = 5;
if (num_comps < 3) color_transform = false;
if (color_transform) planar = false;

const uint8_t *pixels = Data + 4;
size_t pixels_len = Size - 4;
size_t pix_idx = 0;

try
{
ojph::codestream cs;

ojph::param_siz siz = cs.access_siz();
siz.set_image_extent(ojph::point(width, height));
siz.set_num_components(num_comps);
for (ojph::ui32 c = 0; c < num_comps; ++c)
siz.set_component(c, ojph::point(1, 1), bit_depth, is_signed);

ojph::param_cod cod = cs.access_cod();
cod.set_num_decomposition(num_decomps);
cod.set_color_transform(color_transform);
cod.set_reversible(reversible);

if (!reversible)
cs.access_qcd().set_irrev_quant(0.0005f);

cs.set_planar(planar);

ojph::mem_outfile outfile;
outfile.open();
cs.write_headers(&outfile);

// Total rows to push: planar processes each component fully,
// interleaved processes one row from all components at a time.
ojph::ui32 total_rows = num_comps * height;
ojph::ui32 next_comp;
ojph::line_buf *line = cs.exchange(NULL, next_comp);

for (ojph::ui32 r = 0; r < total_rows; ++r)
{
ojph::si32 *dp = line->i32;
for (ojph::ui32 x = 0; x < width; ++x)
{
// Use fuzz bytes as sample values, wrapping around as needed
ojph::si32 val = (ojph::si32)pixels[pix_idx % pixels_len];
pix_idx++;
dp[x] = is_signed ? val - 128 : val;
}
line = cs.exchange(line, next_comp);
}

cs.flush();
cs.close();
}
catch (const std::exception &)
{
}
return 0;
}
123 changes: 110 additions & 13 deletions fuzzing/fuzz_targets/ojph_expand_fuzz_target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <vector>

#include <ojph_arch.h>
Expand All @@ -45,54 +46,147 @@
#include <ojph_codestream.h>
#include <ojph_message.h>
#include <exception>
#include <iostream>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size)
{
// The first 2 bytes are used to control decoder options:
// byte 0 bit 1: force planar mode
// byte 0 bit 2: force interleaved mode
// byte 1: number of resolutions to skip (0-7)
if (Size < 3)
return 0;

uint8_t opts = Data[0];
uint8_t skip_res = Data[1] & 0x07;
Data += 2;
Size -= 2;

bool force_planar = (opts & 0x02) != 0;
bool force_interleaved = (opts & 0x04) != 0;

try
{

ojph::mem_infile infile;
infile.open(reinterpret_cast<const ojph::ui8 *>(Data), Size);

ojph::codestream cs;

// Always enable resilience: all fuzzer inputs are untrusted/mutated,
// so the decoder must use its error-recovery path.
cs.enable_resilience();

cs.read_headers(&infile);

// Guard against inputs that cause excessive decoding work.
{
ojph::param_siz siz = cs.access_siz();
ojph::point extent = siz.get_image_extent();
ojph::point offset = siz.get_image_offset();
ojph::ui64 w = extent.x - offset.x;
ojph::ui64 h = extent.y - offset.y;
if (w * h * siz.get_num_components() > 65536)
{
cs.close();
return 0;
}

ojph::param_cod cod = cs.access_cod();
if (cod.get_num_decompositions() > 5)
{
cs.close();
return 0;
}

// Large precincts cause huge internal buffers and very expensive
// per-row wavelet transforms even for small images.
for (ojph::ui32 lev = 0; lev <= cod.get_num_decompositions(); ++lev)
{
ojph::size psiz = cod.get_precinct_size(lev);
if (psiz.w > 256 || psiz.h > 256)
{
cs.close();
return 0;
}
}
}

if (skip_res > 0)
cs.restrict_input_resolution(skip_res, skip_res);

if (force_planar)
cs.set_planar(true);
else if (force_interleaved)
cs.set_planar(false);

cs.create();

if (cs.is_planar())
ojph::param_siz siz = cs.access_siz();

// Second guard: cap reconstructed dimensions after create().
{
ojph::param_siz siz = cs.access_siz();
ojph::ui64 total_recon = 0;
for (ojph::ui32 c = 0; c < siz.get_num_components(); ++c)
total_recon += (ojph::ui64)siz.get_recon_width(c)
* (ojph::ui64)siz.get_recon_height(c);
if (total_recon > 65536)
{
cs.close();
return 0;
}
}

// Time budget: abort if decoding takes too long.
struct timespec start_ts;
clock_gettime(CLOCK_MONOTONIC, &start_ts);
ojph::ui32 pull_count = 0;
const ojph::ui32 MAX_SECONDS = 10;
bool timed_out = false;

if (cs.is_planar())
{
for (ojph::ui32 c = 0; c < siz.get_num_components() && !timed_out; ++c)
{
ojph::ui32 height = siz.get_recon_height(c);
for (ojph::ui32 i = height; i > 0; --i)
for (ojph::ui32 i = height; i > 0 && !timed_out; --i)
{
ojph::ui32 comp_num;
cs.pull(comp_num);
assert(comp_num == c);
if (++pull_count % 64 == 0)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if ((ojph::ui32)(now.tv_sec - start_ts.tv_sec) >= MAX_SECONDS)
timed_out = true;
}
}
}
}
else
{
ojph::param_siz siz = cs.access_siz();
ojph::ui32 height = siz.get_recon_height(0);
for (ojph::ui32 i = 0; i < height; ++i)
for (ojph::ui32 i = 0; i < height && !timed_out; ++i)
{
for (ojph::ui32 c = 0; c < siz.get_num_components(); ++c)
{
ojph::ui32 comp_num;
cs.pull(comp_num);
assert(comp_num == c);
if (++pull_count % 64 == 0)
{
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if ((ojph::ui32)(now.tv_sec - start_ts.tv_sec) >= MAX_SECONDS)
timed_out = true;
}
}
}
}

cs.close();
}
catch (const std::exception &e)
catch (const std::exception &)
{
std::cerr << e.what() << '\n';
}

return 0;
}

Expand All @@ -109,8 +203,11 @@ int main(int argc, char **argv) {
return -1;
}
rewind(f);
std::vector<uint8_t> buf(len);
size_t n = fread(buf.data(), 1, len, f);
// Prepend 2 control bytes (default: no skip)
std::vector<uint8_t> buf(len + 2);
buf[0] = 0;
buf[1] = 0;
size_t n = fread(buf.data() + 2, 1, len, f);
if(n != static_cast<size_t>(len)) {
return -1;
}
Expand Down
Loading