Skip to content
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
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,7 @@ if(UHDR_BUILD_TESTS)
target_include_directories(ultrahdr_unit_test PRIVATE
${PRIVATE_INCLUDE_DIR}
${GTEST_INCLUDE_DIRS}
"${THIRD_PARTY_DIR}/image_io/includes"
)
if(UHDR_BUILD_FUZZERS)
target_link_options(ultrahdr_unit_test PRIVATE -fsanitize=fuzzer-no-link)
Expand Down Expand Up @@ -925,6 +926,17 @@ if(UHDR_BUILD_FUZZERS)
endif()
target_link_libraries(ultrahdr_dec_fuzzer ${UHDR_CORE_LIB_NAME})

add_executable(ultrahdr_xmp_fuzzer ${FUZZERS_DIR}/ultrahdr_xmp_fuzzer.cpp)
add_dependencies(ultrahdr_xmp_fuzzer ${UHDR_CORE_LIB_NAME})
target_compile_options(ultrahdr_xmp_fuzzer PRIVATE ${UHDR_WERROR_FLAGS})
target_include_directories(ultrahdr_xmp_fuzzer PRIVATE ${PRIVATE_INCLUDE_DIR})
if(DEFINED ENV{LIB_FUZZING_ENGINE})
target_link_options(ultrahdr_xmp_fuzzer PRIVATE $ENV{LIB_FUZZING_ENGINE})
else()
target_link_options(ultrahdr_xmp_fuzzer PRIVATE -fsanitize=fuzzer)
endif()
target_link_libraries(ultrahdr_xmp_fuzzer ${UHDR_CORE_LIB_NAME})

