Return an error when tone mapping a gain map that was not decoded (#3377)
avifRGBImageApplyGainMap() dereferences gainMap->image without checking
it for null. With the default avifDecoder::imageContentToDecode, the
gain map metadata is read but gainMap->image is left null, so tone
mapping a normally decoded image through avifImageApplyGainMap() crashes
(null pointer dereference, CWE-476).
Reject a missing gain map image upfront, next to the other input
validation, and return AVIF_RESULT_INVALID_ARGUMENT, as
avifRGBImageComputeGainMap() already does for the same situation. The
gain map is then consistently rejected, including calls that would not
need its pixels (e.g. a target headroom equal to the base headroom).
---------
Co-authored-by: krishna28238-arch <krishna28238-arch@users.noreply.github.com>
diff --git a/src/gainmap.c b/src/gainmap.c
index 1b3df43..a8d6288 100644
--- a/src/gainmap.c
+++ b/src/gainmap.c
@@ -91,6 +91,14 @@
avifDiagnosticsPrintf(diag, "NULL input image");
return AVIF_RESULT_INVALID_ARGUMENT;
}
+ // The gain map image may be missing if it was not decoded (see
+ // avifDecoder::imageContentToDecode) or was never set. Fail early and
+ // consistently, even for calls that would not need the gain map pixels
+ // (e.g. a target headroom matching the base image headroom).
+ if (gainMap->image == NULL) {
+ avifDiagnosticsPrintf(diag, "gainMap->image is null (gain map image not decoded?)");
+ return AVIF_RESULT_INVALID_ARGUMENT;
+ }
AVIF_CHECKRES(avifGainMapValidateMetadata(gainMap, diag));
const uint32_t width = baseImage->width;
diff --git a/tests/gtest/avifgainmaptest.cc b/tests/gtest/avifgainmaptest.cc
index 71f8234..a26b8b3 100644
--- a/tests/gtest/avifgainmaptest.cc
+++ b/tests/gtest/avifgainmaptest.cc
@@ -1323,6 +1323,47 @@
avifRGBImageFreePixels(&tone_mapped);
}
+// Tone mapping needs the gain map pixel data. With the default
+// avifDecoder::imageContentToDecode, the gain map metadata is read but
+// gainMap->image is left null. Tone mapping such an image should return an
+// error rather than dereference a null pointer.
+TEST(ToneMapTest, ToneMapWithoutDecodedGainMap) {
+ ImagePtr image(avifImageCreateEmpty());
+ ASSERT_NE(image, nullptr);
+ DecoderPtr decoder(avifDecoderCreate());
+ ASSERT_NE(decoder, nullptr);
+ // Default imageContentToDecode: the gain map metadata is read but the gain
+ // map image is not decoded.
+ const avifResult result = avifDecoderReadFile(
+ decoder.get(), image.get(),
+ (std::string(data_path) + "seine_sdr_gainmap_srgb.avif").c_str());
+ ASSERT_EQ(result, AVIF_RESULT_OK)
+ << avifResultToString(result) << ": " << decoder->diag.error;
+ ASSERT_NE(image->gainMap, nullptr);
+ ASSERT_EQ(image->gainMap->image, nullptr);
+
+ // The check happens during input validation, before the headrooms are
+ // used, so any target headroom fails. Keep the headrooms of the original
+ // crash repro (the null dereference used to happen at the first use of
+ // gainMap->image).
+ image->gainMap->baseHdrHeadroom = {0, 1};
+ image->gainMap->alternateHdrHeadroom = {2, 1};
+
+ avifDiagnostics diag;
+ avifDiagnosticsClearError(&diag);
+
+ avifRGBImage tone_mapped = {};
+ tone_mapped.depth = 8;
+ tone_mapped.format = AVIF_RGB_FORMAT_RGBA;
+ EXPECT_EQ(
+ avifImageApplyGainMap(image.get(), image->gainMap,
+ /*hdrHeadroom=*/1.0f, AVIF_COLOR_PRIMARIES_BT709,
+ AVIF_TRANSFER_CHARACTERISTICS_SRGB, &tone_mapped,
+ /*clli=*/nullptr, &diag),
+ AVIF_RESULT_INVALID_ARGUMENT)
+ << diag.error;
+}
+
TEST(GainMapTest, OpaqueProperties) {
ImagePtr image = CreateTestImageWithGainMap(/*base_rendition_is_hdr=*/false);
ASSERT_NE(image, nullptr);