add_executable(ultrahdr_legacy_fuzzer ${FUZZERS_DIR}/ultrahdr_legacy_fuzzer.cpp)
add_dependencies(ultrahdr_legacy_fuzzer ${UHDR_CORE_LIB_NAME})
target_compile_options(ultrahdr_legacy_fuzzer PRIVATE ${UHDR_WERROR_FLAGS})
Expand Down
49 changes: 44 additions & 5 deletions examples/ultrahdr_app.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,9 @@ class UltraHdrAppInput {
public:
UltraHdrAppInput(const char* hdrIntentRawFile, const char* sdrIntentRawFile,
const char* sdrIntentCompressedFile, const char* gainmapCompressedFile,
const char* gainmapMetadataCfgFile, const char* exifFile, const char* outputFile,
int width, int height, uhdr_img_fmt_t hdrCf = UHDR_IMG_FMT_32bppRGBA1010102,
const char* gainmapMetadataCfgFile, const char* exifFile, const char* xmpFile,
const char* outputFile, int width, int height,
uhdr_img_fmt_t hdrCf = UHDR_IMG_FMT_32bppRGBA1010102,
uhdr_img_fmt_t sdrCf = UHDR_IMG_FMT_32bppRGBA8888,
uhdr_color_gamut_t hdrCg = UHDR_CG_DISPLAY_P3,
uhdr_color_gamut_t sdrCg = UHDR_CG_BT_709,
Expand All @@ -277,6 +278,7 @@ class UltraHdrAppInput {
mGainMapCompressedFile(gainmapCompressedFile),
mGainMapMetadataCfgFile(gainmapMetadataCfgFile),
mExifFile(exifFile),
mXmpFile(xmpFile),
mUhdrFile(nullptr),
mOutputFile(outputFile),
mWidth(width),
Expand Down Expand Up @@ -312,6 +314,7 @@ class UltraHdrAppInput {
mGainMapCompressedFile(nullptr),
mGainMapMetadataCfgFile(gainmapMetadataCfgFile),
mExifFile(nullptr),
mXmpFile(nullptr),
mUhdrFile(uhdrFile),
mOutputFile(outputFile),
mWidth(0),
Expand Down Expand Up @@ -370,6 +373,7 @@ class UltraHdrAppInput {
}
}
if (mExifBlock.data) free(mExifBlock.data);
if (mXmpBlock.data) free(mXmpBlock.data);
if (mUhdrImage.data) free(mUhdrImage.data);
}

Expand All @@ -385,6 +389,7 @@ class UltraHdrAppInput {
bool fillGainMapCompressedImageHandle();
bool fillGainMapMetadataDescriptor();
bool fillExifMemoryBlock();
bool fillXmpMemoryBlock();
void writeGainMapMetadataToFile(uhdr_gainmap_metadata_t* metadata, std::ostream& file);
bool convertRgba8888ToYUV444Image();
bool convertRgba1010102ToYUV444Image();
Expand All @@ -401,6 +406,7 @@ class UltraHdrAppInput {
const char* mGainMapCompressedFile;
const char* mGainMapMetadataCfgFile;
const char* mExifFile;
const char* mXmpFile;
const char* mUhdrFile;
const char* mOutputFile;
const int mWidth;
Expand Down Expand Up @@ -435,6 +441,7 @@ class UltraHdrAppInput {
uhdr_compressed_image_t mGainMapCompressedImage{};
uhdr_gainmap_metadata mGainMapMetadata{};
uhdr_mem_block_t mExifBlock{};
uhdr_mem_block_t mXmpBlock{};
uhdr_compressed_image_t mUhdrImage{};
uhdr_raw_image_t mDecodedUhdrRgbImage{};
uhdr_raw_image_t mDecodedUhdrYuv444Image{};
Expand Down Expand Up @@ -614,6 +621,19 @@ bool UltraHdrAppInput::fillExifMemoryBlock() {
return false;
}

bool UltraHdrAppInput::fillXmpMemoryBlock() {
std::ifstream ifd(mXmpFile, std::ios::binary | std::ios::ate);
if (ifd.good()) {
auto size = ifd.tellg();
mXmpBlock.data = nullptr;
mXmpBlock.data_sz = size;
mXmpBlock.capacity = size;
ifd.close();
return loadFile(mXmpFile, mXmpBlock.data, size);
}
return false;
}

void UltraHdrAppInput::writeGainMapMetadataToFile(uhdr_gainmap_metadata_t* metadata,
std::ostream& file) {
bool allChannelsIdentical = metadata->max_content_boost[0] == metadata->max_content_boost[1] &&
Expand Down Expand Up @@ -725,6 +745,12 @@ bool UltraHdrAppInput::encode() {
return false;
}
}
if (mXmpFile != nullptr) {
if (!fillXmpMemoryBlock()) {
std::cerr << " failed to load file " << mXmpFile << std::endl;
return false;
}
}

#define RET_IF_ERR(x) \
{ \
Expand Down Expand Up @@ -766,6 +792,9 @@ bool UltraHdrAppInput::encode() {
if (mExifFile != nullptr) {
RET_IF_ERR(uhdr_enc_set_exif_data(handle, &mExifBlock))
}
if (mXmpFile != nullptr) {
RET_IF_ERR(uhdr_enc_set_xmp_data(handle, &mXmpBlock))
}

if (mOutputFile != nullptr) {
std::string outStr(mOutputFile);
Expand Down Expand Up @@ -856,6 +885,11 @@ bool UltraHdrAppInput::decode() {
if (mProbe) {
std::cout << "Ultra HDR Image: Yes \nGainMap Metadata: " << std::endl;
writeGainMapMetadataToFile(metadata, std::cout);
uhdr_mem_block_t* xmp = uhdr_dec_get_xmp(handle);
if (xmp != nullptr && xmp->data != nullptr && xmp->data_sz > 0) {
std::cout << "XMP Metadata (" << xmp->data_sz << " bytes): \n"
<< std::string(static_cast<const char*>(xmp->data), xmp->data_sz) << std::endl;
}
uhdr_release_decoder(handle);
return true;
}
Expand Down Expand Up @@ -1501,6 +1535,7 @@ static void usage(const char* name) {
" For PQ content, this defaults to 10000 nits. \n"
" any real number in range [203, 10000]. \n");
fprintf(stderr, " -x binary input resource containing exif data to insert, optional. \n");
fprintf(stderr, " -X resource containing xmp data to insert, optional. \n");
fprintf(stderr, "\n## decoder options : \n");
fprintf(stderr, " -j ultra hdr compressed input resource, required. \n");
fprintf(stderr,
Expand Down Expand Up @@ -1594,10 +1629,11 @@ static void usage(const char* name) {
}

int main(int argc, char* argv[]) {
char opt_string[] = "p:y:i:g:f:w:h:C:c:t:q:o:O:m:j:e:a:b:z:R:s:M:Q:G:x:u:D:k:K:L:P";
char opt_string[] = "p:y:i:g:f:w:h:C:c:t:q:o:O:m:j:e:a:b:z:R:s:M:Q:G:x:X:u:D:k:K:L:P";
char *hdr_intent_raw_file = nullptr, *sdr_intent_raw_file = nullptr, *uhdr_file = nullptr,
*sdr_intent_compressed_file = nullptr, *gainmap_compressed_file = nullptr,
*gainmap_metadata_cfg_file = nullptr, *output_file = nullptr, *exif_file = nullptr;
*gainmap_metadata_cfg_file = nullptr, *output_file = nullptr, *exif_file = nullptr,
*xmp_file = nullptr;
int width = 0, height = 0;
uhdr_color_gamut_t hdr_cg = UHDR_CG_DISPLAY_P3;
uhdr_color_gamut_t sdr_cg = UHDR_CG_BT_709;
Expand Down Expand Up @@ -1702,6 +1738,9 @@ int main(int argc, char* argv[]) {
case 'x':
exif_file = optarg_s;
break;
case 'X':
xmp_file = optarg_s;
break;
case 'u':
enable_gles = atoi(optarg_s) == 1 ? true : false;
break;
Expand Down Expand Up @@ -1744,7 +1783,7 @@ int main(int argc, char* argv[]) {
}
UltraHdrAppInput appInput(
hdr_intent_raw_file, sdr_intent_raw_file, sdr_intent_compressed_file,
gainmap_compressed_file, gainmap_metadata_cfg_file, exif_file,
gainmap_compressed_file, gainmap_metadata_cfg_file, exif_file, xmp_file,
output_file ? output_file : "out.jpeg", width, height, hdr_cf, sdr_cf, hdr_cg, sdr_cg,
hdr_tf, quality, out_tf, out_cf, use_full_range_color_hdr, gainmap_scale_factor,
gainmap_compression_quality, use_multi_channel_gainmap, gamma, enable_gles, enc_preset,
Expand Down
8 changes: 8 additions & 0 deletions fuzzer/Android.bp
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,11 @@ cc_fuzz {
"ultrahdr_legacy_fuzzer.cpp",
],
}

cc_fuzz {
name: "ultrahdr_xmp_fuzzer",
defaults: ["ultrahdr_fuzzer_defaults"],
srcs: [
"ultrahdr_xmp_fuzzer.cpp",
],
}
3 changes: 2 additions & 1 deletion fuzzer/ossfuzz.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ mkdir -p ${build_dir}
pushd ${build_dir}

cmake $SRC/libultrahdr -DUHDR_BUILD_FUZZERS=1 -DUHDR_MAX_DIMENSION=1280 -DUHDR_BUILD_DEPS=1 -DBUILD_SHARED_LIBS=OFF
make -j$(nproc) ultrahdr_dec_fuzzer ultrahdr_enc_fuzzer ultrahdr_legacy_fuzzer
make -j$(nproc) ultrahdr_dec_fuzzer ultrahdr_enc_fuzzer ultrahdr_legacy_fuzzer ultrahdr_xmp_fuzzer
cp ${build_dir}/ultrahdr_dec_fuzzer $OUT/
cp ${build_dir}/ultrahdr_enc_fuzzer $OUT/
cp ${build_dir}/ultrahdr_legacy_fuzzer $OUT/
cp ${build_dir}/ultrahdr_xmp_fuzzer $OUT/
popd
97 changes: 97 additions & 0 deletions fuzzer/ultrahdr_xmp_fuzzer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright 2026 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
* https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
* <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
* option. This file may not be copied, modified, or distributed
* except according to those terms.
*/

#include <fuzzer/FuzzedDataProvider.h>

#include <string>

#include "ultrahdr/jpegrutils.h"
#include "ultrahdr/ultrahdrcommon.h"

using namespace ultrahdr;

// APP1 XMP identifier, as it appears on the wire ahead of the XML payload.
static const std::string kXmpNameSpace = std::string("http://ns.adobe.com/xap/1.0/\0", 29);

// The primary image XMP packet of a JPEG is attacker controlled: when a gain
// map is appended to an already compressed base image, that image's APP1 packet
// is inherited verbatim and handed to generateXmpForPrimaryImage(), which
// parses it, resolves namespace prefixes, rewrites the encoder owned gain map
// description in place and serializes the result. This target drives that code,
// and the XMP reader, with arbitrary bytes.
class UltraHdrXmpFuzzer {
public:
UltraHdrXmpFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
void process();

private:
void parseXmpPacket(const std::string& xml);

FuzzedDataProvider mFdp;
};

void UltraHdrXmpFuzzer::parseXmpPacket(const std::string& xml) {
std::string packet = kXmpNameSpace;
packet.append(xml);
uhdr_gainmap_metadata_ext_t parsed;
(void)getMetadataFromXMP(reinterpret_cast<uint8_t*>(packet.data()), packet.size(),
/* exif_data= */ nullptr, /* exif_size= */ 0, &parsed);
}

void UltraHdrXmpFuzzer::process() {
uhdr_gainmap_metadata_ext_t metadata("1.0");
bool are_all_channels_identical = mFdp.ConsumeBool();
int channels = are_all_channels_identical ? 1 : 3;
for (int i = 0; i < channels; i++) {
metadata.max_content_boost[i] = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 64.0f);
metadata.min_content_boost[i] = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 64.0f);
metadata.gamma[i] = mFdp.ConsumeFloatingPointInRange<float>(-1.0f, 5.0f);
metadata.offset_sdr[i] = mFdp.ConsumeFloatingPointInRange<float>(-1.0f, 1.0f);
metadata.offset_hdr[i] = mFdp.ConsumeFloatingPointInRange<float>(-1.0f, 1.0f);
}
if (are_all_channels_identical) {
for (int i = 1; i < 3; i++) {
metadata.max_content_boost[i] = metadata.max_content_boost[0];
metadata.min_content_boost[i] = metadata.min_content_boost[0];
metadata.gamma[i] = metadata.gamma[0];
metadata.offset_sdr[i] = metadata.offset_sdr[0];
metadata.offset_hdr[i] = metadata.offset_hdr[0];
}
}
metadata.hdr_capacity_min = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 48.0f);
metadata.hdr_capacity_max = mFdp.ConsumeFloatingPointInRange<float>(-4.0f, 48.0f);
metadata.use_base_cg = mFdp.ConsumeBool();

size_t secondary_image_length = mFdp.ConsumeIntegral<uint32_t>();
bool supply_user_xmp = mFdp.ConsumeBool();
std::string user_xmp = mFdp.ConsumeRemainingBytesAsString();

// Merge path. An empty return value means the packet was rejected; the
// encoder turns that into UHDR_CODEC_INVALID_PARAM.
std::string merged;
if (supply_user_xmp) {
uhdr_mem_block_t user_block{user_xmp.data(), user_xmp.size(), user_xmp.size()};
merged = generateXmpForPrimaryImage(secondary_image_length, metadata, &user_block);
} else {
merged = generateXmpForPrimaryImage(secondary_image_length, metadata, nullptr);
}

// Whatever the merge emits must survive our own reader.
if (!merged.empty()) parseXmpPacket(merged);

// Also drive the reader directly with the untrusted packet.
if (!user_xmp.empty()) parseXmpPacket(user_xmp);
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
UltraHdrXmpFuzzer fuzzHandle(data, size);
fuzzHandle.process();
return 0;
}
Loading
Loading